/** * Capability interfaces for the SDK Kit plugin system. * * These interfaces define the capabilities that plugins receive via capability injection. * Each capability provides specific functionality that plugins can use. * * @module capability.types */ /** * Capability for setting a plugin's namespace. * * Plugins must claim a namespace to identify themselves and scope their configuration. * The namespace is used for config scoping and event namespacing. * * @example * ```typescript * plugin.ns('analytics.tracking'); * ``` */ export interface Namespaced { /** * Set the plugin's namespace. * * @param namespace - Dot-notation namespace (e.g., 'analytics.tracking') */ ns(namespace: string): void; } /** * Capability for emitting and subscribing to events. * * Provides an event system with wildcard pattern matching support. * Plugins can listen to events and emit their own namespaced events. * * @example * ```typescript * // Subscribe to events * const unsubscribe = plugin.on('user.*', (event, data) => { * console.log('User event:', event, data); * }); * * // Emit events * plugin.emit('user.login', { userId: '123' }); * * // Unsubscribe * unsubscribe(); * ``` */ export interface Emitter { /** * Subscribe to an event. * * Supports wildcard patterns: * - Exact match: `'user.login'` * - Wildcard suffix: `'user.*'` matches all `user.` events * - Wildcard prefix: `'*.login'` matches all `.login` events * - Match all: `'*'` matches all events * * @param event - Event name or pattern * @param handler - Event handler function * @returns Unsubscribe function */ on(event: string, handler: (...args: any[]) => void): () => void; /** * Unsubscribe from an event. * * @param event - Event name or pattern * @param handler - Event handler function to remove */ off(event: string, handler: (...args: any[]) => void): void; /** * Emit an event. * * Notifies all matching listeners with the provided arguments. * * @param event - Event name * @param args - Arguments to pass to handlers */ emit(event: string, ...args: any[]): void; } /** * Capability for setting default configuration values. * * Plugins can provide default configuration that won't overwrite existing user config. * Uses an "underwrite" pattern where existing values take precedence. * * @example * ```typescript * plugin.defaults({ * analytics: { * tracking: { * enabled: true, * sampleRate: 1.0 * } * } * }); * ``` */ export interface Defaulter { /** * Set default configuration values. * * Only sets values that don't already exist in the config. * Existing user configuration always takes precedence. * * @param config - Default configuration object */ defaults(config: Record): void; } /** * Capability for exposing public API methods. * * Plugins can add methods to the SDK instance that users can call. * Exposed methods become part of the SDK's public API. * * @example * ```typescript * plugin.expose({ * track(event: string, properties?: Record) { * plugin.emit('analytics.track', { event, properties }); * }, * identify(userId: string) { * plugin.emit('analytics.identify', { userId }); * } * }); * * // Users can then call: * // sdk.track('Page Viewed', { path: '/home' }); * // sdk.identify('user-123'); * ``` */ export interface Exposer { /** * Expose methods on the SDK instance. * * @param api - Object containing methods to expose */ expose(api: Record): void; } /** * Capability for marking a plugin as required. * * Required plugins prevent the SDK from initializing if they're not properly configured. * This is useful for critical plugins that the SDK depends on. * * @example * ```typescript * plugin.mustEnable(); * ``` */ export interface Requirer { /** * Mark this plugin as required. * * The SDK will ensure required plugins are properly enabled before initialization. */ mustEnable(): void; } /** * Capability for plugins to extend other plugins. * * Allows plugins to register methods that other plugins can use, creating a * plugin-to-plugin API separate from the user-facing API exposed via expose(). * * This enables patterns like: * - Cross-cutting utilities (logging, metrics, debugging) * - Middleware patterns (consent gating, rate limiting) * - Validation utilities (schema validation, type checking) * * ## Type Safety with Module Augmentation * * For type-safe held capabilities, use TypeScript module augmentation to extend * the Plugin interface with your custom methods: * * ```typescript * // logging-plugin.ts * import type { Plugin } from '@lytics/sdk-kit'; * * // Extend the Plugin interface * declare module '@lytics/sdk-kit' { * interface Plugin { * log?(level: 'debug' | 'info' | 'warn' | 'error', message: string, data?: any): void; * } * } * * export const loggingPlugin: PluginFunction = (plugin, instance, config) => { * plugin.ns('logging'); * * plugin.hold({ * log(level: 'debug' | 'info' | 'warn' | 'error', message: string, data?: any) { * const namespace = this.namespace || 'sdk'; * console.log(`[${namespace}] [${level}] ${message}`, data); * } * }); * }; * ``` * * After importing the logging plugin, TypeScript will provide autocomplete * and type checking for plugin.log() calls in other plugins: * * ```typescript * // storage-plugin.ts * import '../logging-plugin'; // Triggers module augmentation * * export const storagePlugin: PluginFunction = (plugin, instance, config) => { * plugin.ns('storage'); * * // TypeScript knows about plugin.log() and its signature * plugin.log?.('info', 'Storage initialized'); * * plugin.expose({ * storage: { * set(key: string, value: any) { * // Autocomplete shows valid log levels * plugin.log?.('debug', 'Storage set', { key }); * localStorage.setItem(key, JSON.stringify(value)); * } * } * }); * }; * ``` * * ## Safe Patterns for Held Capabilities * * ### Pattern 1: Optional Dependencies (Graceful Degradation) * * ```typescript * function storagePlugin(plugin, instance, config) { * plugin.ns('storage'); * * // Check capability and warn if missing * if (!plugin.hasCapability('log')) { * console.warn('[storage] Logging plugin not loaded - logs disabled'); * } * * // Use optional chaining for safe access * plugin.log?.('info', 'Storage initialized'); * } * ``` * * ### Pattern 2: Required Dependencies (Fail Fast) * * ```typescript * function analyticsPlugin(plugin, instance, config) { * plugin.ns('analytics'); * * // Fail if required capability missing * if (!plugin.hasCapability('log')) { * throw new Error('Analytics plugin requires logging plugin to be loaded first'); * } * * // Safe to call without optional chaining * plugin.log('info', 'Analytics initialized'); * } * ``` * * ### Pattern 3: Conditional Features * * ```typescript * function transportPlugin(plugin, instance, config) { * plugin.ns('transport'); * * const hasLogging = plugin.hasCapability('log'); * const hasMetrics = plugin.hasCapability('recordMetric'); * * plugin.expose({ * send(request: any) { * const startTime = Date.now(); * * fetch(request.url, request.options) * .then(response => { * if (hasLogging) { * plugin.log?.('info', 'Request completed', { url: request.url }); * } * if (hasMetrics) { * plugin.recordMetric?.('http.duration', Date.now() - startTime); * } * }); * } * }); * } * ``` * * @example Basic usage - Providing a capability * ```typescript * // Logging plugin provides capability * function loggingPlugin(plugin, instance, config) { * plugin.ns('logging'); * * plugin.hold({ * log(level: string, message: string, data?: any) { * console.log(`[${this.namespace}] ${message}`, data); * } * }); * } * ``` * * @example Basic usage - Consuming a capability * ```typescript * // Storage plugin uses capability * function storagePlugin(plugin, instance, config) { * plugin.ns('storage'); * * if (!plugin.hasCapability('log')) { * console.warn('Logging not available'); * } * * plugin.log?.('info', 'Storage initialized'); * } * ``` */ export interface Extensible { /** * Register capabilities that other plugins can use. * * Methods are added to Plugin.prototype, making them available to all * plugin instances as if they were built-in capabilities. * * Held methods are bound to the calling plugin's context, so `this.namespace` * refers to the plugin that calls the method, not the plugin that registered it. * * **Load Order**: Plugins must call hold() before other plugins can use the capability. * Provider plugins should load before consumer plugins. * * **Type Safety**: Use TypeScript module augmentation to add type definitions for * held capabilities. See the Extensible interface documentation for examples. * * @param methods - Object containing methods to register * * @example Providing a logging capability * ```typescript * plugin.hold({ * log(level: string, message: string, data?: any) { * const namespace = this.namespace || 'sdk'; * console.log(`[${namespace}] ${message}`, data); * } * }); * ``` * * @example Providing multiple capabilities * ```typescript * plugin.hold({ * log(level: string, message: string, data?: any) { * console.log(`[${this.namespace}] ${message}`, data); * }, * debug(message: string, data?: any) { * if (config.get('debug.enabled')) { * console.debug(`[${this.namespace}] ${message}`, data); * } * } * }); * ``` */ hold(methods: Record): void; /** * Check if a capability has been registered. * * Use this to validate dependencies before calling held methods. * Enables graceful degradation when optional capabilities are missing. * * **Best Practice**: Always check capabilities before using them, or use * optional chaining (`plugin.log?.()`) for safe access. * * @param name - Capability name to check * @returns True if capability has been registered * * @example Checking optional dependency * ```typescript * if (!plugin.hasCapability('log')) { * console.warn('[storage] Logging not available - logs disabled'); * } * * // Safe to use with optional chaining * plugin.log?.('info', 'Operation completed'); * ``` * * @example Checking required dependency * ```typescript * if (!plugin.hasCapability('log')) { * throw new Error('This plugin requires logging plugin to be loaded first'); * } * * // Safe to call without optional chaining * plugin.log('info', 'Operation completed'); * ``` * * @example Conditional feature enablement * ```typescript * const hasLogging = plugin.hasCapability('log'); * const hasMetrics = plugin.hasCapability('recordMetric'); * * function performOperation() { * if (hasLogging) { * plugin.log?.('info', 'Operation started'); * } * // ... operation logic ... * if (hasMetrics) { * plugin.recordMetric?.('operations', 1); * } * } * ``` */ hasCapability(name: string): boolean; }