UNPKG

@resk/core

Version:

An innovative TypeScript framework that empowers developers to build applications with a fully decorator-based architecture for efficient resource management. By combining the power of decorators with a resource-oriented design, DecorRes enhances code cla

1,321 lines 54.6 kB
import { IClassConstructor } from "../types/index"; declare class Manager { static readonly sessionStorageMetaData: unique symbol; /** * The storage object used by the session manager. * * This property is initialized lazily when the `storage` getter is called. */ private static _storage; /** * The namespace prefix to use for all keys in the session storage. * * This property is optional and can be set using the `keyNamespace` setter. */ private static _keyNamespace?; /** * Retrieves or initializes the session storage implementation used by the Manager. * * This getter implements a sophisticated storage initialization strategy with multiple fallback mechanisms * to ensure reliable session storage across different environments and platforms. The method follows * a priority-based approach to determine the most suitable storage implementation available. * * ### Storage Priority Order: * 1. **Custom Attached Storage** - Storage registered via {@link AttachSessionStorage} decorator * 2. **Browser localStorage** - Native browser localStorage (client-side only) * 3. **In-Memory Storage** - Fallback dictionary-based storage (server-side or fallback) * * ### Initialization Process: * The getter performs the following steps in order: * 1. Checks for custom storage registered via reflection metadata * 2. Validates the custom storage using {@link isValidStorage} * 3. Falls back to browser localStorage if available and valid * 4. Creates in-memory storage as the final fallback option * 5. Caches the initialized storage for subsequent calls * * ### Platform Detection: * Uses {@link Platform.isClientSide} to detect the execution environment and choose * appropriate storage mechanisms. This ensures compatibility across: * - **Browser environments** - Uses localStorage when available * - **Server-side rendering** - Uses in-memory storage * - **Node.js environments** - Uses in-memory storage * - **React Native** - Can use custom storage implementations * * @returns The active session storage implementation conforming to {@link ISessionStorage} * * @example * ```typescript * // Basic usage - get the current storage * const storage = Manager.storage; * * // Use storage directly * storage.set('userToken', 'abc123'); * const token = storage.get('userToken'); * storage.remove('userToken'); * ``` * * @example * ```typescript * // Storage will automatically use localStorage in browser * if (Platform.isClientSide()) { * const storage = Manager.storage; // Uses localStorage * storage.set('preferences', { theme: 'dark', lang: 'en' }); * * // Data persists across page reloads * window.location.reload(); * const prefs = storage.get('preferences'); // Still available * } * ``` * * @example * ```typescript * // Server-side usage automatically uses in-memory storage * import { Manager } from '@resk/core/session'; * * // In Node.js environment * const storage = Manager.storage; // Uses in-memory storage * storage.set('sessionData', { userId: 123 }); * * // Data only persists during application lifetime * const data = storage.get('sessionData'); * console.log(data); // { userId: 123 } * ``` * * @example * ```typescript * // Custom storage integration example * @AttachSessionStorage() * class RedisStorage implements ISessionStorage { * constructor(private redis: RedisClient) {} * * get(key: string): any { * return this.redis.get(key); * } * * set(key: string, value: any): any { * this.redis.set(key, JSON.stringify(value)); * return value; * } * * remove(key: string): any { * const value = this.get(key); * this.redis.del(key); * return value; * } * * removeAll(): any { * this.redis.flushall(); * } * } * * // Now Manager.storage will use Redis * const storage = Manager.storage; // Returns RedisStorage instance * ``` * * @example * ```typescript * // Storage with error handling * try { * const storage = Manager.storage; * * // Attempt to store large object * const largeData = new Array(1000000).fill('data'); * storage.set('largeObject', largeData); * * } catch (error) { * console.error('Storage quota exceeded:', error); * * // Fallback to essential data only * const storage = Manager.storage; * storage.set('essentialData', { id: 1 }); * } * ``` * * @example * ```typescript * // Checking storage capabilities * const storage = Manager.storage; * * // Test if storage is persistent (localStorage) or temporary (in-memory) * storage.set('test', 'value'); * * if (Platform.isClientSide() && window.localStorage) { * console.log('Using persistent localStorage'); * // Data will survive page reloads * } else { * console.log('Using temporary in-memory storage'); * // Data will be lost on page reload * } * ``` * * @example * ```typescript * // Advanced: Storage introspection * const storage = Manager.storage; * * // Check which storage implementation is active * if ('localStorage' in storage.get.toString()) { * console.log('Using browser localStorage'); * } else if ('InMemoryStorage' in storage.get.toString()) { * console.log('Using in-memory storage'); * } else { * console.log('Using custom storage implementation'); * } * ``` * * @see {@link ISessionStorage} - Interface that all storage implementations must follow * @see {@link AttachSessionStorage} - Decorator for registering custom storage implementations * @see {@link isValidStorage} - Function used to validate storage implementations * @see {@link Platform.isClientSide} - Platform detection utility * @see {@link JsonHelper} - JSON serialization utilities used by storage * * @since 1.0.0 * @public * * @remarks * **Important Behavior Notes:** * - The storage is initialized lazily on first access for optimal performance * - Once initialized, the same storage instance is reused for all subsequent calls * - Custom storage registered via {@link AttachSessionStorage} takes highest priority * - Browser localStorage is only used in client-side environments with proper window object * - In-memory storage is automatically garbage collected when the application ends * * **Browser Compatibility:** * - Checks for `window.localStorage` availability before attempting to use it * - Gracefully degrades to in-memory storage if localStorage is disabled/unavailable * - Works in all major browsers including legacy versions * - Supports private/incognito browsing modes that may disable localStorage * * **Performance Characteristics:** * - **localStorage**: Synchronous, persistent, ~5-10MB limit per origin * - **In-memory**: Synchronous, non-persistent, limited by available RAM * - **Custom storage**: Performance depends on implementation (can be async) * * **Security Considerations:** * - localStorage data is accessible to all scripts on the same origin * - In-memory storage is more secure as it's not persistent * - Custom storage implementations should implement appropriate security measures * - Consider encryption for sensitive data regardless of storage type * * **Thread Safety:** * - localStorage operations are synchronous and atomic * - In-memory storage access is synchronous but not thread-safe across workers * - Custom storage implementations should handle concurrency appropriately */ static get storage(): ISessionStorage; /** * Sets the storage object used by the session manager. * * The provided storage object must be valid and have the required methods. * * @param {ISessionStorage} storage - The storage object to use. */ static set storage(storage: ISessionStorage); /** * Gets the current namespace prefix used for all session storage keys. * * This getter provides access to the global namespace prefix that is automatically prepended * to all session storage keys when they are sanitized. Namespacing is essential for creating * isolated storage contexts in applications that share the same storage backend, preventing * key collisions between different modules, features, or application instances. * * ### Key Benefits: * - **Isolation**: Prevents key conflicts between different application parts * - **Organization**: Groups related storage keys under a common prefix * - **Multi-tenancy**: Enables separate storage contexts for different users/tenants * - **Environment Separation**: Allows different prefixes for dev/staging/production * * ### Namespace Format: * The namespace is returned as a clean string without any trailing separators. * The system automatically adds the `-` separator when constructing the final key. * * @returns The current namespace prefix as a string, or empty string if no namespace is set * * @example * ```typescript * // Get the current namespace * const namespace = Manager.keyNamespace; * console.log(namespace); // "" (empty by default) * * // Set a namespace first * Manager.keyNamespace = "myapp"; * console.log(Manager.keyNamespace); // "myapp" * ``` * * @example * ```typescript * // Multi-tenant application example * class TenantSessionManager { * static setTenant(tenantId: string) { * Manager.keyNamespace = `tenant-${tenantId}`; * } * * static getCurrentTenant(): string { * const namespace = Manager.keyNamespace; * return namespace.replace('tenant-', ''); * } * } * * // Usage * TenantSessionManager.setTenant('acme-corp'); * console.log(Manager.keyNamespace); // "tenant-acme-corp" * * // All session operations now use this namespace * Session.set('userPrefs', { theme: 'dark' }); * // Stored as key: "tenant-acme-corp-userPrefs" * ``` * * @example * ```typescript * // Environment-based namespacing * const environment = process.env.NODE_ENV || 'development'; * const appVersion = process.env.APP_VERSION || '1.0.0'; * * Manager.keyNamespace = `${environment}-v${appVersion}`; * console.log(Manager.keyNamespace); // "production-v2.1.0" * * // All storage operations are now environment-scoped * Session.set('cache', data); // Stored as "production-v2.1.0-cache" * ``` * * @example * ```typescript * // Dynamic namespace checking * function ensureNamespace(requiredNamespace: string) { * const current = Manager.keyNamespace; * if (current !== requiredNamespace) { * throw new Error(`Expected namespace '${requiredNamespace}', got '${current}'`); * } * } * * // Usage in critical operations * Manager.keyNamespace = "secure-context"; * ensureNamespace("secure-context"); // Passes * Session.set('sensitiveData', encryptedData); * ``` * * @example * ```typescript * // Namespace validation and formatting * function validateNamespace(): boolean { * const namespace = Manager.keyNamespace; * * // Check for valid format * const isValidFormat = /^[a-z0-9-]+$/.test(namespace); * const hasNoConsecutiveDashes = !namespace.includes('--'); * const doesNotStartOrEndWithDash = !namespace.startsWith('-') && !namespace.endsWith('-'); * * return isValidFormat && hasNoConsecutiveDashes && doesNotStartOrEndWithDash; * } * * Manager.keyNamespace = "my-app-v1"; * console.log(validateNamespace()); // true * * Manager.keyNamespace = "invalid--namespace-"; * console.log(validateNamespace()); // false * ``` * * @see {@link keyNamespace} (setter) - Method to set the namespace prefix * @see {@link sanitizeKey} - Method that applies the namespace to keys * @see {@link Session.set} - Session storage method that uses namespaced keys * @see {@link Session.get} - Session retrieval method that uses namespaced keys * * @since 1.0.0 * @public * * @remarks * **Important Considerations:** * - The namespace affects ALL subsequent session operations * - Changing the namespace will effectively "switch" to a different storage context * - Keys stored with one namespace won't be accessible with a different namespace * - The namespace is stored globally and persists until explicitly changed * - An empty namespace means no prefixing is applied to keys * * **Best Practices:** * - Set the namespace early in your application lifecycle * - Use consistent naming conventions (lowercase, hyphens for separation) * - Consider including version numbers for schema evolution * - Avoid changing namespaces frequently during application runtime * - Document your namespace strategy for team members * * **Thread Safety:** * - Reading the namespace is thread-safe * - The value is cached until explicitly changed * - Multiple reads return the same value consistently */ static get keyNamespace(): string; /** * Sets the namespace prefix that will be prepended to all session storage keys. * * This setter allows you to configure a global namespace prefix that will be automatically * applied to all session storage keys when they are sanitized. Setting a namespace is crucial * for applications that need to maintain separate storage contexts, avoid key collisions, * or implement multi-tenant architectures where different users or application instances * should have isolated storage spaces. * * ### Namespace Validation: * The setter validates the input using {@link isNonNullString} to ensure only valid, * non-empty strings are accepted. Invalid inputs (null, undefined, empty strings) are * silently ignored, preserving the current namespace value. * * ### Impact on Storage Operations: * Once a namespace is set, ALL subsequent session storage operations will use the * namespaced keys. This includes {@link Session.get}, {@link Session.set}, * {@link Session.remove}, and all other storage methods. * * @param prefix - The namespace prefix to use for all storage keys * * @example * ```typescript * // Basic namespace setup * Manager.keyNamespace = "myapp"; * * // Now all storage operations use the namespace * Session.set('user', { id: 1, name: 'John' }); * // Stored with key: "myapp-user" * * const user = Session.get('user'); * // Retrieves using key: "myapp-user" * ``` * * @example * ```typescript * // Multi-environment configuration * const environment = process.env.NODE_ENV; * const version = process.env.APP_VERSION; * * // Set environment-specific namespace * Manager.keyNamespace = `${environment}-${version}`; * * // Examples: * // Development: "development-1.0.0" * // Production: "production-2.1.5" * // Testing: "testing-1.2.0-beta" * * Session.set('config', appConfig); * // Stored as "production-2.1.5-config" * ``` * * @example * ```typescript * // User-specific namespacing for multi-tenant apps * class UserSessionManager { * static loginUser(userId: string, organizationId: string) { * // Create unique namespace for this user context * Manager.keyNamespace = `org-${organizationId}-user-${userId}`; * * // All subsequent storage is user-specific * Session.set('preferences', userPreferences); * Session.set('cache', userData); * } * * static logoutUser() { * // Clear user-specific data * Session.removeAll(); * * // Reset to global namespace * Manager.keyNamespace = "global"; * } * } * * // Usage * UserSessionManager.loginUser('john123', 'acme-corp'); * // Namespace: "org-acme-corp-user-john123" * * Session.set('lastAction', 'document_created'); * // Stored as: "org-acme-corp-user-john123-lastAction" * ``` * * @example * ```typescript * // Feature-based namespacing * class FeatureFlags { * private static originalNamespace: string; * * static enableFeature(featureName: string) { * // Save current namespace * this.originalNamespace = Manager.keyNamespace; * * // Switch to feature-specific namespace * Manager.keyNamespace = `feature-${featureName}`; * * return { * store: (key: string, value: any) => Session.set(key, value), * retrieve: (key: string) => Session.get(key), * cleanup: () => { * Session.removeAll(); * Manager.keyNamespace = this.originalNamespace; * } * }; * } * } * * // Usage * const betaFeature = FeatureFlags.enableFeature('beta-dashboard'); * betaFeature.store('userProgress', progressData); * // Stored as: "feature-beta-dashboard-userProgress" * * betaFeature.cleanup(); // Removes all feature data and restores namespace * ``` * * @example * ```typescript * // Namespace migration for application updates * class NamespaceMigration { * static migrateFrom(oldNamespace: string, newNamespace: string) { * // Step 1: Read all data from old namespace * Manager.keyNamespace = oldNamespace; * const oldData = this.getAllStorageData(); * * // Step 2: Switch to new namespace and write data * Manager.keyNamespace = newNamespace; * Object.entries(oldData).forEach(([key, value]) => { * Session.set(key, value); * }); * * // Step 3: Clean up old namespace * Manager.keyNamespace = oldNamespace; * Session.removeAll(); * * // Step 4: Set new namespace as active * Manager.keyNamespace = newNamespace; * } * * private static getAllStorageData(): Record<string, any> { * // Implementation would depend on storage backend * // This is a simplified example * return {}; * } * } * * // Usage during app update * NamespaceMigration.migrateFrom('v1.0', 'v2.0'); * ``` * * @example * ```typescript * // Namespace validation and sanitization * function setSecureNamespace(rawNamespace: string): boolean { * // Sanitize the namespace * const sanitized = rawNamespace * .toLowerCase() * .replace(/[^a-z0-9-]/g, '-') * .replace(/-+/g, '-') * .replace(/^-|-$/g, ''); * * if (sanitized.length < 3) { * console.warn('Namespace too short, using default'); * Manager.keyNamespace = 'default'; * return false; * } * * Manager.keyNamespace = sanitized; * return true; * } * * // Usage * setSecureNamespace('My App!! v2.0'); // Sets to "my-app-v2-0" * setSecureNamespace('x'); // Sets to "default" (too short) * ``` * * @see {@link keyNamespace} (getter) - Method to retrieve the current namespace * @see {@link sanitizeKey} - Method that applies the namespace to keys * @see {@link isNonNullString} - Validation function used for input checking * @see {@link Session} - Session utilities that use the namespaced keys * * @since 1.0.0 * @public * * @remarks * **Critical Behavior Notes:** * - Only valid, non-empty strings are accepted as namespace values * - Invalid inputs (null, undefined, empty strings) are silently ignored * - The namespace change affects ALL subsequent storage operations immediately * - Previously stored data under different namespaces becomes inaccessible * - The namespace is stored globally and persists until explicitly changed * * **Security Considerations:** * - Ensure namespace values don't contain sensitive information * - Validate namespace inputs in security-critical applications * - Consider namespace as part of your access control strategy * - Be aware that namespace switching can expose or hide data * * **Performance Impact:** * - Namespace setting is a lightweight operation * - The namespace is cached and doesn't impact storage operation performance * - Frequent namespace changes can lead to memory fragmentation in some storage backends * * **Best Practices:** * - Set the namespace once during application initialization * - Use consistent, predictable naming patterns * - Document your namespace strategy and conventions * - Consider versioning in your namespace for future migrations * - Test namespace switching thoroughly in your application */ static set keyNamespace(prefix: string); /** * Sanitizes and prepares a storage key by removing whitespace and applying the global namespace prefix. * * This method is the core key processing function that ensures all session storage keys are * properly formatted and consistently namespaced. It performs essential cleaning operations * to prevent storage errors and applies the global namespace to create isolated storage contexts. * * The sanitization process is critical for maintaining data integrity and preventing conflicts * in shared storage environments. This method is automatically called by all session storage * operations, ensuring consistent key handling throughout the application. * * ### Sanitization Process: * 1. **Validation**: Checks if the key is valid using {@link isNonNullString} * 2. **Trimming**: Removes leading and trailing whitespace * 3. **Whitespace Removal**: Removes all internal whitespace characters * 4. **Namespace Application**: Prepends the global namespace with a hyphen separator * 5. **Return**: Provides the final, storage-ready key * * ### Key Format: * - **Without namespace**: `"cleankey"` * - **With namespace**: `"namespace-cleankey"` * - **Invalid input**: `""` (empty string) * * @param key - The raw key string to sanitize (optional) * * @returns The sanitized key string ready for storage operations, or empty string if input is invalid * * @example * ```typescript * // Basic key sanitization * const clean1 = Manager.sanitizeKey("userToken"); * console.log(clean1); // "userToken" * * const clean2 = Manager.sanitizeKey("user profile"); * console.log(clean2); // "userprofile" * * const clean3 = Manager.sanitizeKey(" spaced key "); * console.log(clean3); // "spacedkey" * ``` * * @example * ```typescript * // Namespace application * Manager.keyNamespace = "myapp"; * * const key1 = Manager.sanitizeKey("settings"); * console.log(key1); // "myapp-settings" * * const key2 = Manager.sanitizeKey("user preferences"); * console.log(key2); // "myapp-userpreferences" * * const key3 = Manager.sanitizeKey("cache data"); * console.log(key3); // "myapp-cachedata" * ``` * * @example * ```typescript * // Invalid input handling * const invalid1 = Manager.sanitizeKey(""); * console.log(invalid1); // "" * * const invalid2 = Manager.sanitizeKey(null); * console.log(invalid2); // "" * * const invalid3 = Manager.sanitizeKey(undefined); * console.log(invalid3); // "" * * const invalid4 = Manager.sanitizeKey(" "); * console.log(invalid4); // "" * ``` * * @example * ```typescript * // Complex whitespace handling * const complex1 = Manager.sanitizeKey("user\tprofile\ndata"); * console.log(complex1); // "userprofiledata" * * const complex2 = Manager.sanitizeKey("multi space key"); * console.log(complex2); // "multispacekey" * * const complex3 = Manager.sanitizeKey(" \t\n mixed \r\n whitespace \t "); * console.log(complex3); // "mixedwhitespace" * ``` * * @example * ```typescript * // Usage in custom storage operations * class CustomStorage { * static setWithMetadata(key: string, value: any, metadata: object) { * const cleanKey = Manager.sanitizeKey(key); * const metaKey = Manager.sanitizeKey(`${key}_meta`); * * Session.set(cleanKey, value); * Session.set(metaKey, metadata); * } * * static getWithMetadata(key: string) { * const cleanKey = Manager.sanitizeKey(key); * const metaKey = Manager.sanitizeKey(`${key}_meta`); * * return { * value: Session.get(cleanKey), * metadata: Session.get(metaKey) * }; * } * } * * // Usage * Manager.keyNamespace = "app"; * CustomStorage.setWithMetadata("user data", userData, { created: Date.now() }); * // Stores: "app-userdata" and "app-userdata_meta" * * const result = CustomStorage.getWithMetadata("user data"); * console.log(result.value, result.metadata); * ``` * * @example * ```typescript * // Key collision prevention * class StorageKeyManager { * static generateUniqueKey(baseKey: string, identifier: string): string { * const timestamp = Date.now(); * const randomSuffix = Math.random().toString(36).substr(2, 9); * const rawKey = `${baseKey}_${identifier}_${timestamp}_${randomSuffix}`; * * return Manager.sanitizeKey(rawKey); * } * * static isKeyValid(key: string): boolean { * const sanitized = Manager.sanitizeKey(key); * return sanitized.length > 0 && sanitized === Manager.sanitizeKey(sanitized); * } * } * * // Usage * const uniqueKey = StorageKeyManager.generateUniqueKey("temp cache", "user123"); * console.log(uniqueKey); // "namespace-tempcache_user123_1642123456789_abc123def" * * console.log(StorageKeyManager.isKeyValid("valid key")); // true * console.log(StorageKeyManager.isKeyValid("")); // false * ``` * * @example * ```typescript * // Debugging key transformations * function debugKeySanitization(rawKey: string) { * console.log('=== Key Sanitization Debug ==='); * console.log('Input:', JSON.stringify(rawKey)); * console.log('Current namespace:', Manager.keyNamespace); * * const sanitized = Manager.sanitizeKey(rawKey); * console.log('Output:', JSON.stringify(sanitized)); * * const steps = { * 'Original': rawKey, * 'After trim': rawKey?.trim(), * 'After whitespace removal': rawKey?.trim().replace(/\s+/g, ""), * 'With namespace': sanitized * }; * * Object.entries(steps).forEach(([step, value]) => { * console.log(`${step}: "${value}"`); * }); * } * * // Usage * Manager.keyNamespace = "debug"; * debugKeySanitization(" test key with spaces "); * ``` * * @see {@link keyNamespace} - The namespace property applied to keys * @see {@link isNonNullString} - Validation function for key input * @see {@link Session.set} - Method that uses sanitized keys for storage * @see {@link Session.get} - Method that uses sanitized keys for retrieval * @see {@link sanitizeKey} - Standalone function wrapper for this method * * @since 1.0.0 * @public * * @remarks * **Key Behavior Notes:** * - All whitespace characters (spaces, tabs, newlines, etc.) are completely removed * - The method is case-sensitive - no case transformation is performed * - Namespace application is automatic when a namespace is set * - Empty or invalid inputs always return an empty string * - The method is idempotent - sanitizing a sanitized key produces the same result * * **Performance Characteristics:** * - Very fast operation, primarily string manipulation * - No external dependencies or async operations * - Minimal memory allocation for most inputs * - Regex operations are optimized for common whitespace patterns * * **Integration Points:** * - Called automatically by all Session storage methods * - Used by the standalone {@link sanitizeKey} function * - Can be called directly for key validation or preview * - Essential for custom storage implementations * * **Whitespace Handling:** * - **Spaces**: ` ` → removed * - **Tabs**: `\t` → removed * - **Newlines**: `\n`, `\r\n` → removed * - **Form feeds**: `\f` → removed * - **Vertical tabs**: `\v` → removed * - **Unicode whitespace**: Various Unicode space characters → removed */ static sanitizeKey(key?: string): string; } /** * Sanitizes a string for session storage. * * This function trims and removes whitespace from the key, and adds the namespace prefix if set. * \nExample * ```typescript Manager.keyNamespace = "my-prefix"; const prefixedKey = sanitizeKey("my-key"); console.log(prefixedKey); // "my-prefix-my-key" * ```` * @param {string} key - The key to sanitize. * @returns {string} The sanitized key. */ declare function sanitizeKey(key: string): string; /** * Interface for a session storage object. * * This interface defines the methods for setting, getting, and removing values from a session storage object. */ export interface ISessionStorage { /** * Sets a value in the session storage object. * * @param {string} key - The key to set the value for. * @param {any} value - The value to set. * @param {boolean} [decycle] - Optional parameter to decycle the value. * @returns {any} The set value. */ set: (key: string, value: any, decycle?: boolean) => any; /** * Gets a value from the session storage object. * * @param {string} key - The key to get the value for. * @returns {any} The value associated with the key. */ get: (key: string) => any; /** * Removes a value from the session storage object. * * @param {string} key - The key to remove the value for. * @returns {any} The removed value. */ remove: (key: string) => any; /** * Removes all values from the session storage object. * */ removeAll: () => any; } /** * Session management utilities providing comprehensive storage operations with automatic serialization and namespace support. * * The Session object serves as the primary interface for all session storage operations in the application. * It provides a clean, consistent API that abstracts away the complexity of different storage backends while * offering advanced features like automatic JSON serialization, key sanitization, namespace management, * and support for both synchronous and asynchronous storage operations. * * ### Core Features: * - **Automatic Serialization**: JSON serialization/deserialization with decycling support * - **Key Sanitization**: Automatic key cleaning and namespace prefixing * - **Storage Abstraction**: Works with any storage backend implementing ISessionStorage * - **Type Safety**: Full TypeScript support with intelligent type inference * - **Async Support**: Seamless handling of both sync and async storage operations * - **Error Resilience**: Graceful handling of storage failures and edge cases * * @namespace Session * @since 1.0.0 * @public */ export declare const Session: { get: (key: string) => any; /** * Stores a value in session storage with automatic serialization and key sanitization. * * This method is the primary interface for persisting data to session storage. It provides * intelligent handling of complex data types through JSON serialization, automatic key * sanitization with namespace support, and optional object decycling to handle circular * references safely. * * The method leverages the configured storage backend through the Manager, ensuring that * your data is stored consistently regardless of whether you're using localStorage, * sessionStorage, IndexedDB, or a custom storage implementation. * * ### Key Processing Pipeline: * 1. **Key Sanitization**: Applies {@link sanitizeKey} to clean and prefix the key * 2. **Value Processing**: Uses {@link handleSetValue} for JSON serialization * 3. **Storage Delegation**: Passes processed data to the configured storage backend * 4. **Result Return**: Returns the result from the underlying storage operation * * ### Serialization Features: * - **JSON Serialization**: Converts objects, arrays, and primitives to JSON strings * - **Circular Reference Handling**: Optional decycling prevents infinite recursion * - **Type Preservation**: Maintains data types through intelligent parsing on retrieval * - **Null/Undefined Handling**: Graceful handling of empty values * * @param key - The storage key identifier for the value * @param value - The data to store (objects, arrays, primitives, etc.) * @param decycle - Whether to remove circular references during serialization * * @returns The result of the storage operation (implementation-dependent) * * @example * ```typescript * // Basic primitive value storage * Session.set('username', 'john_doe'); * Session.set('user_id', 12345); * Session.set('is_authenticated', true); * Session.set('last_login', new Date()); * * console.log(Session.get('username')); // 'john_doe' * console.log(Session.get('user_id')); // 12345 * console.log(Session.get('is_authenticated')); // true * ``` * * @example * ```typescript * // Complex object storage with automatic serialization * const userProfile = { * id: 1001, * name: 'Alice Johnson', * email: 'alice@example.com', * preferences: { * theme: 'dark', * language: 'en', * notifications: { * email: true, * push: false, * sms: true * } * }, * roles: ['user', 'moderator'], * metadata: { * created_at: '2023-01-15T10:30:00Z', * last_updated: '2023-07-20T14:45:30Z' * } * }; * * // Store complex object - automatically serialized * Session.set('user_profile', userProfile); * * // Retrieve and use - automatically deserialized * const retrievedProfile = Session.get('user_profile'); * console.log(retrievedProfile.name); // 'Alice Johnson' * console.log(retrievedProfile.preferences.theme); // 'dark' * console.log(retrievedProfile.roles.length); // 2 * ``` * * @example * ```typescript * // Array storage and manipulation * const shoppingCart = [ * { id: 'prod1', name: 'Laptop', price: 999.99, quantity: 1 }, * { id: 'prod2', name: 'Mouse', price: 29.99, quantity: 2 }, * { id: 'prod3', name: 'Keyboard', price: 79.99, quantity: 1 } * ]; * * Session.set('shopping_cart', shoppingCart); * * // Retrieve and work with array * const cart = Session.get('shopping_cart'); * const totalItems = cart.reduce((sum, item) => sum + item.quantity, 0); * const totalPrice = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); * * console.log(`Cart has ${totalItems} items, total: $${totalPrice.toFixed(2)}`); * ``` * * @example * ```typescript * // Handling circular references with decycling * interface Node { * id: string; * name: string; * parent?: Node; * children: Node[]; * } * * const parentNode: Node = { * id: 'parent', * name: 'Parent Node', * children: [] * }; * * const childNode: Node = { * id: 'child', * name: 'Child Node', * parent: parentNode, * children: [] * }; * * parentNode.children.push(childNode); * * // Store with decycling enabled (default) to handle circular reference * Session.set('node_tree', parentNode, true); * * // Store without decycling (use with caution for circular data) * // Session.set('node_tree', parentNode, false); // May cause errors * ``` * * @example * ```typescript * // Application state management * interface AppState { * currentView: 'dashboard' | 'profile' | 'settings'; * user: { * id: number; * name: string; * permissions: string[]; * }; * uiState: { * sidebarCollapsed: boolean; * activeTab: string; * filters: Record<string, any>; * }; * } * * const appState: AppState = { * currentView: 'dashboard', * user: { * id: 123, * name: 'John Doe', * permissions: ['read', 'write', 'admin'] * }, * uiState: { * sidebarCollapsed: false, * activeTab: 'overview', * filters: { * dateRange: '30days', * category: 'all', * status: 'active' * } * } * }; * * // Persist entire application state * Session.set('app_state', appState); * * // Later, restore application state * const restoredState = Session.get('app_state') as AppState; * if (restoredState) { * console.log(`Welcome back, ${restoredState.user.name}!`); * console.log(`Current view: ${restoredState.currentView}`); * } * ``` * * @example * ```typescript * // Form data persistence for better UX * interface FormData { * personalInfo: { * firstName: string; * lastName: string; * email: string; * phone: string; * }; * address: { * street: string; * city: string; * state: string; * zipCode: string; * }; * preferences: { * newsletter: boolean; * notifications: boolean; * }; * } * * // Save form data as user types (draft functionality) * function saveFormDraft(formData: Partial<FormData>) { * const existingDraft = Session.get('form_draft') || {}; * const updatedDraft = { ...existingDraft, ...formData }; * Session.set('form_draft', updatedDraft); * console.log('Draft saved automatically'); * } * * // Restore form data when user returns * function restoreFormDraft(): Partial<FormData> | null { * return Session.get('form_draft'); * } * * // Clear draft after successful submission * function clearFormDraft() { * Session.remove('form_draft'); * } * ``` * * @example * ```typescript * // Configuration and settings management * interface UserSettings { * appearance: { * theme: 'light' | 'dark' | 'auto'; * fontSize: 'small' | 'medium' | 'large'; * accentColor: string; * }; * behavior: { * autoSave: boolean; * confirmDeletes: boolean; * showTooltips: boolean; * }; * privacy: { * shareUsageData: boolean; * allowCookies: boolean; * }; * } * * const defaultSettings: UserSettings = { * appearance: { * theme: 'auto', * fontSize: 'medium', * accentColor: '#007bff' * }, * behavior: { * autoSave: true, * confirmDeletes: true, * showTooltips: true * }, * privacy: { * shareUsageData: false, * allowCookies: true * } * }; * * // Initialize or update settings * function updateUserSettings(newSettings: Partial<UserSettings>) { * const currentSettings = Session.get('user_settings') || defaultSettings; * const mergedSettings = deepMerge(currentSettings, newSettings); * Session.set('user_settings', mergedSettings); * return mergedSettings; * } * * // Helper function for deep merging * function deepMerge(target: any, source: any): any { * const result = { ...target }; * for (const key in source) { * if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { * result[key] = deepMerge(target[key] || {}, source[key]); * } else { * result[key] = source[key]; * } * } * return result; * } * ``` * * @example * ```typescript * // Cache management for API responses * interface CacheEntry<T> { * data: T; * timestamp: number; * ttl: number; // time to live in milliseconds * } * * function setCachedData<T>(key: string, data: T, ttlMinutes: number = 30) { * const cacheEntry: CacheEntry<T> = { * data, * timestamp: Date.now(), * ttl: ttlMinutes * 60 * 1000 * }; * * Session.set(`cache_${key}`, cacheEntry); * console.log(`Cached data for ${key} (TTL: ${ttlMinutes} minutes)`); * } * * function getCachedData<T>(key: string): T | null { * const cacheEntry = Session.get(`cache_${key}`) as CacheEntry<T>; * * if (!cacheEntry) return null; * * const isExpired = Date.now() - cacheEntry.timestamp > cacheEntry.ttl; * if (isExpired) { * Session.remove(`cache_${key}`); * return null; * } * * return cacheEntry.data; * } * * // Usage example * interface User { * id: number; * name: string; * email: string; * } * * async function fetchUser(id: number): Promise<User> { * // Check cache first * const cached = getCachedData<User>(`user_${id}`); * if (cached) { * console.log('Returning cached user data'); * return cached; * } * * // Fetch from API * const response = await fetch(`/api/users/${id}`); * const user: User = await response.json(); * * // Cache for 15 minutes * setCachedData(`user_${id}`, user, 15); * * return user; * } * ``` * * @example * ```typescript * // Advanced usage with namespacing * // Set namespace for application context * Session.Manager.keyNamespace = 'myapp'; * * // All keys will be automatically prefixed * Session.set('user_data', { id: 1, name: 'John' }); * // Actual key stored: 'myapp-user-data' * * Session.set('app settings', { theme: 'dark' }); * // Actual key stored: 'myapp-app-settings' (spaces replaced with hyphens) * * // Switch namespace for different context * Session.Manager.keyNamespace = 'admin'; * Session.set('permissions', ['read', 'write', 'delete']); * // Actual key stored: 'admin-permissions' * * // Clear namespace * Session.Manager.keyNamespace = ''; * Session.set('global_config', { version: '1.0.0' }); * // Actual key stored: 'global-config' (no prefix) * ``` * * @see {@link get} - Retrieve values from session storage * @see {@link remove} - Remove specific values from storage * @see {@link removeAll} - Clear all session storage * @see {@link sanitizeKey} - Key sanitization and namespace handling * @see {@link handleSetValue} - Value serialization with decycling support * @see {@link Manager} - Global session manager configuration * @see {@link ISessionStorage} - Storage backend interface * * @since 1.0.0 * @public * * @remarks * **Important Implementation Details:** * - Keys are automatically sanitized and may be prefixed with namespace * - Values are JSON-serialized unless they're already strings * - Circular references are handled when decycle is true (default) * - Storage operations depend on the configured storage backend * - Return value format depends on the underlying storage implementation * * **Performance Considerations:** * - **Serialization Overhead**: Large objects take more time to serialize * - **Storage Limits**: Be aware of storage quotas (localStorage ~5-10MB) * - **Key Length**: Very long keys may impact performance * - **Decycling Cost**: Circular reference detection adds processing time * * **Error Handling:** * - Invalid JSON serialization may throw errors * - Storage quota exceeded errors are passed through * - Null/undefined Manager.storage results in no operation * - Circular references without decycling may cause infinite recursion * * **Browser Compatibility:** * - Works with any storage backend implementing ISessionStorage * - JSON serialization uses native JSON.stringify/parse * - No dependencies on specific browser APIs * - Graceful degradation when storage is unavailable */ set: (key: string, value: any, decycle?: boolean) => any; remove: (key: string) => any; handleGetValue: any; sanitizeKey: typeof sanitizeKey; handleSetValue: (value: any, decycle?: boolean) => any; isValidStorage: (storage?: ISessionStorage) => boolean; Manager: typeof Manager; removeAll: () => any; }; /** * Class decorator that attaches a custom session storage implementation to the global session Manager. * * This decorator provides a clean and declarative way to register custom storage implementations * that will be used throughout the application for session management. When applied to a class, * it automatically instantiates the class and registers it as the global storage provider. * * The decorator implements the Dependency Injection pattern for storage providers, allowing * applications to easily swap between different storage implementations (localStorage, * sessionStorage, IndexedDB, in-memory storage, etc.) without changing the core session logic. * * ### Features: * - **Automatic Registration**: Instantiates and registers the storage class automatically * - **Validation**: Ensures the storage implementation meets the required interface * - **Error Handling**: Gracefully handles instantiation failures * - **Type Safety**: Enforces ISessionStorage interface compliance at compile time * - **Global Scope**: Makes the storage available throughout the entire application * * ### Storage Requirements: * The decorated class must implement the {@link ISessionStorage} interface with these methods: * - `get(key: string): any` - Retrieve a value by key * - `set(key: string, value: any, decycle?: boolean): any` - Store a value with optional decycling * - `remove(key: string): any` - Remove a value by key * - `removeAll(): any` - Clear all stored values * * @decorator * @param target - The class constructor that implements {@link ISessionStorage} * * @returns A class decorator function that registers the storage implementation * * @throws {Error} Logs error to console if storage instantiation fails, but doesn't throw * * @example * ```typescript * // Basic localStorage implementation * @AttachSessionStorage() * class LocalStorageProvider implements ISessionStorage { * get(key: string): any { * return localStorage.getItem(key); * } * * set(key: string, value: any): any { * localStorage.setItem(key, String(value)); * return value; * } * * remove(key: string): any { * const value = this.get(key); * localStorage.removeItem(key); * return value; * } * * removeAll(): any { * localStorage.clear(); * } * } * * // Now Session.get(), Session.set(), etc. will use localStorage * Session.set('user', { id: 1, name: 'John' }); * const user = Session.get('user'); // Retrieves from localStorage * ``` * * @example * ```typescript * // Custom encrypted storage implementation * @AttachSessionStorage() * class EncryptedStorageProvider implements ISessionStorage { * private encrypt(value: string): string { * // Your encryption logic here * return btoa(value); // Simple base64 for demo * } * * private decrypt(value: string): string { * // Your decryption logic here * return atob(value); // Simple base64 decode for demo * } * * get(key: string): any { * const encrypted = localStorage.getItem(key); * return encrypted ? this.decrypt(encrypted) : null; * } * * set(key: string, value: any): any { * const encrypted = this.encrypt(String(value