From 359665264075145d6cf5dfba55b5d44db7516e26 Mon Sep 17 00:00:00 2001 From: prosdev Date: Tue, 16 Dec 2025 22:35:21 -0800 Subject: [PATCH] feat: add minimal SDK example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements Task #9 - Create Minimal Example SDK Example SDK Features: - Logger Plugin - Console logging with levels (debug, info, warn, error) - Analytics Plugin - Event tracking with batching and auto-flush - Monitor Plugin - Cross-plugin event monitoring with statistics Demonstrates All Core Capabilities: ✅ Plugin registration and namespace setting ✅ Configuration with defaults (underwrite pattern) ✅ Event handling (pub/sub with wildcards) ✅ API exposure (plugin methods on SDK) ✅ Lifecycle management (init/destroy) ✅ Plugin communication via events Example Structure: - src/index.ts - Main SDK with demo usage - src/plugins/logger.ts - Logging plugin (104 lines) - src/plugins/analytics.ts - Analytics plugin (148 lines) - src/plugins/monitor.ts - Monitoring plugin (98 lines) - README.md - Comprehensive walkthrough (282 lines) - package.json - Dependencies and scripts - tsconfig.json - TypeScript configuration Key Demonstrations: - Multiple plugins working together - Plugin-to-plugin communication - Wildcard event subscriptions (analytics:*) - Config sharing between plugins - Proper cleanup on destroy - Auto-flush with timers - Statistics collection Closes #8 --- examples/minimal-sdk/README.md | 281 ++++++++++++++++++ examples/minimal-sdk/package.json | 20 ++ examples/minimal-sdk/src/index.ts | 150 ++++++++++ examples/minimal-sdk/src/plugins/analytics.ts | 146 +++++++++ examples/minimal-sdk/src/plugins/logger.ts | 102 +++++++ examples/minimal-sdk/src/plugins/monitor.ts | 96 ++++++ examples/minimal-sdk/tsconfig.json | 23 ++ 7 files changed, 818 insertions(+) create mode 100644 examples/minimal-sdk/README.md create mode 100644 examples/minimal-sdk/package.json create mode 100644 examples/minimal-sdk/src/index.ts create mode 100644 examples/minimal-sdk/src/plugins/analytics.ts create mode 100644 examples/minimal-sdk/src/plugins/logger.ts create mode 100644 examples/minimal-sdk/src/plugins/monitor.ts create mode 100644 examples/minimal-sdk/tsconfig.json diff --git a/examples/minimal-sdk/README.md b/examples/minimal-sdk/README.md new file mode 100644 index 0000000..e909c23 --- /dev/null +++ b/examples/minimal-sdk/README.md @@ -0,0 +1,281 @@ +# Minimal SDK Example + +This example demonstrates how to build a complete SDK using `@lytics/sdk-kit`. + +## Features Demonstrated + +### ✅ Plugin System +- **Logger Plugin** - Console logging with levels +- **Analytics Plugin** - Event tracking with batching +- **Monitor Plugin** - Cross-plugin event monitoring + +### ✅ Core Capabilities +- **Configuration** - Hierarchical config with defaults +- **Events** - Pub/sub event system with wildcards +- **Lifecycle** - Init/destroy with proper cleanup +- **API Exposure** - Public methods from plugins +- **Plugin Communication** - Inter-plugin events + +## Project Structure + +``` +examples/minimal-sdk/ +├── src/ +│ ├── index.ts # Main SDK and demo +│ └── plugins/ +│ ├── logger.ts # Logging plugin +│ ├── analytics.ts # Analytics plugin +│ └── monitor.ts # Monitoring plugin +├── package.json +├── tsconfig.json +└── README.md +``` + +## Running the Example + +### Install Dependencies + +From the repository root: + +```bash +pnpm install +``` + +### Build the Example + +```bash +cd examples/minimal-sdk +pnpm build +``` + +### Run the Demo + +```bash +pnpm start +``` + +## Code Walkthrough + +### 1. Logger Plugin + +**File:** `src/plugins/logger.ts` + +Demonstrates: +- Setting a namespace (`plugin.ns('logger')`) +- Providing config defaults +- Exposing public methods (`debug`, `info`, `warn`, `error`) +- Listening to SDK lifecycle events +- Emitting custom events + +```typescript +export const loggerPlugin: PluginFunction = (plugin, instance, config) => { + plugin.ns('logger'); + + plugin.defaults({ + logger: { + enabled: true, + level: 'info', + }, + }); + + plugin.expose({ + debug(message: string) { /* ... */ }, + info(message: string) { /* ... */ }, + // ... + }); + + instance.on('sdk:ready', () => { + console.log('SDK ready!'); + }); +}; +``` + +### 2. Analytics Plugin + +**File:** `src/plugins/analytics.ts` + +Demonstrates: +- Event batching and auto-flush +- Timer management in lifecycle +- Emitting events for other plugins +- Async operations + +```typescript +export const analyticsPlugin: PluginFunction = (plugin, instance, config) => { + plugin.ns('analytics'); + + plugin.expose({ + track(eventName: string, properties?: any) { + eventQueue.push({ eventName, properties }); + plugin.emit('analytics:tracked', { eventName }); + }, + flush: async () => { /* send to server */ } + }); + + instance.on('sdk:ready', () => { + startFlushTimer(); + }); +}; +``` + +### 3. Monitor Plugin + +**File:** `src/plugins/monitor.ts` + +Demonstrates: +- Wildcard event subscriptions (`analytics:*`) +- Plugin-to-plugin communication +- Reading config from other plugins +- Statistics collection + +```typescript +export const monitorPlugin: PluginFunction = (plugin, instance, config) => { + plugin.ns('monitor'); + + // Listen to all analytics events + instance.on('analytics:*', (data) => { + console.log('Analytics event:', data); + }); + + // Listen to specific events + instance.on('analytics:tracked', () => { + stats.trackedEvents++; + }); +}; +``` + +### 4. Main SDK + +**File:** `src/index.ts` + +Shows how to: +- Create SDK instance +- Register multiple plugins +- Configure plugins +- Use exposed APIs +- Subscribe to events +- Handle lifecycle + +```typescript +const sdk = createMySDK({ + logger: { level: 'debug' }, + analytics: { batchSize: 3 }, +}); + +sdk.on('sdk:ready', () => console.log('Ready!')); + +await sdk.init(); + +// Use plugin methods +sdk.info('Hello world'); +sdk.track('button_clicked', { button: 'sign_up' }); +sdk.identify('user-123', { name: 'John' }); + +// Check stats from monitor +const stats = sdk.getStats(); + +await sdk.destroy(); +``` + +## Expected Output + +When you run the demo, you should see: + +``` +============================================================ +SDK Kit - Minimal Example +============================================================ + +[Monitor] Analytics event: { ... } +2024-01-01T12:00:00.000Z [MySDK] [INFO] SDK initialized successfully + +✅ SDK is ready! + +2024-01-01T12:00:00.001Z [MySDK] [DEBUG] Debug information +📊 Event tracked: button_clicked +📊 Event tracked: form_submitted + +⏱️ Waiting for auto-flush... + +[Analytics] Flushing 3 events to https://my-analytics.example.com/events +[Analytics] Events: [...] + +📈 Monitoring Stats: +{ trackedEvents: 3, identifyCalls: 1, pageViews: 1, ... } + +📝 All Logs: +2024-01-01T12:00:00.000Z [MySDK] [INFO] SDK initialized +... + +⚙️ Configuration: +Analytics endpoint: https://my-analytics.example.com/events +Logger level: debug + +🧹 Destroying SDK... +2024-01-01T12:00:01.000Z [MySDK] [INFO] SDK destroyed + +✅ Demo complete! +============================================================ +``` + +## Key Takeaways + +### Plugin Architecture +- Plugins are **pure functions** that receive capabilities +- Each plugin sets a **unique namespace** +- Plugins can **expose methods** that become part of the SDK API +- Plugins **communicate via events** (decoupled) + +### Configuration +- Hierarchical config with **dot-notation** access +- **Defaults are underwritten** (user config wins) +- Config is **accessible** to all plugins +- Can be **updated at runtime** + +### Events +- **Pub/sub pattern** for loose coupling +- **Wildcard subscriptions** (e.g., `analytics:*`) +- **Lifecycle events** (`sdk:init`, `sdk:ready`, `sdk:destroy`) +- **Custom events** for plugin communication + +### Lifecycle +- **`init()`** - Start the SDK, emit events +- **`destroy()`** - Clean up resources, flush data +- Plugins can **hook into lifecycle** via events +- Proper **cleanup** prevents memory leaks + +## Extending This Example + +You can easily add more plugins: + +```typescript +// Create a new plugin +export const myPlugin: PluginFunction = (plugin, instance, config) => { + plugin.ns('my.plugin'); + + plugin.defaults({ + my: { plugin: { setting: 'value' } } + }); + + plugin.expose({ + myMethod() { + plugin.emit('my:event', { data: 'value' }); + } + }); +}; + +// Register it +sdk.use(myPlugin); +``` + +## Next Steps + +- Check out the [Core Package README](../../packages/core/README.md) for full API documentation +- See the [Architecture Notes](../../notes/architecture.md) for design details +- Build your own plugins following these patterns +- Create a production-ready SDK with error handling, retries, etc. + +## Questions? + +See the main [README](../../README.md) or check the [specs](../../specs) for more information. + diff --git a/examples/minimal-sdk/package.json b/examples/minimal-sdk/package.json new file mode 100644 index 0000000..b873ae6 --- /dev/null +++ b/examples/minimal-sdk/package.json @@ -0,0 +1,20 @@ +{ + "name": "minimal-sdk-example", + "version": "1.0.0", + "description": "Minimal example SDK built with @lytics/sdk-kit", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "start": "node dist/index.js" + }, + "dependencies": { + "@lytics/sdk-kit": "workspace:*" + }, + "devDependencies": { + "typescript": "^5.9.3" + } +} + diff --git a/examples/minimal-sdk/src/index.ts b/examples/minimal-sdk/src/index.ts new file mode 100644 index 0000000..5c26520 --- /dev/null +++ b/examples/minimal-sdk/src/index.ts @@ -0,0 +1,150 @@ +/** + * Minimal SDK Example + * + * This example demonstrates all core features of SDK Kit: + * - Plugin registration + * - Configuration management + * - Event handling + * - Public API exposure + * - Lifecycle management + */ + +import { SDK } from '@lytics/sdk-kit'; +import { analyticsPlugin } from './plugins/analytics'; +import { loggerPlugin } from './plugins/logger'; +import { monitorPlugin } from './plugins/monitor'; + +// Extend SDK type to include plugin methods +declare module '@lytics/sdk-kit' { + interface SDK { + // Logger methods + debug(message: string, data?: any): void; + info(message: string, data?: any): void; + warn(message: string, data?: any): void; + error(message: string, data?: any): void; + getLogs(): string[]; + + // Analytics methods + track(eventName: string, properties?: Record): void; + identify(userId: string, traits?: Record): void; + page(pageName: string, properties?: Record): void; + flush(): Promise; + + // Monitor methods + getStats(): Record; + resetStats(): void; + } +} + +/** + * Create and export the SDK instance + */ +export function createMySDK(config?: Record) { + const sdk = new SDK({ + name: 'minimal-sdk-example', + version: '1.0.0', + ...config, + }); + + // Register plugins + sdk.use(loggerPlugin).use(analyticsPlugin).use(monitorPlugin); + + return sdk; +} + +/** + * Demo: Run the SDK + */ +async function demo() { + console.log('='.repeat(60)); + console.log('SDK Kit - Minimal Example'); + console.log('='.repeat(60)); + console.log(); + + // Create SDK with custom config + const sdk = createMySDK({ + logger: { + level: 'debug', + prefix: '[MySDK]', + }, + analytics: { + endpoint: 'https://my-analytics.example.com/events', + batchSize: 3, // Flush after 3 events + }, + monitor: { + verbose: true, + }, + }); + + // Subscribe to events before init + sdk.on('sdk:ready', () => { + console.log('\n✅ SDK is ready!\n'); + }); + + sdk.on('analytics:tracked', (event: any) => { + console.log(`📊 Event tracked: ${event.name}`); + }); + + // Initialize SDK + await sdk.init(); + + // Use logger methods (from loggerPlugin) + sdk.info('SDK initialized successfully'); + sdk.debug('Debug information', { timestamp: Date.now() }); + + // Use analytics methods (from analyticsPlugin) + sdk.identify('user-123', { + name: 'John Doe', + email: 'john@example.com', + }); + + sdk.track('button_clicked', { + button: 'sign_up', + page: '/home', + }); + + sdk.page('Home Page', { + path: '/home', + referrer: document?.referrer || 'direct', + }); + + sdk.track('form_submitted', { + form: 'newsletter', + }); + + // This should trigger auto-flush (batch size = 3) + console.log('\n⏱️ Waiting for auto-flush...\n'); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Check monitoring stats + const stats = sdk.getStats(); + console.log('\n📈 Monitoring Stats:'); + console.log(stats); + + // Get all logs + console.log('\n📝 All Logs:'); + const logs = sdk.getLogs(); + logs.forEach((log) => console.log(log)); + + // Demonstrate configuration access + console.log('\n⚙️ Configuration:'); + console.log('Analytics endpoint:', sdk.get('analytics.endpoint')); + console.log('Logger level:', sdk.get('logger.level')); + + // Update config at runtime + sdk.set('logger.level', 'warn'); + sdk.debug('This will not be logged (level is now warn)'); + sdk.warn('This warning will be logged'); + + // Cleanup + console.log('\n🧹 Destroying SDK...'); + await sdk.destroy(); + + console.log('\n✅ Demo complete!'); + console.log('='.repeat(60)); +} + +// Run demo if this is the main module +// Uncomment to run: demo().catch(console.error); + +export { analyticsPlugin, loggerPlugin, monitorPlugin }; diff --git a/examples/minimal-sdk/src/plugins/analytics.ts b/examples/minimal-sdk/src/plugins/analytics.ts new file mode 100644 index 0000000..28da916 --- /dev/null +++ b/examples/minimal-sdk/src/plugins/analytics.ts @@ -0,0 +1,146 @@ +/** + * Analytics Plugin + * + * Demonstrates: + * - Event emission + * - Config usage + * - Plugin communication via events + * - Public API exposure + */ + +import type { PluginFunction } from '@lytics/sdk-kit'; + +export const analyticsPlugin: PluginFunction = (plugin, instance, config) => { + // 1. Set namespace + plugin.ns('analytics'); + + // 2. Provide defaults + plugin.defaults({ + analytics: { + enabled: true, + endpoint: 'https://api.example.com/events', + batchSize: 10, + flushInterval: 5000, + }, + }); + + // Internal state + const eventQueue: any[] = []; + let flushTimer: ReturnType | null = null; + + /** + * Flush events to endpoint + */ + async function flush() { + if (eventQueue.length === 0) return; + + const enabled = config.get('analytics.enabled'); + if (!enabled) return; + + const endpoint = config.get('analytics.endpoint'); + const events = [...eventQueue]; + eventQueue.length = 0; + + // Emit event for other plugins + plugin.emit('analytics:flushing', { count: events.length }); + + // In a real SDK, this would make an HTTP request + console.log(`[Analytics] Flushing ${events.length} events to ${endpoint}`); + console.log('[Analytics] Events:', events); + + plugin.emit('analytics:flushed', { count: events.length, success: true }); + } + + /** + * Start auto-flush timer + */ + function startFlushTimer() { + if (flushTimer) return; + + const interval = config.get('analytics.flushInterval'); + flushTimer = setInterval(() => { + flush(); + }, interval); + } + + /** + * Stop auto-flush timer + */ + function stopFlushTimer() { + if (flushTimer) { + clearInterval(flushTimer); + flushTimer = null; + } + } + + // 3. Expose public API + plugin.expose({ + /** + * Track an event + */ + track(eventName: string, properties?: Record) { + const event = { + type: 'track', + name: eventName, + properties: properties || {}, + timestamp: Date.now(), + }; + + eventQueue.push(event); + plugin.emit('analytics:tracked', event); + + // Auto-flush if batch size reached + const batchSize = config.get('analytics.batchSize'); + if (eventQueue.length >= batchSize) { + flush(); + } + }, + + /** + * Identify a user + */ + identify(userId: string, traits?: Record) { + const event = { + type: 'identify', + userId, + traits: traits || {}, + timestamp: Date.now(), + }; + + eventQueue.push(event); + plugin.emit('analytics:identified', event); + }, + + /** + * Track a page view + */ + page(pageName: string, properties?: Record) { + const event = { + type: 'page', + name: pageName, + properties: properties || {}, + timestamp: Date.now(), + }; + + eventQueue.push(event); + plugin.emit('analytics:page', event); + }, + + /** + * Manually flush events + */ + flush, + }); + + // 4. Listen to lifecycle events + instance.on('sdk:ready', () => { + startFlushTimer(); + plugin.emit('analytics:ready'); + }); + + instance.on('sdk:destroy', () => { + stopFlushTimer(); + // Flush remaining events before destroy + flush(); + }); +}; diff --git a/examples/minimal-sdk/src/plugins/logger.ts b/examples/minimal-sdk/src/plugins/logger.ts new file mode 100644 index 0000000..0f535f4 --- /dev/null +++ b/examples/minimal-sdk/src/plugins/logger.ts @@ -0,0 +1,102 @@ +/** + * Logger Plugin + * + * Demonstrates: + * - Setting a namespace + * - Providing config defaults + * - Listening to events + * - Exposing public methods + */ + +import type { PluginFunction } from '@lytics/sdk-kit'; + +export const loggerPlugin: PluginFunction = (plugin, instance, config) => { + // 1. Set namespace (required for each plugin) + plugin.ns('logger'); + + // 2. Provide default configuration + plugin.defaults({ + logger: { + enabled: true, + level: 'info', // 'debug' | 'info' | 'warn' | 'error' + prefix: '[MySDK]', + }, + }); + + // Internal state + const logs: string[] = []; + + /** + * Internal log function + */ + function log(level: string, message: string, data?: any) { + const enabled = config.get('logger.enabled'); + const configLevel = config.get('logger.level'); + const prefix = config.get('logger.prefix'); + + if (!enabled) return; + + // Simple level filtering + const levels = ['debug', 'info', 'warn', 'error']; + if (levels.indexOf(level) < levels.indexOf(configLevel)) { + return; + } + + const timestamp = new Date().toISOString(); + const logMessage = `${timestamp} ${prefix} [${level.toUpperCase()}] ${message}`; + + console.log(logMessage, data || ''); + logs.push(logMessage); + + // Emit event so other plugins can react + plugin.emit('logger:logged', { level, message, data }); + } + + // 3. Expose public API methods + plugin.expose({ + /** + * Log a debug message + */ + debug(message: string, data?: any) { + log('debug', message, data); + }, + + /** + * Log an info message + */ + info(message: string, data?: any) { + log('info', message, data); + }, + + /** + * Log a warning message + */ + warn(message: string, data?: any) { + log('warn', message, data); + }, + + /** + * Log an error message + */ + error(message: string, data?: any) { + log('error', message, data); + }, + + /** + * Get all logs + */ + getLogs() { + return [...logs]; + }, + }); + + // 4. Listen to SDK lifecycle events + instance.on('sdk:ready', () => { + log('info', 'SDK initialized'); + }); + + instance.on('sdk:destroy', () => { + log('info', 'SDK destroyed'); + logs.length = 0; // Clear logs on destroy + }); +}; diff --git a/examples/minimal-sdk/src/plugins/monitor.ts b/examples/minimal-sdk/src/plugins/monitor.ts new file mode 100644 index 0000000..46fe322 --- /dev/null +++ b/examples/minimal-sdk/src/plugins/monitor.ts @@ -0,0 +1,96 @@ +/** + * Monitor Plugin + * + * Demonstrates: + * - Plugin-to-plugin communication via events + * - Wildcard event patterns + * - Reading from config + */ + +import type { PluginFunction } from '@lytics/sdk-kit'; + +export const monitorPlugin: PluginFunction = (plugin, instance, config) => { + // 1. Set namespace + plugin.ns('monitor'); + + // 2. Provide defaults + plugin.defaults({ + monitor: { + enabled: true, + verbose: false, + }, + }); + + // Internal counters + const stats = { + trackedEvents: 0, + identifyCalls: 0, + pageViews: 0, + logMessages: 0, + errors: 0, + }; + + // 3. Expose public API + plugin.expose({ + /** + * Get monitoring statistics + */ + getStats() { + return { ...stats }; + }, + + /** + * Reset statistics + */ + resetStats() { + stats.trackedEvents = 0; + stats.identifyCalls = 0; + stats.pageViews = 0; + stats.logMessages = 0; + stats.errors = 0; + }, + }); + + // 4. Listen to events from other plugins + const enabled = config.get('monitor.enabled'); + const verbose = config.get('monitor.verbose'); + + if (enabled) { + // Listen to all analytics events using wildcard + instance.on('analytics:*', (data: any) => { + if (verbose) { + console.log('[Monitor] Analytics event:', data); + } + }); + + // Listen to specific analytics events + instance.on('analytics:tracked', () => { + stats.trackedEvents++; + }); + + instance.on('analytics:identified', () => { + stats.identifyCalls++; + }); + + instance.on('analytics:page', () => { + stats.pageViews++; + }); + + // Listen to logger events + instance.on('logger:logged', (data: any) => { + stats.logMessages++; + if (data.level === 'error') { + stats.errors++; + } + }); + + // Log stats periodically + instance.on('sdk:ready', () => { + setInterval(() => { + if (verbose) { + console.log('[Monitor] Current stats:', stats); + } + }, 10000); // Every 10 seconds + }); + } +}; diff --git a/examples/minimal-sdk/tsconfig.json b/examples/minimal-sdk/tsconfig.json new file mode 100644 index 0000000..c8d3b0c --- /dev/null +++ b/examples/minimal-sdk/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": ["ES2020", "DOM"], + "moduleResolution": "bundler", + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "declarationMap": true, + "strict": false, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "paths": { + "@lytics/sdk-kit": ["../../packages/core/dist/index.d.ts"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} +