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/tableoptions (passed toget/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
BroadcastChannelwhen available and falls back tostorageevents. Browsers only fire cross-tabstorageevents forlocalStorage;sessionStorageis 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
- Explore Storage Adapters
- Learn about Encryption
- Read about Caching Patterns