# @lytics/sdk-kit

[![npm version](https://img.shields.io/npm/v/@lytics/sdk-kit.svg)](https://www.npmjs.com/package/@lytics/sdk-kit)
[![npm downloads](https://img.shields.io/npm/dm/@lytics/sdk-kit.svg)](https://www.npmjs.com/package/@lytics/sdk-kit)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](../../LICENSE)

A lightweight, extensible TypeScript framework for building JavaScript SDKs with a powerful functional plugin architecture.

## Features

- 🔌 **Functional Plugin System** - Pure functions with capability injection
- 🎯 **TypeScript First** - Full type safety with strict mode
- 📦 **Tree-Shakeable** - Ship only what you use
- 🪶 **Zero Dependencies** - No runtime dependencies in core
- 🎨 **Flexible** - Build any kind of SDK (analytics, tracking, APIs, etc.)
- ⚡ **Event-Driven** - Built-in event system with wildcard support

## Installation

```bash
npm install @lytics/sdk-kit
# or
pnpm add @lytics/sdk-kit
# or
yarn add @lytics/sdk-kit
```

## Quick Start

### Basic Usage

```typescript
import { SDK } from '@lytics/sdk-kit';

// Create SDK instance
const sdk = new SDK({
  name: 'my-sdk',
  apiKey: 'abc123',
});

// Register plugins
sdk.use((plugin, instance, config) => {
  plugin.ns('analytics');
  
  plugin.defaults({
    analytics: {
      endpoint: 'https://api.example.com',
    },
  });
  
  plugin.expose({
    track(event: string, properties?: any) {
      const endpoint = config.get('analytics.endpoint');
      console.log(`Tracking ${event} to ${endpoint}`, properties);
    },
  });
});

// Initialize and use
await sdk.init();
sdk.track('page_view', { path: '/home' });
```

### Creating a Plugin

```typescript
import type { PluginFunction } from '@lytics/sdk-kit';

export const myPlugin: PluginFunction = (plugin, instance, config) => {
  // 1. Set namespace (required)
  plugin.ns('my.plugin');
  
  // 2. Set default config
  plugin.defaults({
    my: {
      plugin: {
        enabled: true,
        setting: 'default value',
      },
    },
  });
  
  // 3. Expose public methods
  plugin.expose({
    myMethod() {
      const setting = config.get('my.plugin.setting');
      console.log('My method called with:', setting);
    },
  });
  
  // 4. Listen to events
  instance.on('sdk:ready', () => {
    console.log('Plugin initialized!');
  });
  
  // 5. Emit custom events
  plugin.emit('my:custom:event', { data: 'value' });
};

// Usage
sdk.use(myPlugin);
```

## Core Concepts

### Plugin Capabilities

Each plugin receives a `plugin` object with these capabilities:

#### Namespace
```typescript
plugin.ns('my.plugin'); // Set plugin namespace (required, can only be set once)
```

#### Config Defaults
```typescript
plugin.defaults({
  my: { 
    plugin: { 
      setting: 'value' 
    } 
  }
});
// Note: User config always wins (underwrite pattern)
```

#### Event Emitter
```typescript
// Subscribe to events
plugin.on('event:name', (data) => console.log(data));

// Emit events
plugin.emit('my:event', { key: 'value' });

// Unsubscribe
plugin.off('event:name', handler);

// Wildcard support
plugin.on('track:*', (data) => {
  // Matches track:page, track:event, etc.
});
```

#### Expose API
```typescript
plugin.expose({
  publicMethod() {
    return 'Called from SDK';
  },
  anotherMethod(arg: string) {
    return `Arg: ${arg}`;
  },
});

// Now available as:
// sdk.publicMethod()
// sdk.anotherMethod('test')
```

#### Required Plugins
```typescript
plugin.mustEnable(); // Mark this plugin as required for SDK to work
```

### SDK Instance

The second parameter is the SDK instance with these methods:

```typescript
// Event handling
instance.on('sdk:ready', () => {});
instance.emit('custom:event', data);

// Config access
const value = config.get('path.to.value');
config.set('path.to.value', 'new value');

// Check initialization
if (instance.isReady()) {
  // SDK is initialized
}
```

### Configuration

The third parameter is the config object:

```typescript
// Get values with dot-notation
const timeout = config.get('api.timeout');
const nested = config.get('deeply.nested.value');

// Set values
config.set('api.timeout', 5000);

// Get all config
const allConfig = config.getAll();

// Set defaults (user config wins)
config.defaults({ api: { timeout: 3000 } });
```

## Lifecycle Events

The SDK emits these lifecycle events:

```typescript
// Before initialization
sdk.on('sdk:init', () => {
  console.log('SDK initializing...');
});

// After initialization (SDK is ready)
sdk.on('sdk:ready', () => {
  console.log('SDK ready!');
});

// During cleanup
sdk.on('sdk:destroy', () => {
  console.log('SDK cleaning up...');
});
```

## API Reference

### SDK Class

#### Constructor
```typescript
new SDK(config?: SDKConfig)
```

#### Methods

**`use(pluginFn: PluginFunction): this`**  
Register a plugin. Returns `this` for chaining.

**`async init(): Promise<void>`**  
Initialize the SDK. Idempotent (safe to call multiple times).

**`async destroy(): Promise<void>`**  
Destroy the SDK and clean up resources.

**`get<T>(path: string): T`**  
Get a config value by dot-notation path.

**`set(path: string, value: any): void`**  
Set a config value by dot-notation path.

**`on(event: string, handler: Function): () => void`**  
Subscribe to an event. Returns unsubscribe function.

**`off(event: string, handler: Function): void`**  
Unsubscribe from an event.

**`emit(event: string, ...args: any[]): void`**  
Emit an event.

**`getAll(): Record<string, any>`**  
Get all config as an immutable copy.

**`isReady(): boolean`**  
Check if SDK is initialized.

## Advanced Patterns

### Plugin Communication

Plugins can communicate via events:

```typescript
// Publisher plugin
sdk.use((plugin) => {
  plugin.ns('publisher');
  plugin.expose({
    publish(message: string) {
      plugin.emit('message:published', message);
    },
  });
});

// Subscriber plugin
sdk.use((plugin, instance) => {
  plugin.ns('subscriber');
  instance.on('message:published', (message) => {
    console.log('Received:', message);
  });
});
```

### Shared Configuration

Plugins can share config:

```typescript
// Config provider
sdk.use((plugin) => {
  plugin.ns('config.provider');
  plugin.defaults({
    shared: { apiKey: 'key123' },
  });
});

// Config consumer
sdk.use((plugin, instance, config) => {
  plugin.ns('config.consumer');
  const apiKey = config.get('shared.apiKey'); // 'key123'
});
```

### Async Initialization

Wait for SDK to be ready:

```typescript
sdk.use((plugin, instance) => {
  plugin.ns('async.plugin');
  
  instance.on('sdk:ready', async () => {
    // Do async setup
    await fetchRemoteConfig();
    console.log('Plugin ready!');
  });
});

await sdk.init();
```

### Method Chaining

```typescript
const sdk = new SDK({ name: 'my-sdk' })
  .use(plugin1)
  .use(plugin2)
  .use(plugin3);

await sdk.init();
```

## TypeScript Support

Full TypeScript support with strict types:

```typescript
import type { SDK, PluginFunction, Plugin } from '@lytics/sdk-kit';

// Type-safe plugin
const myPlugin: PluginFunction = (plugin, instance, config) => {
  plugin.ns('typed.plugin');
  
  plugin.expose({
    typedMethod(arg: string): number {
      return arg.length;
    },
  });
};

// Extend SDK type for exposed methods
declare module '@lytics/sdk-kit' {
  interface SDK {
    typedMethod(arg: string): number;
  }
}
```

## Examples

See the [examples directory](../../examples) for complete examples:

- **Basic SDK** - Simple SDK with tracking
- **Analytics SDK** - Full-featured analytics SDK
- **API Client** - REST API client wrapper
- **Real-time SDK** - WebSocket-based SDK

## Contributing

See [CONTRIBUTING.md](../../CONTRIBUTING.md) for development setup and guidelines.

## License

MIT © [Lytics](https://github.com/lytics)

