diff --git a/docs/js-client-sdk.iclientconfig.md b/docs/js-client-sdk.iclientconfig.md
index 9fa2f9e0..6a235d54 100644
--- a/docs/js-client-sdk.iclientconfig.md
+++ b/docs/js-client-sdk.iclientconfig.md
@@ -206,6 +206,25 @@ boolean
_(Optional)_ Whether initialization will be considered successfully complete if expired cache values are loaded. If false, initialization will always wait for a fetch if cached values are expired. (default: false)
+
+
|
+
+[useIndexedDB?](./js-client-sdk.iclientconfig.useindexeddb.md)
+
+
+ |
+
+
+ |
+
+boolean
+
+
+ |
+
+_(Optional)_ Use IndexedDB for storing flag configurations and assignment cache instead of localStorage. IndexedDB provides significantly larger storage capacity (gigabytes, browser-dependent) compared to localStorage (\~5-10MB). Data is stored as native JavaScript objects using IndexedDB's structured clone algorithm for efficient storage and retrieval. (default: false)
+
+
|
diff --git a/docs/js-client-sdk.iclientconfig.useindexeddb.md b/docs/js-client-sdk.iclientconfig.useindexeddb.md
new file mode 100644
index 00000000..c9e4ee10
--- /dev/null
+++ b/docs/js-client-sdk.iclientconfig.useindexeddb.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@eppo/js-client-sdk](./js-client-sdk.md) > [IClientConfig](./js-client-sdk.iclientconfig.md) > [useIndexedDB](./js-client-sdk.iclientconfig.useindexeddb.md)
+
+## IClientConfig.useIndexedDB property
+
+Use IndexedDB for storing flag configurations and assignment cache instead of localStorage. IndexedDB provides significantly larger storage capacity (gigabytes, browser-dependent) compared to localStorage (\~5-10MB). Data is stored as native JavaScript objects using IndexedDB's structured clone algorithm for efficient storage and retrieval. (default: false)
+
+**Signature:**
+
+```typescript
+useIndexedDB?: boolean;
+```
diff --git a/docs/js-client-sdk.iprecomputedclientconfig.md b/docs/js-client-sdk.iprecomputedclientconfig.md
index c153fd25..3b269c9c 100644
--- a/docs/js-client-sdk.iprecomputedclientconfig.md
+++ b/docs/js-client-sdk.iprecomputedclientconfig.md
@@ -90,6 +90,25 @@ IPrecompute
+ |
+|
+
+[useIndexedDB?](./js-client-sdk.iprecomputedclientconfig.useindexeddb.md)
+
+
+ |
+
+
+ |
+
+boolean
+
+
+ |
+
+_(Optional)_ Use IndexedDB for storing flag configurations and assignment cache instead of localStorage. IndexedDB provides significantly larger storage capacity (gigabytes, browser-dependent) compared to localStorage (\~5-10MB). Data is stored as native JavaScript objects using IndexedDB's structured clone algorithm for efficient storage and retrieval. (default: false)
+
+
|
diff --git a/docs/js-client-sdk.iprecomputedclientconfig.useindexeddb.md b/docs/js-client-sdk.iprecomputedclientconfig.useindexeddb.md
new file mode 100644
index 00000000..e944886f
--- /dev/null
+++ b/docs/js-client-sdk.iprecomputedclientconfig.useindexeddb.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@eppo/js-client-sdk](./js-client-sdk.md) > [IPrecomputedClientConfig](./js-client-sdk.iprecomputedclientconfig.md) > [useIndexedDB](./js-client-sdk.iprecomputedclientconfig.useindexeddb.md)
+
+## IPrecomputedClientConfig.useIndexedDB property
+
+Use IndexedDB for storing flag configurations and assignment cache instead of localStorage. IndexedDB provides significantly larger storage capacity (gigabytes, browser-dependent) compared to localStorage (\~5-10MB). Data is stored as native JavaScript objects using IndexedDB's structured clone algorithm for efficient storage and retrieval. (default: false)
+
+**Signature:**
+
+```typescript
+useIndexedDB?: boolean;
+```
diff --git a/js-client-sdk.api.md b/js-client-sdk.api.md
index 4ec328b8..88e43298 100644
--- a/js-client-sdk.api.md
+++ b/js-client-sdk.api.md
@@ -163,6 +163,7 @@ export interface IClientConfig extends IBaseRequestConfig {
// Warning: (ae-forgotten-export) The symbol "ServingStoreUpdateStrategy" needs to be exported by the entry point index.d.ts
updateOnFetch?: ServingStoreUpdateStrategy;
useExpiredCache?: boolean;
+ useIndexedDB?: boolean;
}
// @public
@@ -198,6 +199,7 @@ export interface IPrecomputedClientConfig extends IBaseRequestConfig {
//
// (undocumented)
precompute: IPrecompute;
+ useIndexedDB?: boolean;
}
// @public
diff --git a/src/cache/assignment-cache-factory.ts b/src/cache/assignment-cache-factory.ts
index c23943a3..1fe9339a 100644
--- a/src/cache/assignment-cache-factory.ts
+++ b/src/cache/assignment-cache-factory.ts
@@ -1,18 +1,21 @@
import { AssignmentCache } from '@eppo/js-client-sdk-common';
-import { hasWindowLocalStorage } from '../configuration-factory';
+import { hasIndexedDB, hasWindowLocalStorage } from '../configuration-factory';
import ChromeStorageAssignmentCache from './chrome-storage-assignment-cache';
import HybridAssignmentCache from './hybrid-assignment-cache';
+import { IndexedDBAssignmentCache } from './indexed-db-assignment-cache';
import { LocalStorageAssignmentCache } from './local-storage-assignment-cache';
import SimpleAssignmentCache from './simple-assignment-cache';
export function assignmentCacheFactory({
forceMemoryOnly = false,
+ useIndexedDB = false,
chromeStorage,
storageKeySuffix,
}: {
forceMemoryOnly?: boolean;
+ useIndexedDB?: boolean;
storageKeySuffix: string;
chromeStorage?: chrome.storage.StorageArea;
}): AssignmentCache {
@@ -22,15 +25,18 @@ export function assignmentCacheFactory({
return simpleCache;
}
+ // Priority order: Chrome storage > IndexedDB (if opted in) > localStorage > memory-only
if (chromeStorage) {
const chromeStorageCache = new ChromeStorageAssignmentCache(chromeStorage);
return new HybridAssignmentCache(simpleCache, chromeStorageCache);
+ } else if (useIndexedDB && hasIndexedDB()) {
+ // IndexedDB is available and user has opted in
+ const indexedDBCache = new IndexedDBAssignmentCache(storageKeySuffix);
+ return new HybridAssignmentCache(simpleCache, indexedDBCache);
+ } else if (hasWindowLocalStorage()) {
+ const localStorageCache = new LocalStorageAssignmentCache(storageKeySuffix);
+ return new HybridAssignmentCache(simpleCache, localStorageCache);
} else {
- if (hasWindowLocalStorage()) {
- const localStorageCache = new LocalStorageAssignmentCache(storageKeySuffix);
- return new HybridAssignmentCache(simpleCache, localStorageCache);
- } else {
- return simpleCache;
- }
+ return simpleCache;
}
}
diff --git a/src/cache/indexed-db-assignment-cache.ts b/src/cache/indexed-db-assignment-cache.ts
new file mode 100644
index 00000000..7063bf61
--- /dev/null
+++ b/src/cache/indexed-db-assignment-cache.ts
@@ -0,0 +1,39 @@
+import { AbstractAssignmentCache } from '@eppo/js-client-sdk-common';
+
+import { BulkReadAssignmentCache, BulkWriteAssignmentCache } from './hybrid-assignment-cache';
+import { IndexedDBAssignmentShim } from './indexed-db-assignment-shim';
+
+/**
+ * IndexedDB-backed assignment cache.
+ *
+ * Provides persistent storage for assignment results with larger capacity than localStorage.
+ * Stores assignments as a blob in IndexedDB for efficient storage and retrieval.
+ */
+export class IndexedDBAssignmentCache
+ extends AbstractAssignmentCache
+ implements BulkReadAssignmentCache, BulkWriteAssignmentCache
+{
+ constructor(storageKeySuffix: string) {
+ super(new IndexedDBAssignmentShim(storageKeySuffix));
+ }
+
+ setEntries(entries: [string, string][]): void {
+ entries.forEach(([key, value]) => {
+ if (key && value) {
+ this.delegate.set(key, value);
+ }
+ });
+ }
+
+ async getEntries(): Promise<[string, string][]> {
+ return Array.from(this.entries());
+ }
+
+ /**
+ * Initialize the cache by loading from IndexedDB.
+ * This should be called during SDK initialization.
+ */
+ async init(): Promise {
+ await this.delegate.init();
+ }
+}
diff --git a/src/cache/indexed-db-assignment-shim.ts b/src/cache/indexed-db-assignment-shim.ts
new file mode 100644
index 00000000..40b739ab
--- /dev/null
+++ b/src/cache/indexed-db-assignment-shim.ts
@@ -0,0 +1,223 @@
+/**
+ * IndexedDB-backed Map implementation for assignment cache.
+ *
+ * Stores the assignment cache as a native JavaScript array in IndexedDB using the
+ * structured clone algorithm. This provides significantly larger storage capacity
+ * (gigabytes, browser-dependent, typically 10GB+) compared to localStorage's ~5-10MB limit.
+ *
+ * Chrome compresses IndexedDB values natively at the storage layer. The cache is loaded
+ * into memory at initialization for fast synchronous access during flag evaluations.
+ */
+export class IndexedDBAssignmentShim implements Map {
+ private static readonly DB_NAME = 'eppo-sdk-storage';
+ private static readonly DB_VERSION = 1;
+ private static readonly ASSIGNMENTS_STORE = 'assignments';
+
+ private readonly assignmentKey: string;
+ private dbPromise: Promise | null = null;
+ private cache: Map | null = null;
+
+ public constructor(storageKeySuffix: string) {
+ const keySuffix = storageKeySuffix ? `-${storageKeySuffix}` : '';
+ this.assignmentKey = `eppo-assignment${keySuffix}`;
+ }
+
+ /**
+ * Initialize the IndexedDB database and create object stores if needed.
+ * This is called lazily on first access.
+ */
+ private initDB(): Promise {
+ if (this.dbPromise) {
+ return this.dbPromise;
+ }
+
+ this.dbPromise = new Promise((resolve, reject) => {
+ const request = indexedDB.open(
+ IndexedDBAssignmentShim.DB_NAME,
+ IndexedDBAssignmentShim.DB_VERSION,
+ );
+
+ request.onerror = () => {
+ reject(new Error(`Failed to open IndexedDB: ${request.error?.message}`));
+ };
+
+ request.onsuccess = () => {
+ resolve(request.result);
+ };
+
+ request.onupgradeneeded = (event) => {
+ const db = (event.target as IDBOpenDBRequest).result;
+
+ // Create assignments object store if it doesn't exist
+ if (!db.objectStoreNames.contains(IndexedDBAssignmentShim.ASSIGNMENTS_STORE)) {
+ db.createObjectStore(IndexedDBAssignmentShim.ASSIGNMENTS_STORE);
+ }
+
+ // Note: The config storage engine may have already created 'contents' and 'meta' stores
+ // We only create 'assignments' here if needed
+ if (!db.objectStoreNames.contains('contents')) {
+ db.createObjectStore('contents');
+ }
+ if (!db.objectStoreNames.contains('meta')) {
+ db.createObjectStore('meta');
+ }
+ };
+ });
+
+ return this.dbPromise;
+ }
+
+ /**
+ * Load the cache from IndexedDB.
+ * Returns a cached Map if already loaded, otherwise fetches from IndexedDB.
+ */
+ private async getCache(): Promise