---
name: typescript-sdk
description: |
  Experten-Skill für TypeScript SDK Entwicklung mit tsup/Vitest. Nutze diesen Skill wenn:
  - Public APIs designed werden
  - Types und Interfaces definiert werden
  - Build-Konfiguration angepasst wird
  - JSDoc Dokumentation geschrieben wird
  - Tests mit Vitest implementiert werden
  Trigger-Keywords: "TypeScript", "types", "interface", "build", "tsup", "test", "vitest", "export"
---

# TypeScript SDK Development

Dieser Skill enthält Best Practices für die Entwicklung von TypeScript SDKs.

## Tech Stack

- **TypeScript**: 5.6+ mit strict mode
- **Build**: tsup (ESM + CJS + DTS)
- **Test**: Vitest
- **Docs**: TypeDoc

## Vor dem Start

**WICHTIG**: Context7 für aktuelle Patterns nutzen:

```
Context7: resolve-library-id → "typescript"
Context7: query-docs → "[specific topic like 'type guards' or 'strict mode']"
```

## Core Patterns

### 1. Public API Design

```typescript
// src/index.ts - Haupt-Export (klein halten!)

// Re-export main classes
export { LumosPrimitives } from './LumosPrimitives';
export { LunaSDK } from './LunaSDK';

// Re-export types (explicit!)
export type {
  LumosConfig,
  BurnResult,
  StakeInfo,
  VaultState,
} from './types';

// Re-export errors
export {
  LumosError,
  InsufficientFundsError,
  WalletNotConnectedError,
} from './errors';

// Namespace exports for sub-modules
export * as primitives from './primitives';
export * as ai from './ai';
```

### 2. Type Definitions

```typescript
// Prefer interfaces for object shapes
export interface BurnResult {
  /** Transaction hash */
  txHash: string;
  /** Amount burned in uLUNC */
  burnedAmount: string;
  /** On-chain proof ID */
  proofId: number;
  /** Block timestamp */
  timestamp: Date;
}

// Use type for unions, intersections, utilities
export type ChainId = 'columbus-5' | 'rebel-2' | 'localterra';
export type WithOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

// Branded types for type safety
export type Address = string & { readonly __brand: 'Address' };
export type TxHash = string & { readonly __brand: 'TxHash' };

function validateAddress(input: string): Address {
  if (!input.startsWith('terra1')) {
    throw new Error('Invalid Terra address');
  }
  return input as Address;
}
```

### 3. Type Guards

```typescript
// Runtime type checking
export function isBurnResult(obj: unknown): obj is BurnResult {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'txHash' in obj &&
    'burnedAmount' in obj &&
    typeof (obj as BurnResult).txHash === 'string' &&
    typeof (obj as BurnResult).burnedAmount === 'string'
  );
}

// Usage
const data: unknown = await fetchData();
if (isBurnResult(data)) {
  console.log(data.txHash); // TypeScript knows it's BurnResult
}
```

### 4. JSDoc for Public APIs

```typescript
/**
 * Burn LUNC tokens and receive an on-chain proof
 * 
 * @param amount - Amount to burn in uLUNC (1 LUNC = 1,000,000 uLUNC)
 * @param options - Optional burn configuration
 * @returns Promise resolving to burn result with proof
 * 
 * @throws {WalletNotConnectedError} If wallet not connected
 * @throws {InsufficientFundsError} If balance insufficient
 * @throws {ContractError} If contract execution fails
 * 
 * @example
 * ```typescript
 * // Burn 1 LUNC
 * const result = await lumos.proofOfBurn.burn('1000000');
 * console.log('Proof ID:', result.proofId);
 * ```
 * 
 * @example
 * ```typescript
 * // Burn with memo
 * const result = await lumos.proofOfBurn.burn('1000000', {
 *   memo: 'Community burn event'
 * });
 * ```
 * 
 * @see {@link BurnResult} for the return type
 * @since 2.0.0
 */
async burn(amount: string, options?: BurnOptions): Promise<BurnResult> {
  // Implementation
}
```

### 5. Error Classes

```typescript
/**
 * Base error class for all SDK errors
 */
export class LumosError extends Error {
  /** Error code for programmatic handling */
  readonly code: string;
  /** Additional error details */
  readonly details?: Record<string, unknown>;
  /** Whether the error is retryable */
  readonly retryable: boolean;

  constructor(
    message: string,
    code: string,
    options: { details?: Record<string, unknown>; retryable?: boolean } = {}
  ) {
    super(message);
    this.name = 'LumosError';
    this.code = code;
    this.details = options.details;
    this.retryable = options.retryable ?? false;
    
    // Maintains proper stack trace
    Error.captureStackTrace?.(this, this.constructor);
  }

  toJSON() {
    return {
      name: this.name,
      code: this.code,
      message: this.message,
      details: this.details,
    };
  }
}

// Specific errors
export class InsufficientFundsError extends LumosError {
  constructor(required: string, available: string) {
    super(
      `Insufficient funds: need ${required}, have ${available}`,
      'INSUFFICIENT_FUNDS',
      { details: { required, available } }
    );
    this.name = 'InsufficientFundsError';
  }
}
```

### 6. tsup Configuration

```typescript
// tsup.config.ts
import { defineConfig } from 'tsup';

export default defineConfig({
  entry: {
    index: 'src/index.ts',
    cli: 'src/cli/index.ts',
  },
  format: ['cjs', 'esm'],
  dts: true,
  splitting: false,
  sourcemap: true,
  clean: true,
  treeshake: true,
  minify: false, // Keep readable for debugging
  
  // External dependencies (don't bundle)
  external: [
    '@cosmjs/cosmwasm-stargate',
    '@cosmjs/stargate',
    'openai',
    '@anthropic-ai/sdk',
  ],
  
  // Banner for CLI
  banner: {
    js: '#!/usr/bin/env node',
  },
});
```

### 7. Package.json Exports

```json
{
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.mjs",
      "require": "./dist/index.js"
    },
    "./primitives": {
      "types": "./dist/primitives/index.d.ts",
      "import": "./dist/primitives/index.mjs",
      "require": "./dist/primitives/index.js"
    }
  },
  "typesVersions": {
    "*": {
      "primitives": ["./dist/primitives/index.d.ts"]
    }
  }
}
```

## Testing Patterns

### Unit Tests

```typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';

describe('ProofOfBurn', () => {
  let proofOfBurn: ProofOfBurn;

  beforeEach(() => {
    proofOfBurn = new ProofOfBurn({ network: 'testnet' });
    vi.clearAllMocks();
  });

  describe('burn()', () => {
    it('should return BurnResult on success', async () => {
      // Arrange
      vi.spyOn(proofOfBurn, 'getSigningClient').mockResolvedValue(mockClient);
      
      // Act
      const result = await proofOfBurn.burn('1000000');
      
      // Assert
      expect(result).toMatchObject({
        txHash: expect.stringMatching(/^[A-F0-9]{64}$/),
        burnedAmount: '1000000',
        proofId: expect.any(Number),
      });
    });

    it('should throw InsufficientFundsError when balance low', async () => {
      mockClient.execute.mockRejectedValue(new Error('insufficient funds'));
      
      await expect(proofOfBurn.burn('1000000000000'))
        .rejects
        .toThrow(InsufficientFundsError);
    });
  });
});
```

### Integration Tests

```typescript
describe.skip('ProofOfBurn Integration', () => {
  // Skip in CI, run manually against testnet
  it('should execute real burn on testnet', async () => {
    const proofOfBurn = new ProofOfBurn({
      network: 'testnet',
      mnemonic: process.env.TEST_MNEMONIC,
    });

    const result = await proofOfBurn.burn('1000');
    
    expect(result.txHash).toBeDefined();
    
    // Verify on chain
    const proof = await proofOfBurn.getProof(result.proofId);
    expect(proof.amount).toBe('1000');
  });
});
```

## Related Workflows

- `/run-tests` — Test-Commands
- `/build-release` — Release Workflow
