Skip to content

Latest commit

 

History

History
508 lines (375 loc) · 12.2 KB

File metadata and controls

508 lines (375 loc) · 12.2 KB

Storage Plugin

Universal storage abstraction for browser-based SDKs with support for multiple backends, TTL/expiration, namespace isolation, and automatic fallback.

Features

  • Multiple Backends: localStorage, sessionStorage, cookies, and in-memory
  • TTL/Expiration: Automatic expiration of stored values
  • Namespace Isolation: Prevent key collisions between apps
  • JSON Serialization: Store any JSON-serializable value
  • Automatic Fallback: Falls back to memory storage when other backends are unavailable
  • Type-Safe: Full TypeScript support
  • Event-Driven: Emits events for all storage operations

Installation

pnpm add @prosdevlab/sdk-kit @prosdevlab/sdk-kit-plugins

Basic Usage

import { SDK } from '@prosdevlab/sdk-kit';
import { storagePlugin } from '@prosdevlab/sdk-kit-plugins/storage';

const sdk = new SDK({
  storage: {
    backend: 'localStorage',
    namespace: 'myapp'
  }
});

sdk.use(storagePlugin);

// Set a value
sdk.storage.set('user', { id: 123, name: 'Alice' });

// Get a value
const user = sdk.storage.get('user');
console.log(user); // { id: 123, name: 'Alice' }

// Remove a value
sdk.storage.remove('user');

// Clear all values
sdk.storage.clear();

Configuration

const sdk = new SDK({
  storage: {
    // Backend to use (default: 'localStorage')
    backend: 'localStorage' | 'sessionStorage' | 'cookie' | 'memory',
    
    // Namespace for key isolation (default: '')
    namespace: 'myapp',
    
    // Default TTL in seconds (default: undefined - no expiration)
    ttl: 3600,
    
    // Cookie-specific options
    domain: '.example.com',
    path: '/',
    secure: true,
    sameSite: 'lax'
  }
});

API Reference

storage.set<T>(key: string, value: T, options?: StorageOptions): void

Store a value in storage.

// Simple value
sdk.storage.set('name', 'Alice');

// Object
sdk.storage.set('user', { id: 123, name: 'Alice' });

// With TTL (expires in 1 hour)
sdk.storage.set('session', { token: 'abc123' }, { ttl: 3600 });

// With custom backend
sdk.storage.set('temp', 'value', { backend: 'sessionStorage' });

// With custom namespace
sdk.storage.set('key', 'value', { namespace: 'app1' });

Options:

  • backend?: 'localStorage' | 'sessionStorage' | 'cookie' | 'memory' - Override default backend
  • namespace?: string - Override default namespace
  • ttl?: number - Time to live in seconds
  • domain?: string - Cookie domain (cookie backend only)
  • path?: string - Cookie path (cookie backend only)
  • secure?: boolean - Secure flag (cookie backend only)
  • sameSite?: 'strict' | 'lax' | 'none' - SameSite attribute (cookie backend only)

storage.get<T>(key: string, options?: StorageOptions): T | null

Retrieve a value from storage. Returns null if the key doesn't exist or the value has expired.

const user = sdk.storage.get<User>('user');

if (user) {
  console.log(user.name);
}

// From custom backend
const temp = sdk.storage.get('temp', { backend: 'sessionStorage' });

// From custom namespace
const value = sdk.storage.get('key', { namespace: 'app1' });

storage.remove(key: string, options?: StorageOptions): void

Remove a value from storage.

sdk.storage.remove('user');

// From custom backend
sdk.storage.remove('temp', { backend: 'sessionStorage' });

// From custom namespace
sdk.storage.remove('key', { namespace: 'app1' });

storage.clear(options?: StorageOptions): void

Clear all values from storage.

⚠️ Warning: This clears ALL values in the backend, not just namespaced ones. Use with caution!

sdk.storage.clear();

// Clear specific backend
sdk.storage.clear({ backend: 'sessionStorage' });

storage.isSupported(backend?: string): boolean

Check if a storage backend is supported in the current environment.

if (sdk.storage.isSupported('localStorage')) {
  console.log('localStorage is available');
}

if (!sdk.storage.isSupported('cookie')) {
  console.log('Cookies are disabled');
}

Backends

localStorage

Persistent storage that survives page refreshes and browser restarts. Data is stored per origin.

sdk.storage.set('key', 'value', { backend: 'localStorage' });

Characteristics:

  • Persistent across sessions
  • ~5-10MB storage limit
  • Synchronous API
  • Automatically falls back to memory if unavailable or quota exceeded

sessionStorage

Session-scoped storage that persists only for the duration of the page session. Data is cleared when the tab/window is closed.

sdk.storage.set('key', 'value', { backend: 'sessionStorage' });

Characteristics:

  • Cleared when tab/window closes
  • ~5-10MB storage limit
  • Synchronous API
  • Automatically falls back to memory if unavailable

cookie

Stores data in browser cookies. Useful for server-side access or cross-subdomain sharing.

sdk.storage.set('key', 'value', {
  backend: 'cookie',
  domain: '.example.com',
  path: '/',
  secure: true,
  sameSite: 'lax',
  ttl: 3600
});

Characteristics:

  • Sent with every HTTP request
  • ~4KB size limit per cookie
  • Can be shared across subdomains
  • Supports expiration via TTL
  • Automatically falls back to memory if cookies are disabled

Cookie Options:

  • domain - Cookie domain (e.g., .example.com for all subdomains)
  • path - Cookie path (default: /)
  • secure - Only send over HTTPS (default: false)
  • sameSite - CSRF protection ('strict', 'lax', or 'none')
  • ttl - Expiration time in seconds

memory

In-memory storage that exists only for the lifetime of the SDK instance. Data is lost on page refresh.

sdk.storage.set('key', 'value', { backend: 'memory' });

Characteristics:

  • No persistence
  • No size limits (limited by available memory)
  • Always available (used as fallback)
  • Cleared when SDK is destroyed

TTL / Expiration

Values can be set with a time-to-live (TTL) in seconds. Expired values are automatically removed when accessed.

// Expires in 1 hour
sdk.storage.set('session', { token: 'abc123' }, { ttl: 3600 });

// Set default TTL in config
const sdk = new SDK({
  storage: {
    ttl: 3600 // All values expire in 1 hour by default
  }
});

// Override default TTL
sdk.storage.set('permanent', 'value', { ttl: undefined }); // No expiration

How it works:

  1. When a value is stored with TTL, an expiration timestamp is calculated
  2. When the value is retrieved, the timestamp is checked
  3. If expired, the value is removed and null is returned
  4. A storage:expired event is emitted

Namespace Isolation

Namespaces prevent key collisions when multiple apps or SDK instances share the same storage backend.

// Set default namespace in config
const sdk1 = new SDK({ storage: { namespace: 'app1' } });
const sdk2 = new SDK({ storage: { namespace: 'app2' } });

sdk1.use(storagePlugin);
sdk2.use(storagePlugin);

sdk1.storage.set('user', 'Alice');
sdk2.storage.set('user', 'Bob');

console.log(sdk1.storage.get('user')); // 'Alice'
console.log(sdk2.storage.get('user')); // 'Bob'

// Or use per-operation namespace
sdk.storage.set('key', 'value1', { namespace: 'app1' });
sdk.storage.set('key', 'value2', { namespace: 'app2' });

How it works:

  • Keys are prefixed with the namespace: namespace:key
  • Empty namespace (default) means no prefix

Logging

The storage plugin integrates with the logging plugin for operational visibility.

Setup

Load the logging plugin before the storage plugin:

import { SDK } from '@prosdevlab/sdk-kit';
import { loggingPlugin, storagePlugin } from '@prosdevlab/sdk-kit-plugins';

const sdk = new SDK({
  logging: { level: 'debug' }
});

sdk.use(loggingPlugin).use(storagePlugin);
await sdk.init();

What Gets Logged

Operation Level Data Logged
set() debug key, backend, ttl
get() hit trace key, backend
get() miss trace key, backend
remove() debug key, backend
clear() info backend, namespace
Backend init debug backend
Backend fallback warn requested backend, fallback backend
Parse error warn key, backend, error

Privacy Guarantees

The storage plugin NEVER logs stored values - only metadata like keys, backend types, and configuration.

// Safe: Only logs key and metadata
sdk.storage.set('credentials', { password: 'secret123' });
// Logs: { key: 'credentials', backend: 'localStorage', ttl: null }

// Safe: Only logs key
sdk.storage.get('credentials');
// Logs: { key: 'credentials', backend: 'localStorage' }

Example: Log Aggregation

sdk.logging.onLog((entry) => {
  if (entry.namespace === 'storage') {
    // Send storage operations to analytics
    analytics.track('storage_operation', {
      operation: entry.message,
      backend: entry.data?.backend
    });
  }
});

Load Order

The logging plugin is optional. If not loaded, all log calls are safely ignored:

 sdk.use(loggingPlugin).use(storagePlugin); // Logging enabled
 sdk.use(storagePlugin);                     // No logging (graceful)
 sdk.use(storagePlugin).use(loggingPlugin); // No logs (load order)

Events

The storage plugin emits events for all operations:

// Value was stored
sdk.on('storage:set', (data) => {
  console.log(`Set ${data.key} in ${data.backend}`);
});

// Value was retrieved
sdk.on('storage:get', (data) => {
  console.log(`Got ${data.key} from ${data.backend}`);
});

// Value was removed
sdk.on('storage:remove', (data) => {
  console.log(`Removed ${data.key} from ${data.backend}`);
});

// Storage was cleared
sdk.on('storage:clear', (data) => {
  console.log(`Cleared ${data.backend}`);
});

// Value expired
sdk.on('storage:expired', (data) => {
  console.log(`${data.key} expired in ${data.backend}`);
});

Error Handling

The storage plugin handles errors gracefully:

  • Quota Exceeded: Falls back to memory storage
  • Storage Unavailable: Falls back to memory storage
  • Corrupted Data: Removes corrupted value and returns null
  • Unsupported Backend: Falls back to memory storage with warning
// Check if backend is supported
if (!sdk.storage.isSupported('localStorage')) {
  console.warn('localStorage not available, using memory fallback');
}

// Errors are logged but don't throw
sdk.storage.set('key', 'value'); // Never throws

TypeScript

The storage plugin is fully typed:

interface User {
  id: number;
  name: string;
  email: string;
}

// Type-safe get/set
sdk.storage.set<User>('user', { id: 123, name: 'Alice', email: 'alice@example.com' });
const user = sdk.storage.get<User>('user');

if (user) {
  console.log(user.name); // TypeScript knows user is User | null
}

Examples

Session Management

// Store session with 1 hour expiration
sdk.storage.set('session', {
  token: 'abc123',
  userId: 456,
  expiresAt: Date.now() + 3600000
}, { ttl: 3600 });

// Check if session exists
const session = sdk.storage.get('session');
if (session) {
  console.log('User is logged in');
} else {
  console.log('Session expired');
}

User Preferences

// Store preferences in localStorage (persistent)
sdk.storage.set('preferences', {
  theme: 'dark',
  language: 'en',
  notifications: true
}, { backend: 'localStorage' });

// Load preferences on init
const prefs = sdk.storage.get('preferences') || {
  theme: 'light',
  language: 'en',
  notifications: false
};

Cross-Subdomain Tracking

// Store visitor ID in cookie accessible across subdomains
sdk.storage.set('visitorId', generateId(), {
  backend: 'cookie',
  domain: '.example.com',
  ttl: 365 * 24 * 3600, // 1 year
  secure: true,
  sameSite: 'lax'
});

Multi-Tenant Apps

// Isolate data by tenant
const tenantId = 'tenant-123';

sdk.storage.set('data', { foo: 'bar' }, {
  namespace: tenantId
});

// Each tenant has isolated storage
const data = sdk.storage.get('data', { namespace: tenantId });

Browser Compatibility

  • localStorage: All modern browsers, IE 8+
  • sessionStorage: All modern browsers, IE 8+
  • Cookies: All browsers
  • Memory: All browsers (always available)

The plugin automatically detects support and falls back to memory storage when needed.

License

MIT