Skip to main content

Configuration

Strata Storage provides extensive configuration options to customize behavior for your specific needs.

Configuration Overview​

import { defineStorage } from 'strata-storage';

const storage = defineStorage({
// Platform detection (auto-detected by default)
platform: 'web', // 'web' | 'ios' | 'android' | 'node'

// Default storage types in order of preference
defaultStorages: ['indexedDB', 'localStorage', 'memory'],

// Storage adapter specific configurations
adapters: {
indexedDB: {
dbName: 'MyAppDB',
version: 1
},
localStorage: {
prefix: 'myapp_'
},
cookies: {
secure: true,
sameSite: 'strict'
}
},

// Encryption settings
encryption: {
enabled: false,
password: 'your-secure-password',
algorithm: 'AES-GCM',
keyLength: 256,
keyDerivation: 'PBKDF2'
},

// Compression settings
compression: {
enabled: false,
algorithm: 'lz', // the only supported algorithm
threshold: 1024 // bytes
},

// Synchronization settings
sync: {
enabled: false,
storages: ['localStorage'],
conflictResolution: 'latest'
},

// Time-to-live settings
ttl: {
defaultTTL: 3600000, // 1 hour (ms)
cleanupInterval: 60000, // 1 minute
autoCleanup: true
}
});

Configuration Options​

Platform​

Strata automatically detects the platform, but you can override it:

import { registerCapacitorAdapters } from 'strata-storage/capacitor';

const storage = defineStorage({
platform: 'ios' // Force iOS behavior
});
await registerCapacitorAdapters(storage);

platform alone does not register native adapters. Run this in a Capacitor app on a device.

Options:

  • 'web' - Browser environment
  • 'ios' - iOS native (Capacitor)
  • 'android' - Android native (Capacitor)
  • 'node' - Node.js environment

Default Storages​

Specify storage preference order. Strata will use the first available:

import { registerCapacitorAdapters } from 'strata-storage/capacitor';

const storage = defineStorage({
defaultStorages: ['sqlite', 'preferences', 'memory']
});
await registerCapacitorAdapters(storage);

Register native adapters before reading or writing to sqlite or preferences on a device.

Web Options:

  • 'memory' - In-memory storage
  • 'localStorage' - Browser localStorage
  • 'sessionStorage' - Browser sessionStorage
  • 'indexedDB' - IndexedDB database
  • 'cookies' - HTTP cookies
  • 'cache' - Cache API

Mobile Options:

  • 'preferences' - Native preferences (UserDefaults/SharedPreferences)
  • 'sqlite' - SQLite database
  • 'secure' - Keychain (iOS) / EncryptedSharedPreferences (Android)
  • 'filesystem' - File system storage

Adapter Configuration​

IndexedDB​

adapters: {
indexedDB: {
dbName: 'MyDatabase', // Database name
version: 1 // Database version
}
}

LocalStorage/SessionStorage​

adapters: {
localStorage: {
prefix: 'app_' // Key prefix
}
}

Cookies​

adapters: {
cookies: {
secure: true, // HTTPS only
sameSite: 'lax' // 'strict' | 'lax' | 'none'
}
}

SQLite (Mobile)​

adapters: {
sqlite: {
filename: 'app.db' // Database file name
}
}

The native SQLite backend also accepts per-call database / table options (passed to get/set/etc.) to address multiple physical stores — see the SQLite adapter reference.

Encryption Configuration​

encryption: {
enabled: true, // Enable/disable encryption
password: 'secret', // Encryption password
algorithm: 'AES-GCM', // 'AES-GCM' | 'AES-CBC'
keyLength: 256, // 128 | 192 | 256
keyDerivation: 'PBKDF2', // PBKDF2
iterations: 600000, // PBKDF2 iterations (default: 600000, OWASP)
saltLength: 16 // Salt length in bytes
}

Compression Configuration​

compression: {
enabled: true, // Enable/disable compression
algorithm: 'lz', // only the bundled zero-dependency LZ codec is supported
threshold: 1024 // Min size to compress (bytes)
}

Sync Configuration​

sync: {
enabled: true, // Enable cross-tab sync
storages: ['localStorage'], // Storages to sync (cross-tab fallback is localStorage-only)
interval: 5000, // Optional periodic sync interval (ms)
conflictResolution: 'latest' // 'latest' | 'merge' | custom function
}

Cross-tab sync uses BroadcastChannel when available and falls back to storage events. Browsers only fire cross-tab storage events for localStorage; sessionStorage is tab-scoped and never propagates cross-tab.

TTL Configuration​

ttl: {
defaultTTL: 3600000, // Default TTL in ms (1 hour)
cleanupInterval: 60000, // Cleanup sweep interval (ms)
autoCleanup: true, // Auto-remove expired items
batchSize: 100, // Max items removed per cleanup cycle
slidingTTL: false, // Reset TTL on access (instance default)
onExpire: (keys) => {} // Callback invoked with expired keys
}

Environment-Specific Configuration​

Development​

const devConfig = {
encryption: { enabled: false }, // Disable for debugging
compression: { enabled: false },
sync: {
enabled: true,
interval: 0 // 0 = event-driven (no polling)
}
};

Production​

const prodConfig = {
encryption: {
enabled: true,
password: process.env.STORAGE_KEY
},
compression: {
enabled: true,
threshold: 512 // Aggressive compression
},
ttl: {
autoCleanup: true,
cleanupInterval: 300000 // 5 minutes
}
};

Storage Selection​

Strata does not use named "strategies." Instead, list the storage backends you want in defaultStorages, in order of preference — Strata uses the first one available on the current platform:

const storage = defineStorage({
// Try persistent stores first, fall back to memory as a last resort
defaultStorages: ['indexedDB', 'localStorage', 'memory']
});

:::danger defaultStorages is not a registration list It is the preference order for choosing the default adapter, and the fallback order when one is unusable. keys(), clear() and size() with no explicit storage span every registered adapter; subscribe() listens to registered adapters that support change events. Listing one storage here does not stop the others from being registered or swept.

To leave an adapter out entirely, say so:

const storage = defineStorage({
defaultStorages: ['localStorage'],
adapters: { sessionStorage: false, indexedDB: false, cookies: false, cache: false },
});

Since 2.9.0, adapters: { <name>: false } skips registration, not just initialization. (Before 2.9.0 it only skipped initialization, so the adapter was still registered and still swept — which is what made defaultStorages look like a registration list that was being ignored.) :::

Key prefix and the 3.0.0 migration​

Since 3.0.0, localStorage and sessionStorage keys are written under strata: (DEFAULT_WEB_KEY_PREFIX, exported). Existing data migrates itself on read.

const storage = defineStorage(); // keys at `strata:<key>`
const legacy = defineStorage({ keyPrefix: false }); // pre-3.0, bare keys
const custom = defineStorage({ keyPrefix: 'myapp:' }); // your own
const noMove = defineStorage({ migrateLegacyKeys: false }); // don't adopt 2.x entries

:::danger Take keyPrefix: false when something outside this library reads a physical key A pre-paint theme script that runs before any module loads, or a logger reading its own level, knows the exact key name — and a prefix changes it underneath them. Migration keeps the data reachable through this library; it cannot fix a hard-coded reader. :::

How migration works. A miss at strata:<key> falls back to the bare <key>; if the value is one of ours it is moved under the prefix and returned. It is per key, on read — never a bulk sweep, which would adopt every unprefixed envelope on the origin including keys belonging to an instance that opted out or a sibling app still on 2.x. It never adopts a non-envelope, and it never overwrites a value already at the prefixed key. keys() lists not-yet-migrated entries so enumeration stays complete, but listing never moves anything.

Unchanged: cookies (already strata_), indexedDB and cache (named stores), memory (its own Map), url (already prefixes its params). namespace is a separate mechanism — the physical key is <keyPrefix><namespace>:<key>.

Shared storage areas and key ownership​

localStorage, sessionStorage and cookies are shared with every other script on the origin, and the localStorage and sessionStorage use the strata: prefix by default since 3.0.0. Since 2.9.0 an adapter identifies its own data by shape — a key is its own only when the stored value is a StorageValue envelope — so keys(), the TTL sweep and clear() never touch data this library did not write.

Consequences worth knowing:

  • A key written to the same area by anything else is invisible to keys() by design.
  • A value we cannot read is skipped and reported at debug, not as an error: it is evidence the key belongs to somebody else, which is the ordinary case in a shared area.
  • setLogLevel('debug') (exported) names every skipped key and why.

Setting namespace or a per-adapter prefix is still worth doing for a clean keyspace. It is no longer what keeps this library off other applications' keys.

Custom Configuration Patterns​

Multi-Instance Configuration​

import { defineStorage } from 'strata-storage';
import { registerCapacitorAdapters } from 'strata-storage/capacitor';

// User data storage
const userStorage = defineStorage({
defaultStorages: ['secure', 'preferences']
});

// Cache storage
const cacheStorage = defineStorage({
defaultStorages: ['cache', 'indexedDB'],
ttl: { defaultTTL: 300000 } // 5 minutes
});

// Temporary storage
const tempStorage = defineStorage({
defaultStorages: ['memory', 'sessionStorage'],
ttl: { defaultTTL: 60000 } // 1 minute
});

// In a Capacitor app, before using userStorage on a device:
await registerCapacitorAdapters(userStorage);

If you also enable encryption, supply a password from a secure runtime source.

Optional Features​

import { defineStorage } from 'strata-storage';

const storage = defineStorage({
compression: {
enabled: true,
threshold: 1024
},
sync: {
enabled: typeof BroadcastChannel !== 'undefined'
}
});

Encryption needs a password from a secure runtime source when you enable it; see the encryption guide.

Validation​

Initialisation selects the first available registered adapter from defaultStorages. Encryption can be enabled without an instance password when each encrypted operation supplies encryptionPassword:

try {
const storage = defineStorage({ encryption: { enabled: true } });
await storage.initialize();
await storage.set('private-draft', draft, {
encryptionPassword: password,
});
} catch (error) {
// Show a setup or write failure in the application UI.
}

Next Steps​