@workglow/indexeddb 0.2.30 → 0.2.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/dist/job-queue/IndexedDbQueueStorage.d.ts +16 -11
- package/dist/job-queue/IndexedDbQueueStorage.d.ts.map +1 -1
- package/dist/job-queue/IndexedDbRateLimiterStorage.d.ts +15 -4
- package/dist/job-queue/IndexedDbRateLimiterStorage.d.ts.map +1 -1
- package/dist/job-queue/browser.js +399 -351
- package/dist/job-queue/browser.js.map +9 -6
- package/dist/job-queue/common.d.ts +3 -0
- package/dist/job-queue/common.d.ts.map +1 -1
- package/dist/job-queue/node.js +399 -351
- package/dist/job-queue/node.js.map +9 -6
- package/dist/migrations/IndexedDbMigrationRunner.d.ts +93 -0
- package/dist/migrations/IndexedDbMigrationRunner.d.ts.map +1 -0
- package/dist/migrations/indexedDbQueueMigrations.d.ts +24 -0
- package/dist/migrations/indexedDbQueueMigrations.d.ts.map +1 -0
- package/dist/migrations/indexedDbRateLimiterMigrations.d.ts +37 -0
- package/dist/migrations/indexedDbRateLimiterMigrations.d.ts.map +1 -0
- package/dist/storage/IndexedDbTable.d.ts.map +1 -1
- package/dist/storage/IndexedDbTabularMigrationApplier.d.ts +84 -0
- package/dist/storage/IndexedDbTabularMigrationApplier.d.ts.map +1 -0
- package/dist/storage/IndexedDbTabularStorage.d.ts +21 -2
- package/dist/storage/IndexedDbTabularStorage.d.ts.map +1 -1
- package/dist/storage/browser.js +472 -30
- package/dist/storage/browser.js.map +8 -5
- package/dist/storage/common.d.ts +3 -0
- package/dist/storage/common.d.ts.map +1 -1
- package/dist/storage/node.js +472 -30
- package/dist/storage/node.js.map +8 -5
- package/dist/storage/openIdb.d.ts +19 -0
- package/dist/storage/openIdb.d.ts.map +1 -0
- package/package.json +7 -7
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../src/storage/IndexedDbTable.ts", "../../src/storage/IndexedDbKvStorage.ts", "../../src/storage/IndexedDbTabularStorage.ts", "../../src/storage/IndexedDbVectorStorage.ts"],
|
|
3
|
+
"sources": ["../../src/storage/openIdb.ts", "../../src/storage/IndexedDbTable.ts", "../../src/storage/IndexedDbKvStorage.ts", "../../src/storage/IndexedDbTabularStorage.ts", "../../src/migrations/IndexedDbMigrationRunner.ts", "../../src/storage/IndexedDbTabularMigrationApplier.ts", "../../src/storage/IndexedDbVectorStorage.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\n// Production-ready IndexedDB table management with proper migration support.\n// Handles schema evolution without data loss by incrementally migrating the database\n// structure and transforming existing data as needed.\n\nimport { deepEqual } from \"@workglow/util\";\n\nexport interface ExpectedIndexDefinition {\n name: string;\n keyPath: string | string[];\n options?: IDBIndexParameters;\n}\n\nexport interface MigrationContext {\n db: IDBDatabase;\n transaction: IDBTransaction;\n oldVersion: number;\n newVersion: number;\n tableName: string;\n}\n\nexport interface DataTransformer {\n (oldData: any): any | Promise<any>;\n}\n\nexport interface MigrationOptions {\n /** Custom data transformer to apply during migration */\n dataTransformer?: DataTransformer;\n /** Whether to allow destructive operations (delete and recreate). Default: false */\n allowDestructiveMigration?: boolean;\n /** Callback for migration progress/logging */\n onMigrationProgress?: (message: string, progress?: number) => void;\n /** Callback for migration errors (non-fatal warnings) */\n onMigrationWarning?: (message: string, error?: Error) => void;\n}\n\ninterface SchemaSnapshot {\n version: number;\n primaryKey: string | string[];\n indexes: ExpectedIndexDefinition[];\n recordCount?: number;\n timestamp: number;\n}\n\nconst METADATA_STORE_NAME = \"__schema_metadata__\";\n\n/**\n * Stores metadata about the database schema for migration tracking\n */\nasync function saveSchemaMetadata(\n db: IDBDatabase,\n tableName: string,\n snapshot: SchemaSnapshot\n): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n const transaction = db.transaction(METADATA_STORE_NAME, \"readwrite\");\n const store = transaction.objectStore(METADATA_STORE_NAME);\n const request = store.put({ ...snapshot, tableName }, tableName);\n\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n transaction.onerror = () => reject(transaction.error);\n } catch (err) {\n // Metadata store might not exist in old databases, that's OK\n resolve();\n }\n });\n}\n\n/**\n * Opens an IndexedDB database with proper error handling\n */\nasync function openIndexedDbTable(\n tableName: string,\n version?: number,\n upgradeNeededCallback?: (event: IDBVersionChangeEvent) => void\n): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const openRequest = indexedDB.open(tableName, version);\n\n openRequest.onsuccess = (event) => {\n const db = (event.target as IDBOpenDBRequest).result;\n\n // Handle unexpected close\n db.onversionchange = () => {\n db.close();\n };\n\n resolve(db);\n };\n\n openRequest.onupgradeneeded = (event) => {\n if (upgradeNeededCallback) {\n upgradeNeededCallback(event);\n }\n };\n\n openRequest.onerror = () => {\n const error = openRequest.error;\n // Check if it's a VersionError - this means the database exists at a higher version\n if (error && error.name === \"VersionError\") {\n reject(\n new Error(\n `Database ${tableName} exists at a higher version. Cannot open at version ${version || \"current\"}.`\n )\n );\n } else {\n reject(error);\n }\n };\n openRequest.onblocked = () => {\n reject(\n new Error(`Database ${tableName} is blocked. Close all other tabs using this database.`)\n );\n };\n });\n}\n\n/**\n * Deletes an IndexedDB database completely\n */\nasync function deleteIndexedDbTable(tableName: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const deleteRequest = indexedDB.deleteDatabase(tableName);\n\n deleteRequest.onsuccess = () => resolve();\n deleteRequest.onerror = () => reject(deleteRequest.error);\n deleteRequest.onblocked = () => {\n reject(\n new Error(`Cannot delete database ${tableName}. Close all other tabs using this database.`)\n );\n };\n });\n}\n\n/**\n * Compares two schema definitions to determine what changes are needed\n */\ninterface SchemaDiff {\n indexesToAdd: ExpectedIndexDefinition[];\n indexesToRemove: string[];\n indexesToModify: ExpectedIndexDefinition[];\n primaryKeyChanged: boolean;\n needsObjectStoreRecreation: boolean;\n}\n\nfunction compareSchemas(\n store: IDBObjectStore,\n expectedPrimaryKey: string | string[],\n expectedIndexes: ExpectedIndexDefinition[]\n): SchemaDiff {\n const diff: SchemaDiff = {\n indexesToAdd: [],\n indexesToRemove: [],\n indexesToModify: [],\n primaryKeyChanged: false,\n needsObjectStoreRecreation: false,\n };\n\n // Check primary key\n const actualKeyPath = store.keyPath;\n const normalizedExpected = Array.isArray(expectedPrimaryKey)\n ? expectedPrimaryKey\n : expectedPrimaryKey;\n const normalizedActual = Array.isArray(actualKeyPath) ? actualKeyPath : actualKeyPath;\n\n if (!deepEqual(normalizedExpected, normalizedActual)) {\n diff.primaryKeyChanged = true;\n diff.needsObjectStoreRecreation = true;\n return diff; // If primary key changed, we need full recreation\n }\n\n // Build a map of existing indexes\n const existingIndexes = new Map<string, IDBIndex>();\n for (let i = 0; i < store.indexNames.length; i++) {\n const indexName = store.indexNames[i];\n existingIndexes.set(indexName, store.index(indexName));\n }\n\n // Check for indexes to add or modify\n for (const expectedIdx of expectedIndexes) {\n const existingIdx = existingIndexes.get(expectedIdx.name);\n\n if (!existingIdx) {\n diff.indexesToAdd.push(expectedIdx);\n } else {\n // Compare index properties\n const expectedKeyPath = Array.isArray(expectedIdx.keyPath)\n ? expectedIdx.keyPath\n : [expectedIdx.keyPath];\n const actualKeyPath = Array.isArray(existingIdx.keyPath)\n ? existingIdx.keyPath\n : [existingIdx.keyPath];\n\n const keyPathChanged = !deepEqual(expectedKeyPath, actualKeyPath);\n const uniqueChanged = existingIdx.unique !== (expectedIdx.options?.unique ?? false);\n const multiEntryChanged =\n existingIdx.multiEntry !== (expectedIdx.options?.multiEntry ?? false);\n\n if (keyPathChanged || uniqueChanged || multiEntryChanged) {\n diff.indexesToModify.push(expectedIdx);\n }\n\n existingIndexes.delete(expectedIdx.name);\n }\n }\n\n // Remaining indexes should be removed\n diff.indexesToRemove = Array.from(existingIndexes.keys());\n\n return diff;\n}\n\n/**\n * Reads all data from a store\n */\nasync function readAllData(store: IDBObjectStore): Promise<any[]> {\n return new Promise((resolve, reject) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result || []);\n request.onerror = () => reject(request.error);\n });\n}\n\n/**\n * Performs a non-destructive migration by adding/removing indexes\n */\nasync function performIncrementalMigration(\n db: IDBDatabase,\n tableName: string,\n diff: SchemaDiff,\n options: MigrationOptions = {}\n): Promise<IDBDatabase> {\n const currentVersion = db.version;\n const newVersion = currentVersion + 1;\n\n db.close();\n\n options.onMigrationProgress?.(\n `Migrating ${tableName} from version ${currentVersion} to ${newVersion}...`,\n 0\n );\n\n return openIndexedDbTable(tableName, newVersion, (event: IDBVersionChangeEvent) => {\n const transaction = (event.target as IDBOpenDBRequest).transaction!;\n const store = transaction.objectStore(tableName);\n\n // Remove outdated indexes\n for (const indexName of diff.indexesToRemove) {\n options.onMigrationProgress?.(`Removing index: ${indexName}`, 0.2);\n store.deleteIndex(indexName);\n }\n\n // Remove and recreate modified indexes\n for (const indexDef of diff.indexesToModify) {\n options.onMigrationProgress?.(`Updating index: ${indexDef.name}`, 0.4);\n if (store.indexNames.contains(indexDef.name)) {\n store.deleteIndex(indexDef.name);\n }\n store.createIndex(indexDef.name, indexDef.keyPath, indexDef.options);\n }\n\n // Add new indexes\n for (const indexDef of diff.indexesToAdd) {\n options.onMigrationProgress?.(`Adding index: ${indexDef.name}`, 0.6);\n store.createIndex(indexDef.name, indexDef.keyPath, indexDef.options);\n }\n\n options.onMigrationProgress?.(`Migration complete`, 1.0);\n });\n}\n\n/**\n * Performs a destructive migration by recreating the object store\n * This is needed when the primary key changes\n */\nasync function performDestructiveMigration(\n db: IDBDatabase,\n tableName: string,\n primaryKey: string | string[],\n expectedIndexes: ExpectedIndexDefinition[],\n options: MigrationOptions = {},\n autoIncrement: boolean = false\n): Promise<IDBDatabase> {\n if (!options.allowDestructiveMigration) {\n throw new Error(\n `Destructive migration required for ${tableName} but not allowed. ` +\n `Primary key has changed. Set allowDestructiveMigration=true to proceed with data loss, ` +\n `or provide a dataTransformer to migrate data.`\n );\n }\n\n const currentVersion = db.version;\n const newVersion = currentVersion + 1;\n\n options.onMigrationProgress?.(\n `Performing destructive migration of ${tableName}. Reading existing data...`,\n 0\n );\n\n // Read all existing data\n let existingData: any[] = [];\n try {\n const transaction = db.transaction(tableName, \"readonly\");\n const store = transaction.objectStore(tableName);\n existingData = await readAllData(store);\n options.onMigrationProgress?.(`Read ${existingData.length} records`, 0.3);\n } catch (err) {\n options.onMigrationWarning?.(\n `Failed to read existing data during migration: ${err}`,\n err as Error\n );\n }\n\n db.close();\n\n // Apply data transformer if provided\n if (options.dataTransformer && existingData.length > 0) {\n options.onMigrationProgress?.(`Transforming ${existingData.length} records...`, 0.4);\n try {\n const transformed = [];\n for (let i = 0; i < existingData.length; i++) {\n const record = existingData[i];\n const transformedRecord = await options.dataTransformer(record);\n if (transformedRecord !== undefined && transformedRecord !== null) {\n transformed.push(transformedRecord);\n }\n if (i % 100 === 0) {\n options.onMigrationProgress?.(\n `Transformed ${i}/${existingData.length} records`,\n 0.4 + (i / existingData.length) * 0.3\n );\n }\n }\n existingData = transformed;\n options.onMigrationProgress?.(`Transformation complete: ${existingData.length} records`, 0.7);\n } catch (err) {\n options.onMigrationWarning?.(\n `Data transformation failed: ${err}. Some data may be lost.`,\n err as Error\n );\n existingData = [];\n }\n }\n\n // Open with new version and recreate object store\n options.onMigrationProgress?.(`Recreating object store...`, 0.75);\n\n const newDb = await openIndexedDbTable(tableName, newVersion, (event: IDBVersionChangeEvent) => {\n const db = (event.target as IDBOpenDBRequest).result;\n\n // Delete old object store if it exists\n if (db.objectStoreNames.contains(tableName)) {\n db.deleteObjectStore(tableName);\n }\n\n // Create new object store with new schema\n const store = db.createObjectStore(tableName, { keyPath: primaryKey, autoIncrement });\n\n // Create indexes\n for (const idx of expectedIndexes) {\n store.createIndex(idx.name, idx.keyPath, idx.options);\n }\n\n // Restore data\n if (existingData.length > 0) {\n options.onMigrationProgress?.(`Restoring ${existingData.length} records...`, 0.8);\n\n for (const record of existingData) {\n try {\n store.put(record);\n } catch (err) {\n options.onMigrationWarning?.(`Failed to restore record: ${err}`, err as Error);\n }\n }\n }\n });\n\n options.onMigrationProgress?.(`Destructive migration complete`, 1.0);\n\n return newDb;\n}\n\n/**\n * Creates a new database with the specified schema\n */\nasync function createNewDatabase(\n tableName: string,\n primaryKey: string | string[],\n expectedIndexes: ExpectedIndexDefinition[],\n options: MigrationOptions = {},\n autoIncrement: boolean = false\n): Promise<IDBDatabase> {\n options.onMigrationProgress?.(`Creating new database: ${tableName}`, 0);\n\n // Delete existing database if it exists to avoid version conflicts\n try {\n await deleteIndexedDbTable(tableName);\n // Wait a bit for deletion to complete\n await new Promise((resolve) => setTimeout(resolve, 50));\n } catch (err) {\n // Ignore errors - database might not exist\n }\n\n const version = 1;\n\n const db = await openIndexedDbTable(tableName, version, (event: IDBVersionChangeEvent) => {\n const db = (event.target as IDBOpenDBRequest).result;\n\n // Create metadata store\n if (!db.objectStoreNames.contains(METADATA_STORE_NAME)) {\n db.createObjectStore(METADATA_STORE_NAME, { keyPath: \"tableName\" });\n }\n\n // Create main object store\n const store = db.createObjectStore(tableName, { keyPath: primaryKey, autoIncrement });\n\n // Create indexes\n for (const idx of expectedIndexes) {\n store.createIndex(idx.name, idx.keyPath, idx.options);\n }\n });\n\n // Save schema metadata\n const snapshot: SchemaSnapshot = {\n version: db.version,\n primaryKey,\n indexes: expectedIndexes,\n recordCount: 0,\n timestamp: Date.now(),\n };\n\n await saveSchemaMetadata(db, tableName, snapshot);\n\n options.onMigrationProgress?.(`Database created successfully`, 1.0);\n\n return db;\n}\n\n/**\n * Ensures that an IndexedDB table exists with the specified schema.\n * Performs migrations as needed without data loss when possible.\n */\nexport async function ensureIndexedDbTable(\n tableName: string,\n primaryKey: string | string[],\n expectedIndexes: ExpectedIndexDefinition[] = [],\n options: MigrationOptions = {},\n autoIncrement: boolean = false\n): Promise<IDBDatabase> {\n try {\n // Try to open existing database at current version (or create if doesn't exist)\n let db: IDBDatabase;\n let wasJustCreated = false;\n try {\n // Open without version - this will open at current version if exists, or create at version 1 if doesn't exist\n db = await openIndexedDbTable(tableName);\n\n // Check if database was just created (version 1 and no object stores)\n // This happens when indexedDB.open creates a new database without stores\n if (db.version === 1 && !db.objectStoreNames.contains(tableName)) {\n wasJustCreated = true;\n db.close();\n }\n } catch (err: any) {\n // If opening fails, database might not exist or there's a version conflict\n // Try to create it fresh\n options.onMigrationProgress?.(\n `Database ${tableName} does not exist or has version conflict, creating...`,\n 0\n );\n return await createNewDatabase(\n tableName,\n primaryKey,\n expectedIndexes,\n options,\n autoIncrement\n );\n }\n\n // If database was just created, we need to create the stores\n // We'll upgrade from version 1 to version 1 (which triggers onupgradeneeded with oldVersion=0)\n // Actually, we need to explicitly create at version 1 with stores\n if (wasJustCreated) {\n options.onMigrationProgress?.(`Creating new database: ${tableName}`, 0);\n // Delete the empty database and create it properly at version 1\n try {\n await deleteIndexedDbTable(tableName);\n await new Promise((resolve) => setTimeout(resolve, 50));\n } catch (err) {\n // Ignore errors\n }\n\n // Create at version 1 with stores\n db = await openIndexedDbTable(tableName, 1, (event: IDBVersionChangeEvent) => {\n const db = (event.target as IDBOpenDBRequest).result;\n\n // Create metadata store\n if (!db.objectStoreNames.contains(METADATA_STORE_NAME)) {\n db.createObjectStore(METADATA_STORE_NAME, { keyPath: \"tableName\" });\n }\n\n // Create main object store\n const store = db.createObjectStore(tableName, { keyPath: primaryKey, autoIncrement });\n\n // Create indexes\n for (const idx of expectedIndexes) {\n store.createIndex(idx.name, idx.keyPath, idx.options);\n }\n });\n\n // Save schema metadata\n const snapshot: SchemaSnapshot = {\n version: db.version,\n primaryKey,\n indexes: expectedIndexes,\n recordCount: 0,\n timestamp: Date.now(),\n };\n await saveSchemaMetadata(db, tableName, snapshot);\n\n options.onMigrationProgress?.(`Database created successfully`, 1.0);\n return db;\n }\n\n // Ensure metadata store exists\n if (!db.objectStoreNames.contains(METADATA_STORE_NAME)) {\n const currentVersion = db.version;\n db.close();\n\n db = await openIndexedDbTable(\n tableName,\n currentVersion + 1,\n (event: IDBVersionChangeEvent) => {\n const db = (event.target as IDBOpenDBRequest).result;\n if (!db.objectStoreNames.contains(METADATA_STORE_NAME)) {\n db.createObjectStore(METADATA_STORE_NAME, { keyPath: \"tableName\" });\n }\n }\n );\n }\n\n // Check if table structure matches expected\n if (!db.objectStoreNames.contains(tableName)) {\n // Object store doesn't exist, create it\n options.onMigrationProgress?.(`Object store ${tableName} does not exist, creating...`, 0);\n db.close();\n return await createNewDatabase(\n tableName,\n primaryKey,\n expectedIndexes,\n options,\n autoIncrement\n );\n }\n\n // Compare schemas to determine what migration is needed\n const transaction = db.transaction(tableName, \"readonly\");\n const store = transaction.objectStore(tableName);\n const diff = compareSchemas(store, primaryKey, expectedIndexes);\n\n await new Promise<void>((resolve) => {\n transaction.oncomplete = () => resolve();\n transaction.onerror = () => resolve();\n });\n\n // Determine migration strategy\n const needsMigration =\n diff.indexesToAdd.length > 0 ||\n diff.indexesToRemove.length > 0 ||\n diff.indexesToModify.length > 0 ||\n diff.needsObjectStoreRecreation;\n\n if (!needsMigration) {\n // Schema matches, no migration needed\n options.onMigrationProgress?.(`Schema for ${tableName} is up to date`, 1.0);\n\n // Update metadata anyway to keep timestamp current\n const snapshot: SchemaSnapshot = {\n version: db.version,\n primaryKey,\n indexes: expectedIndexes,\n timestamp: Date.now(),\n };\n await saveSchemaMetadata(db, tableName, snapshot);\n\n return db;\n }\n\n // Perform appropriate migration\n if (diff.needsObjectStoreRecreation) {\n options.onMigrationProgress?.(\n `Schema change requires object store recreation for ${tableName}`,\n 0\n );\n db = await performDestructiveMigration(\n db,\n tableName,\n primaryKey,\n expectedIndexes,\n options,\n autoIncrement\n );\n } else {\n options.onMigrationProgress?.(`Performing incremental migration for ${tableName}`, 0);\n db = await performIncrementalMigration(db, tableName, diff, options);\n }\n\n // Save updated metadata\n const snapshot: SchemaSnapshot = {\n version: db.version,\n primaryKey,\n indexes: expectedIndexes,\n timestamp: Date.now(),\n };\n await saveSchemaMetadata(db, tableName, snapshot);\n\n return db;\n } catch (err) {\n options.onMigrationWarning?.(`Migration failed for ${tableName}: ${err}`, err as Error);\n throw err;\n }\n}\n\n/**\n * Utility function to delete a database (for testing or cleanup)\n */\nexport async function dropIndexedDbTable(tableName: string): Promise<void> {\n return deleteIndexedDbTable(tableName);\n}\n",
|
|
5
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/**\n * Opens an IDB connection and wires `onversionchange` so a future schema\n * bump in another tab can close this connection rather than blocking the\n * upgrade indefinitely.\n *\n * Pass `version` + `onUpgradeNeeded` only when the call site is itself\n * running a schema upgrade. Most callers want the no-arg form: open at\n * the database's current version and use it for reads/writes.\n */\nexport function openIdb(\n dbName: string,\n options: {\n readonly version?: number;\n readonly onUpgradeNeeded?: (event: IDBVersionChangeEvent) => void;\n } = {}\n): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const req = indexedDB.open(dbName, options.version);\n req.onsuccess = () => {\n const db = req.result;\n db.onversionchange = () => db.close();\n resolve(db);\n };\n req.onupgradeneeded = (ev) => options.onUpgradeNeeded?.(ev);\n req.onerror = () => {\n const err = req.error;\n if (err && err.name === \"VersionError\") {\n reject(\n new Error(\n `IndexedDB ${dbName} exists at a higher version than ${options.version ?? \"current\"}`\n )\n );\n return;\n }\n reject(err);\n };\n req.onblocked = () =>\n reject(new Error(`IndexedDB ${dbName} is blocked — close other tabs using this database.`));\n });\n}\n",
|
|
6
|
+
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\n// Production-ready IndexedDB table management with proper migration support.\n// Handles schema evolution without data loss by incrementally migrating the database\n// structure and transforming existing data as needed.\n\nimport { deepEqual } from \"@workglow/util\";\nimport { openIdb } from \"./openIdb\";\n\nexport interface ExpectedIndexDefinition {\n name: string;\n keyPath: string | string[];\n options?: IDBIndexParameters;\n}\n\nexport interface MigrationContext {\n db: IDBDatabase;\n transaction: IDBTransaction;\n oldVersion: number;\n newVersion: number;\n tableName: string;\n}\n\nexport interface DataTransformer {\n (oldData: any): any | Promise<any>;\n}\n\nexport interface MigrationOptions {\n /** Custom data transformer to apply during migration */\n dataTransformer?: DataTransformer;\n /** Whether to allow destructive operations (delete and recreate). Default: false */\n allowDestructiveMigration?: boolean;\n /** Callback for migration progress/logging */\n onMigrationProgress?: (message: string, progress?: number) => void;\n /** Callback for migration errors (non-fatal warnings) */\n onMigrationWarning?: (message: string, error?: Error) => void;\n}\n\ninterface SchemaSnapshot {\n version: number;\n primaryKey: string | string[];\n indexes: ExpectedIndexDefinition[];\n recordCount?: number;\n timestamp: number;\n}\n\nconst METADATA_STORE_NAME = \"__schema_metadata__\";\n\n/**\n * Stores metadata about the database schema for migration tracking\n */\nasync function saveSchemaMetadata(\n db: IDBDatabase,\n tableName: string,\n snapshot: SchemaSnapshot\n): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n const transaction = db.transaction(METADATA_STORE_NAME, \"readwrite\");\n const store = transaction.objectStore(METADATA_STORE_NAME);\n const request = store.put({ ...snapshot, tableName }, tableName);\n\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n transaction.onerror = () => reject(transaction.error);\n } catch (err) {\n // Metadata store might not exist in old databases, that's OK\n resolve();\n }\n });\n}\n\nfunction openIndexedDbTable(\n tableName: string,\n version?: number,\n upgradeNeededCallback?: (event: IDBVersionChangeEvent) => void\n): Promise<IDBDatabase> {\n return openIdb(tableName, { version, onUpgradeNeeded: upgradeNeededCallback });\n}\n\n/**\n * Deletes an IndexedDB database completely\n */\nasync function deleteIndexedDbTable(tableName: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const deleteRequest = indexedDB.deleteDatabase(tableName);\n\n deleteRequest.onsuccess = () => resolve();\n deleteRequest.onerror = () => reject(deleteRequest.error);\n deleteRequest.onblocked = () => {\n reject(\n new Error(`Cannot delete database ${tableName}. Close all other tabs using this database.`)\n );\n };\n });\n}\n\n/**\n * Compares two schema definitions to determine what changes are needed\n */\ninterface SchemaDiff {\n indexesToAdd: ExpectedIndexDefinition[];\n indexesToRemove: string[];\n indexesToModify: ExpectedIndexDefinition[];\n primaryKeyChanged: boolean;\n needsObjectStoreRecreation: boolean;\n}\n\nfunction compareSchemas(\n store: IDBObjectStore,\n expectedPrimaryKey: string | string[],\n expectedIndexes: ExpectedIndexDefinition[]\n): SchemaDiff {\n const diff: SchemaDiff = {\n indexesToAdd: [],\n indexesToRemove: [],\n indexesToModify: [],\n primaryKeyChanged: false,\n needsObjectStoreRecreation: false,\n };\n\n // Check primary key\n const actualKeyPath = store.keyPath;\n const normalizedExpected = Array.isArray(expectedPrimaryKey)\n ? expectedPrimaryKey\n : expectedPrimaryKey;\n const normalizedActual = Array.isArray(actualKeyPath) ? actualKeyPath : actualKeyPath;\n\n if (!deepEqual(normalizedExpected, normalizedActual)) {\n diff.primaryKeyChanged = true;\n diff.needsObjectStoreRecreation = true;\n return diff; // If primary key changed, we need full recreation\n }\n\n // Build a map of existing indexes\n const existingIndexes = new Map<string, IDBIndex>();\n for (let i = 0; i < store.indexNames.length; i++) {\n const indexName = store.indexNames[i];\n existingIndexes.set(indexName, store.index(indexName));\n }\n\n // Check for indexes to add or modify\n for (const expectedIdx of expectedIndexes) {\n const existingIdx = existingIndexes.get(expectedIdx.name);\n\n if (!existingIdx) {\n diff.indexesToAdd.push(expectedIdx);\n } else {\n // Compare index properties\n const expectedKeyPath = Array.isArray(expectedIdx.keyPath)\n ? expectedIdx.keyPath\n : [expectedIdx.keyPath];\n const actualKeyPath = Array.isArray(existingIdx.keyPath)\n ? existingIdx.keyPath\n : [existingIdx.keyPath];\n\n const keyPathChanged = !deepEqual(expectedKeyPath, actualKeyPath);\n const uniqueChanged = existingIdx.unique !== (expectedIdx.options?.unique ?? false);\n const multiEntryChanged =\n existingIdx.multiEntry !== (expectedIdx.options?.multiEntry ?? false);\n\n if (keyPathChanged || uniqueChanged || multiEntryChanged) {\n diff.indexesToModify.push(expectedIdx);\n }\n\n existingIndexes.delete(expectedIdx.name);\n }\n }\n\n // Remaining indexes should be removed\n diff.indexesToRemove = Array.from(existingIndexes.keys());\n\n return diff;\n}\n\n/**\n * Reads all data from a store\n */\nasync function readAllData(store: IDBObjectStore): Promise<any[]> {\n return new Promise((resolve, reject) => {\n const request = store.getAll();\n request.onsuccess = () => resolve(request.result || []);\n request.onerror = () => reject(request.error);\n });\n}\n\n/**\n * Performs a non-destructive migration by adding/removing indexes\n */\nasync function performIncrementalMigration(\n db: IDBDatabase,\n tableName: string,\n diff: SchemaDiff,\n options: MigrationOptions = {}\n): Promise<IDBDatabase> {\n const currentVersion = db.version;\n const newVersion = currentVersion + 1;\n\n db.close();\n\n options.onMigrationProgress?.(\n `Migrating ${tableName} from version ${currentVersion} to ${newVersion}...`,\n 0\n );\n\n return openIndexedDbTable(tableName, newVersion, (event: IDBVersionChangeEvent) => {\n const transaction = (event.target as IDBOpenDBRequest).transaction!;\n const store = transaction.objectStore(tableName);\n\n // Remove outdated indexes\n for (const indexName of diff.indexesToRemove) {\n options.onMigrationProgress?.(`Removing index: ${indexName}`, 0.2);\n store.deleteIndex(indexName);\n }\n\n // Remove and recreate modified indexes\n for (const indexDef of diff.indexesToModify) {\n options.onMigrationProgress?.(`Updating index: ${indexDef.name}`, 0.4);\n if (store.indexNames.contains(indexDef.name)) {\n store.deleteIndex(indexDef.name);\n }\n store.createIndex(indexDef.name, indexDef.keyPath, indexDef.options);\n }\n\n // Add new indexes\n for (const indexDef of diff.indexesToAdd) {\n options.onMigrationProgress?.(`Adding index: ${indexDef.name}`, 0.6);\n store.createIndex(indexDef.name, indexDef.keyPath, indexDef.options);\n }\n\n options.onMigrationProgress?.(`Migration complete`, 1.0);\n });\n}\n\n/**\n * Performs a destructive migration by recreating the object store\n * This is needed when the primary key changes\n */\nasync function performDestructiveMigration(\n db: IDBDatabase,\n tableName: string,\n primaryKey: string | string[],\n expectedIndexes: ExpectedIndexDefinition[],\n options: MigrationOptions = {},\n autoIncrement: boolean = false\n): Promise<IDBDatabase> {\n if (!options.allowDestructiveMigration) {\n throw new Error(\n `Destructive migration required for ${tableName} but not allowed. ` +\n `Primary key has changed. Set allowDestructiveMigration=true to proceed with data loss, ` +\n `or provide a dataTransformer to migrate data.`\n );\n }\n\n const currentVersion = db.version;\n const newVersion = currentVersion + 1;\n\n options.onMigrationProgress?.(\n `Performing destructive migration of ${tableName}. Reading existing data...`,\n 0\n );\n\n // Read all existing data\n let existingData: any[] = [];\n try {\n const transaction = db.transaction(tableName, \"readonly\");\n const store = transaction.objectStore(tableName);\n existingData = await readAllData(store);\n options.onMigrationProgress?.(`Read ${existingData.length} records`, 0.3);\n } catch (err) {\n options.onMigrationWarning?.(\n `Failed to read existing data during migration: ${err}`,\n err as Error\n );\n }\n\n db.close();\n\n // Apply data transformer if provided\n if (options.dataTransformer && existingData.length > 0) {\n options.onMigrationProgress?.(`Transforming ${existingData.length} records...`, 0.4);\n try {\n const transformed = [];\n for (let i = 0; i < existingData.length; i++) {\n const record = existingData[i];\n const transformedRecord = await options.dataTransformer(record);\n if (transformedRecord !== undefined && transformedRecord !== null) {\n transformed.push(transformedRecord);\n }\n if (i % 100 === 0) {\n options.onMigrationProgress?.(\n `Transformed ${i}/${existingData.length} records`,\n 0.4 + (i / existingData.length) * 0.3\n );\n }\n }\n existingData = transformed;\n options.onMigrationProgress?.(`Transformation complete: ${existingData.length} records`, 0.7);\n } catch (err) {\n options.onMigrationWarning?.(\n `Data transformation failed: ${err}. Some data may be lost.`,\n err as Error\n );\n existingData = [];\n }\n }\n\n // Open with new version and recreate object store\n options.onMigrationProgress?.(`Recreating object store...`, 0.75);\n\n const newDb = await openIndexedDbTable(tableName, newVersion, (event: IDBVersionChangeEvent) => {\n const db = (event.target as IDBOpenDBRequest).result;\n\n // Delete old object store if it exists\n if (db.objectStoreNames.contains(tableName)) {\n db.deleteObjectStore(tableName);\n }\n\n // Create new object store with new schema\n const store = db.createObjectStore(tableName, { keyPath: primaryKey, autoIncrement });\n\n // Create indexes\n for (const idx of expectedIndexes) {\n store.createIndex(idx.name, idx.keyPath, idx.options);\n }\n\n // Restore data\n if (existingData.length > 0) {\n options.onMigrationProgress?.(`Restoring ${existingData.length} records...`, 0.8);\n\n for (const record of existingData) {\n try {\n store.put(record);\n } catch (err) {\n options.onMigrationWarning?.(`Failed to restore record: ${err}`, err as Error);\n }\n }\n }\n });\n\n options.onMigrationProgress?.(`Destructive migration complete`, 1.0);\n\n return newDb;\n}\n\n/**\n * Creates a new database with the specified schema\n */\nasync function createNewDatabase(\n tableName: string,\n primaryKey: string | string[],\n expectedIndexes: ExpectedIndexDefinition[],\n options: MigrationOptions = {},\n autoIncrement: boolean = false\n): Promise<IDBDatabase> {\n options.onMigrationProgress?.(`Creating new database: ${tableName}`, 0);\n\n // Delete existing database if it exists to avoid version conflicts\n try {\n await deleteIndexedDbTable(tableName);\n // Wait a bit for deletion to complete\n await new Promise((resolve) => setTimeout(resolve, 50));\n } catch (err) {\n // Ignore errors - database might not exist\n }\n\n const version = 1;\n\n const db = await openIndexedDbTable(tableName, version, (event: IDBVersionChangeEvent) => {\n const db = (event.target as IDBOpenDBRequest).result;\n\n // Create metadata store\n if (!db.objectStoreNames.contains(METADATA_STORE_NAME)) {\n db.createObjectStore(METADATA_STORE_NAME, { keyPath: \"tableName\" });\n }\n\n // Create main object store\n const store = db.createObjectStore(tableName, { keyPath: primaryKey, autoIncrement });\n\n // Create indexes\n for (const idx of expectedIndexes) {\n store.createIndex(idx.name, idx.keyPath, idx.options);\n }\n });\n\n // Save schema metadata\n const snapshot: SchemaSnapshot = {\n version: db.version,\n primaryKey,\n indexes: expectedIndexes,\n recordCount: 0,\n timestamp: Date.now(),\n };\n\n await saveSchemaMetadata(db, tableName, snapshot);\n\n options.onMigrationProgress?.(`Database created successfully`, 1.0);\n\n return db;\n}\n\n/**\n * Ensures that an IndexedDB table exists with the specified schema.\n * Performs migrations as needed without data loss when possible.\n */\nexport async function ensureIndexedDbTable(\n tableName: string,\n primaryKey: string | string[],\n expectedIndexes: ExpectedIndexDefinition[] = [],\n options: MigrationOptions = {},\n autoIncrement: boolean = false\n): Promise<IDBDatabase> {\n try {\n // Try to open existing database at current version (or create if doesn't exist)\n let db: IDBDatabase;\n let wasJustCreated = false;\n try {\n // Open without version - this will open at current version if exists, or create at version 1 if doesn't exist\n db = await openIndexedDbTable(tableName);\n\n // Check if database was just created (version 1 and no object stores)\n // This happens when indexedDB.open creates a new database without stores\n if (db.version === 1 && !db.objectStoreNames.contains(tableName)) {\n wasJustCreated = true;\n db.close();\n }\n } catch (err: any) {\n // If opening fails, database might not exist or there's a version conflict\n // Try to create it fresh\n options.onMigrationProgress?.(\n `Database ${tableName} does not exist or has version conflict, creating...`,\n 0\n );\n return await createNewDatabase(\n tableName,\n primaryKey,\n expectedIndexes,\n options,\n autoIncrement\n );\n }\n\n // If database was just created, we need to create the stores\n // We'll upgrade from version 1 to version 1 (which triggers onupgradeneeded with oldVersion=0)\n // Actually, we need to explicitly create at version 1 with stores\n if (wasJustCreated) {\n options.onMigrationProgress?.(`Creating new database: ${tableName}`, 0);\n // Delete the empty database and create it properly at version 1\n try {\n await deleteIndexedDbTable(tableName);\n await new Promise((resolve) => setTimeout(resolve, 50));\n } catch (err) {\n // Ignore errors\n }\n\n // Create at version 1 with stores\n db = await openIndexedDbTable(tableName, 1, (event: IDBVersionChangeEvent) => {\n const db = (event.target as IDBOpenDBRequest).result;\n\n // Create metadata store\n if (!db.objectStoreNames.contains(METADATA_STORE_NAME)) {\n db.createObjectStore(METADATA_STORE_NAME, { keyPath: \"tableName\" });\n }\n\n // Create main object store\n const store = db.createObjectStore(tableName, { keyPath: primaryKey, autoIncrement });\n\n // Create indexes\n for (const idx of expectedIndexes) {\n store.createIndex(idx.name, idx.keyPath, idx.options);\n }\n });\n\n // Save schema metadata\n const snapshot: SchemaSnapshot = {\n version: db.version,\n primaryKey,\n indexes: expectedIndexes,\n recordCount: 0,\n timestamp: Date.now(),\n };\n await saveSchemaMetadata(db, tableName, snapshot);\n\n options.onMigrationProgress?.(`Database created successfully`, 1.0);\n return db;\n }\n\n // Ensure metadata store exists\n if (!db.objectStoreNames.contains(METADATA_STORE_NAME)) {\n const currentVersion = db.version;\n db.close();\n\n db = await openIndexedDbTable(\n tableName,\n currentVersion + 1,\n (event: IDBVersionChangeEvent) => {\n const db = (event.target as IDBOpenDBRequest).result;\n if (!db.objectStoreNames.contains(METADATA_STORE_NAME)) {\n db.createObjectStore(METADATA_STORE_NAME, { keyPath: \"tableName\" });\n }\n }\n );\n }\n\n // Check if table structure matches expected\n if (!db.objectStoreNames.contains(tableName)) {\n // Object store doesn't exist, create it\n options.onMigrationProgress?.(`Object store ${tableName} does not exist, creating...`, 0);\n db.close();\n return await createNewDatabase(\n tableName,\n primaryKey,\n expectedIndexes,\n options,\n autoIncrement\n );\n }\n\n // Compare schemas to determine what migration is needed\n const transaction = db.transaction(tableName, \"readonly\");\n const store = transaction.objectStore(tableName);\n const diff = compareSchemas(store, primaryKey, expectedIndexes);\n\n await new Promise<void>((resolve) => {\n transaction.oncomplete = () => resolve();\n transaction.onerror = () => resolve();\n });\n\n // Determine migration strategy\n const needsMigration =\n diff.indexesToAdd.length > 0 ||\n diff.indexesToRemove.length > 0 ||\n diff.indexesToModify.length > 0 ||\n diff.needsObjectStoreRecreation;\n\n if (!needsMigration) {\n // Schema matches, no migration needed\n options.onMigrationProgress?.(`Schema for ${tableName} is up to date`, 1.0);\n\n // Update metadata anyway to keep timestamp current\n const snapshot: SchemaSnapshot = {\n version: db.version,\n primaryKey,\n indexes: expectedIndexes,\n timestamp: Date.now(),\n };\n await saveSchemaMetadata(db, tableName, snapshot);\n\n return db;\n }\n\n // Perform appropriate migration\n if (diff.needsObjectStoreRecreation) {\n options.onMigrationProgress?.(\n `Schema change requires object store recreation for ${tableName}`,\n 0\n );\n db = await performDestructiveMigration(\n db,\n tableName,\n primaryKey,\n expectedIndexes,\n options,\n autoIncrement\n );\n } else {\n options.onMigrationProgress?.(`Performing incremental migration for ${tableName}`, 0);\n db = await performIncrementalMigration(db, tableName, diff, options);\n }\n\n // Save updated metadata\n const snapshot: SchemaSnapshot = {\n version: db.version,\n primaryKey,\n indexes: expectedIndexes,\n timestamp: Date.now(),\n };\n await saveSchemaMetadata(db, tableName, snapshot);\n\n return db;\n } catch (err) {\n options.onMigrationWarning?.(`Migration failed for ${tableName}: ${err}`, err as Error);\n throw err;\n }\n}\n\n/**\n * Utility function to delete a database (for testing or cleanup)\n */\nexport async function dropIndexedDbTable(tableName: string): Promise<void> {\n return deleteIndexedDbTable(tableName);\n}\n",
|
|
6
7
|
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { JsonSchema } from \"@workglow/util/schema\";\nimport { createServiceToken } from \"@workglow/util\";\nimport { IndexedDbTabularStorage } from \"./IndexedDbTabularStorage\";\nimport {\n DefaultKeyValueKey,\n DefaultKeyValueSchema,\n IKvStorage,\n KvViaTabularStorage,\n} from \"@workglow/storage\";\n\nexport const IDB_KV_REPOSITORY = createServiceToken<IKvStorage<string, any, any>>(\n \"storage.kvRepository.indexedDb\"\n);\n\n/**\n * A key-value repository implementation that uses IndexedDB for persistent storage in the browser.\n * Leverages a tabular repository abstraction for IndexedDB operations.\n *\n * @template Key - The type of the primary key\n * @template Value - The type of the value being stored\n * @template Combined - Combined type of Key & Value\n */\nexport class IndexedDbKvStorage extends KvViaTabularStorage {\n public tabularRepository: IndexedDbTabularStorage<\n typeof DefaultKeyValueSchema,\n typeof DefaultKeyValueKey\n >;\n\n /**\n * Creates a new KvStorage instance\n */\n constructor(\n public dbName: string,\n keySchema: JsonSchema = { type: \"string\" },\n valueSchema: JsonSchema = {}\n ) {\n super(keySchema, valueSchema);\n this.tabularRepository = new IndexedDbTabularStorage(\n dbName,\n DefaultKeyValueSchema,\n DefaultKeyValueKey\n );\n }\n}\n",
|
|
7
|
-
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { createServiceToken, deepEqual, makeFingerprint, uuid4 } from \"@workglow/util\";\nimport { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from \"@workglow/util/schema\";\nimport {\n HybridSubscriptionManager,\n BaseTabularStorage,\n ClientProvidedKeysOption,\n KeyGenerationStrategy,\n AnyTabularStorage,\n AutoGeneratedKeys,\n CoveringIndexQueryOptions,\n DeleteSearchCriteria,\n InsertEntity,\n isSearchCondition,\n QueryOptions,\n SearchCriteria,\n SearchOperator,\n SimplifyPrimaryKey,\n TabularChangePayload,\n TabularSubscribeOptions,\n pickCoveringIndex,\n} from \"@workglow/storage\";\nimport { ensureIndexedDbTable, ExpectedIndexDefinition, MigrationOptions } from \"./IndexedDbTable\";\n\nexport const IDB_TABULAR_REPOSITORY = createServiceToken<AnyTabularStorage>(\n \"storage.tabularRepository.indexedDb\"\n);\n\n/**\n * Polling change-detection comparator for the hybrid subscription manager.\n *\n * Naively comparing entities via serialized equality is correct but\n * O(size-of-entity) per poll. That falls apart for tables\n * holding large blobs — e.g. an `activities` row whose `output_data` carries\n * a multi-megabyte `Uint8ClampedArray`: each poll cycle (default 1 s) would\n * stringify ~16 MB per row × N rows, locking the main thread.\n *\n * Fast path: if both rows expose a string `updated_at`, compare just those.\n * The repositories in this codebase bump `updated_at` on every write (see\n * `ActivityRepository.updateActivity`), so the timestamp is a sufficient\n * change witness. Fall back to the structural compare for tables that don't\n * follow that convention so correctness is preserved everywhere.\n */\nfunction compareEntitiesForChange<T>(a: T, b: T): boolean {\n const au = (a as { updated_at?: unknown })?.updated_at;\n const bu = (b as { updated_at?: unknown })?.updated_at;\n if (typeof au === \"string\" && typeof bu === \"string\") {\n return au === bu;\n }\n return deepEqual(a, b);\n}\n\n/**\n * A tabular repository implementation using IndexedDB for browser-based storage.\n *\n * @template Schema - The schema definition for the entity\n * @template PrimaryKeyNames - Array of property names that form the primary key\n */\nexport class IndexedDbTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>,\n InsertType extends InsertEntity<Entity, AutoGeneratedKeys<Schema>> = InsertEntity<\n Entity,\n AutoGeneratedKeys<Schema>\n >,\n> extends BaseTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, Value, InsertType> {\n /** Promise that resolves to the IndexedDB database instance */\n private db: IDBDatabase | undefined;\n /** Promise to track ongoing database setup to prevent concurrent setup calls */\n private setupPromise: Promise<IDBDatabase> | null = null;\n /** Migration options for database schema changes */\n private migrationOptions: MigrationOptions;\n /** Shared hybrid subscription manager */\n private hybridManager: HybridSubscriptionManager<\n Entity,\n string,\n TabularChangePayload<Entity>\n > | null = null;\n /** Hybrid subscription options */\n private readonly hybridOptions: {\n readonly useBroadcastChannel: boolean;\n readonly backupPollingIntervalMs: number;\n };\n /**\n * Indexes safe for cursor-based narrowing. An IDB index excludes records\n * whose keyPath has any undefined component, so iterating an index can miss\n * records when an indexed column is optional in the schema. Native\n * `count(range)` and cursor scans are only correct over indexes whose every\n * column is required. Computed lazily on first use.\n */\n private cursorSafeIndexes: Array<Array<keyof Entity>> | undefined;\n\n /**\n * Creates a new IndexedDB-based tabular repository.\n * @param table - Name of the IndexedDB store to use.\n * @param schema - Schema defining the structure of the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param indexes - Array of columns or column arrays to make searchable. Each string or single column creates a single-column index,\n * while each array creates a compound index with columns in the specified order.\n * @param migrationOptions - Options for handling database schema migrations\n * @param clientProvidedKeys - How to handle client-provided values for auto-generated keys\n */\n constructor(\n public table: string = \"tabular_store\",\n schema: Schema,\n primaryKeyNames: PrimaryKeyNames,\n indexes: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[] = [],\n migrationOptions: MigrationOptions & {\n readonly useBroadcastChannel?: boolean;\n readonly backupPollingIntervalMs?: number;\n } = {},\n clientProvidedKeys: ClientProvidedKeysOption = \"if-missing\"\n ) {\n super(schema, primaryKeyNames, indexes, clientProvidedKeys);\n this.migrationOptions = migrationOptions;\n this.hybridOptions = {\n useBroadcastChannel: migrationOptions.useBroadcastChannel ?? true,\n backupPollingIntervalMs: migrationOptions.backupPollingIntervalMs ?? 5000,\n };\n }\n\n /**\n * Internal method to get the database, setting it up if needed.\n * This ensures lazy initialization of the database.\n */\n private async getDb(): Promise<IDBDatabase> {\n if (this.db) return this.db;\n await this.setupDatabase();\n return this.db!;\n }\n\n /**\n * Sets up the IndexedDB database table with the required schema and indexes.\n * Must be called before using any other methods.\n */\n public override async setupDatabase(): Promise<void> {\n if (this.db) return;\n if (this.setupPromise) {\n await this.setupPromise;\n return;\n }\n\n this.setupPromise = this.performSetup();\n try {\n this.db = await this.setupPromise;\n } finally {\n this.setupPromise = null;\n }\n }\n\n /**\n * Internal method to perform the actual database setup\n */\n private async performSetup(): Promise<IDBDatabase> {\n const pkColumns = super.primaryKeyColumns() as string[];\n\n // Create index definitions for both single and compound indexes\n const expectedIndexes: ExpectedIndexDefinition[] = [];\n\n for (const spec of this.indexes) {\n // Handle compound index\n const columns = spec as Array<keyof Entity>;\n // Skip if this is just the primary key or a prefix of it\n if (columns.length <= pkColumns.length) {\n const isPkPrefix = columns.every((col, idx) => col === pkColumns[idx]);\n if (isPkPrefix) continue;\n }\n\n // Create compound index name and keyPath\n const columnNames = columns.map((col) => String(col));\n const indexName = columnNames.join(\"_\");\n expectedIndexes.push({\n name: indexName,\n keyPath: columnNames.length === 1 ? columnNames[0] : columnNames,\n options: { unique: false },\n });\n }\n\n const primaryKey = pkColumns.length === 1 ? pkColumns[0] : pkColumns;\n\n // Determine if we should use autoIncrement\n // IndexedDB autoIncrement only works with single numeric keys\n const useAutoIncrement =\n this.hasAutoGeneratedKey() &&\n this.autoGeneratedKeyStrategy === \"autoincrement\" &&\n pkColumns.length === 1;\n\n // Ensure that our table is created/upgraded only if the structure (indexes) has changed.\n return await ensureIndexedDbTable(\n this.table,\n primaryKey,\n expectedIndexes,\n this.migrationOptions,\n useAutoIncrement\n );\n }\n\n /**\n * Generates a key value for UUID keys\n * Integer autoincrement keys are handled by IndexedDB's autoIncrement\n * @param columnName - Name of the column to generate a key for\n * @param strategy - The generation strategy to use\n * @returns The generated key value\n */\n protected override generateKeyValue(\n columnName: string,\n strategy: KeyGenerationStrategy\n ): string | number {\n if (strategy === \"uuid\") {\n return uuid4();\n }\n // autoincrement is handled by IndexedDB's autoIncrement option\n throw new Error(\n `IndexedDB autoincrement keys are generated by the database, not client-side. Column: ${columnName}`\n );\n }\n\n /**\n * Stores a row in the repository.\n * @param record - The entity to store (may be missing auto-generated keys).\n * @returns The stored entity\n * @emits put - Emitted when the value is successfully stored\n */\n async put(record: InsertType): Promise<Entity> {\n const db = await this.getDb();\n let recordToStore = record as unknown as Entity;\n\n // Handle auto-generated keys\n if (this.hasAutoGeneratedKey() && this.autoGeneratedKeyName) {\n const keyName = String(this.autoGeneratedKeyName);\n const clientProvidedValue = (record as Record<string, unknown>)[keyName];\n const hasClientValue = clientProvidedValue !== undefined && clientProvidedValue !== null;\n\n if (this.autoGeneratedKeyStrategy === \"uuid\") {\n // UUID generation - must be done client-side\n let shouldGenerate = false;\n if (this.clientProvidedKeys === \"never\") {\n shouldGenerate = true;\n } else if (this.clientProvidedKeys === \"always\") {\n if (!hasClientValue) {\n throw new Error(\n `Auto-generated key \"${keyName}\" is required when clientProvidedKeys is \"always\"`\n );\n }\n shouldGenerate = false;\n } else {\n // \"if-missing\"\n shouldGenerate = !hasClientValue;\n }\n\n if (shouldGenerate) {\n const generatedValue = this.generateKeyValue(keyName, \"uuid\");\n recordToStore = { ...record, [keyName]: generatedValue } as Entity;\n }\n } else if (this.autoGeneratedKeyStrategy === \"autoincrement\") {\n // Autoincrement handled by IndexedDB\n // If clientProvidedKeys is \"always\", require the value\n if (this.clientProvidedKeys === \"always\" && !hasClientValue) {\n throw new Error(\n `Auto-generated key \"${keyName}\" is required when clientProvidedKeys is \"always\"`\n );\n }\n // If clientProvidedKeys is \"never\", omit the key to let IDB generate\n if (this.clientProvidedKeys === \"never\") {\n const { [keyName]: _, ...rest } = record as Record<string, unknown>;\n recordToStore = rest as Entity;\n }\n // \"if-missing\": use client value if provided, omit if not\n }\n }\n\n // Merge key and value, ensuring all fields are at the root level for indexing\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(this.table, \"readwrite\");\n const store = transaction.objectStore(this.table);\n const request = store.put(recordToStore);\n request.onerror = () => {\n reject(request.error);\n };\n request.onsuccess = () => {\n // For autoincrement keys, we need to update the record with the generated key\n if (\n this.hasAutoGeneratedKey() &&\n this.autoGeneratedKeyName &&\n this.autoGeneratedKeyStrategy === \"autoincrement\"\n ) {\n const keyName = String(this.autoGeneratedKeyName);\n if (recordToStore[keyName as keyof Entity] === undefined) {\n // Get the generated key from the request result\n recordToStore = { ...recordToStore, [keyName]: request.result } as Entity;\n }\n }\n this.events.emit(\"put\", recordToStore);\n resolve(recordToStore);\n };\n transaction.oncomplete = () => {\n // Notify hybrid manager of local change\n this.hybridManager?.notifyLocalChange();\n };\n });\n }\n\n /**\n * Stores multiple rows in the repository in a bulk operation.\n * @param records - Array of entities to store (may be missing auto-generated keys).\n * @returns Array of stored entities\n * @emits put - Emitted for each record successfully stored\n */\n async putBulk(records: InsertType[]): Promise<Entity[]> {\n // Use individual put calls to ensure auto-generated keys are handled correctly\n return await Promise.all(records.map((record) => this.put(record)));\n }\n\n protected override getPrimaryKeyAsOrderedArray(key: PrimaryKey) {\n return super\n .getPrimaryKeyAsOrderedArray(key)\n .map((value) => (typeof value === \"bigint\" ? value.toString() : value));\n }\n\n private getIndexedKey(key: PrimaryKey): any {\n const keys = super\n .getPrimaryKeyAsOrderedArray(key)\n .map((value) => (typeof value === \"bigint\" ? value.toString() : value));\n return keys.length === 1 ? keys[0] : keys;\n }\n\n /**\n * Retrieves a value from the repository by its key.\n * @param key - The key object.\n * @returns The value object or undefined if not found.\n * @emits get - Emitted when the value is successfully retrieved\n */\n async get(key: PrimaryKey): Promise<Entity | undefined> {\n const db = await this.getDb();\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(this.table, \"readonly\");\n const store = transaction.objectStore(this.table);\n const request = store.get(this.getIndexedKey(key));\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n if (!request.result) {\n this.events.emit(\"get\", key, undefined);\n resolve(undefined);\n return;\n }\n this.events.emit(\"get\", key, request.result);\n resolve(request.result);\n };\n });\n }\n\n /**\n * Returns an array of all entries in the repository, with optional ordering, offset, and limit.\n * @param options - Optional ordering, limit, and offset options\n * @returns Array of all entries in the repository.\n */\n async getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined> {\n this.validateGetAllOptions(options);\n const db = await this.getDb();\n const transaction = db.transaction(this.table, \"readonly\");\n const store = transaction.objectStore(this.table);\n const request = store.getAll();\n return new Promise((resolve, reject) => {\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n let values: Entity[] = request.result;\n if (values.length === 0) {\n resolve(undefined);\n return;\n }\n\n if (options?.orderBy && options.orderBy.length > 0) {\n values.sort((a, b) => {\n for (const { column, direction } of options.orderBy!) {\n const aVal = a[column] as string | number | null | undefined;\n const bVal = b[column] as string | number | null | undefined;\n if (aVal == null && bVal == null) continue;\n if (aVal == null) return direction === \"ASC\" ? -1 : 1;\n if (bVal == null) return direction === \"ASC\" ? 1 : -1;\n if (aVal < bVal) return direction === \"ASC\" ? -1 : 1;\n if (aVal > bVal) return direction === \"ASC\" ? 1 : -1;\n }\n return 0;\n });\n }\n\n if (options?.offset !== undefined) {\n values = values.slice(options.offset);\n }\n\n if (options?.limit !== undefined) {\n values = values.slice(0, options.limit);\n }\n\n resolve(values.length > 0 ? values : undefined);\n };\n });\n }\n\n /**\n * Deletes a row from the repository.\n * @param key - The key object to delete.\n */\n async delete(key: PrimaryKey): Promise<void> {\n const db = await this.getDb();\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(this.table, \"readwrite\");\n const store = transaction.objectStore(this.table);\n const request = store.delete(this.getIndexedKey(key));\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n this.events.emit(\"delete\", key as keyof Entity);\n resolve();\n };\n transaction.oncomplete = () => {\n // Notify hybrid manager of local change\n this.hybridManager?.notifyLocalChange();\n };\n });\n }\n\n /**\n * Deletes all records from the repository.\n * @emits clearall - Emitted when all values are deleted\n */\n async deleteAll(): Promise<void> {\n const db = await this.getDb();\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(this.table, \"readwrite\");\n const store = transaction.objectStore(this.table);\n const request = store.clear();\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n this.events.emit(\"clearall\");\n resolve();\n };\n transaction.oncomplete = () => {\n // Notify hybrid manager of local change\n this.hybridManager?.notifyLocalChange();\n };\n });\n }\n\n /**\n * Returns the total number of rows in the repository.\n * @returns Count of stored items.\n */\n async size(): Promise<number> {\n const db = await this.getDb();\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(this.table, \"readonly\");\n const store = transaction.objectStore(this.table);\n const request = store.count();\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result);\n });\n }\n\n /**\n * Returns the subset of configured indexes safe to use for cursor-based\n * narrowing — those whose every column is required by the schema. IDB\n * excludes records with undefined keyPath components from indexes, so\n * iterating an index would silently miss records when any indexed column\n * is optional.\n */\n private getCursorSafeIndexes(): Array<Array<keyof Entity>> {\n if (this.cursorSafeIndexes) return this.cursorSafeIndexes;\n const required = new Set(this.schema.required ?? []);\n this.cursorSafeIndexes = this.indexes.filter((columns) =>\n columns.every((column) => required.has(String(column)))\n );\n return this.cursorSafeIndexes;\n }\n\n /**\n * Picks the best index to narrow a criteria-based scan, preferring indexes\n * whose prefix covers every criteria column (so callers can use the native\n * `count()` API) and falling back to the longest equality-prefix match.\n *\n * Upper bound uses `[]` as a sentinel that compares greater than any\n * primitive value at the next index position (per IndexedDB key ordering:\n * array > binary > string > date > number). Assumes subsequent indexed\n * columns hold primitive values — array-valued columns would compare\n * greater than `[]` and slip outside the range.\n */\n private createIndexedRange(\n store: IDBObjectStore,\n criteria: SearchCriteria<Entity>\n ):\n | {\n source: IDBObjectStore | IDBIndex;\n range: IDBKeyRange | undefined;\n coversCriteria: boolean;\n }\n | undefined {\n const criteriaColumns = Object.keys(criteria) as Array<keyof Entity>;\n if (criteriaColumns.length === 0) return undefined;\n\n let best:\n | {\n indexName: string;\n prefixValues: unknown[];\n fullMatch: boolean;\n coversCriteria: boolean;\n }\n | undefined;\n\n for (const indexColumns of this.getCursorSafeIndexes()) {\n const prefixValues: unknown[] = [];\n for (const column of indexColumns) {\n const value = this.getEqualityCriterionValue(criteria, column);\n if (value === undefined) break;\n prefixValues.push(value);\n }\n\n if (prefixValues.length === 0) continue;\n\n const indexedPrefix = indexColumns.slice(0, prefixValues.length);\n const coversCriteria = criteriaColumns.every((column) => indexedPrefix.includes(column));\n\n const better =\n !best ||\n (coversCriteria && !best.coversCriteria) ||\n (coversCriteria === best.coversCriteria && prefixValues.length > best.prefixValues.length);\n\n if (better) {\n best = {\n indexName: indexColumns.map((column) => String(column)).join(\"_\"),\n prefixValues,\n fullMatch: prefixValues.length === indexColumns.length,\n coversCriteria,\n };\n }\n }\n\n if (!best) return undefined;\n\n const range = best.fullMatch\n ? IDBKeyRange.only(best.prefixValues.length === 1 ? best.prefixValues[0] : best.prefixValues)\n : IDBKeyRange.bound(best.prefixValues, [...best.prefixValues, []]);\n\n return {\n source: store.index(best.indexName),\n range,\n coversCriteria: best.coversCriteria,\n };\n }\n\n /**\n * Counts rows matching the specified search criteria.\n *\n * Uses the native `count()` API when an index prefix covers every criteria\n * column. Otherwise narrows via the longest matching index prefix and\n * filters the remaining columns during cursor iteration. Falls back to a\n * full store scan only when no index applies.\n */\n override async count(criteria?: SearchCriteria<Entity>): Promise<number> {\n if (!criteria || Object.keys(criteria).length === 0) {\n return await this.size();\n }\n\n this.validateQueryParams(criteria);\n const db = await this.getDb();\n\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(this.table, \"readonly\");\n const store = transaction.objectStore(this.table);\n const plan = this.createIndexedRange(store, criteria);\n\n if (plan?.coversCriteria) {\n const request = plan.source.count(plan.range);\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result);\n return;\n }\n\n const source = plan?.source ?? store;\n const range = plan?.range;\n let count = 0;\n const request = source.openCursor(range);\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n const cursor = request.result;\n if (!cursor) {\n resolve(count);\n return;\n }\n if (this.matchesCriteria(cursor.value as Entity, criteria)) {\n count += 1;\n }\n cursor.continue();\n };\n });\n }\n\n /**\n * Fetches a page of records from the repository.\n * @param offset - Number of records to skip\n * @param limit - Maximum number of records to return\n * @returns Array of entities or undefined if no records found\n */\n async getBulk(offset: number, limit: number): Promise<Entity[] | undefined> {\n if (offset < 0) {\n throw new RangeError(`offset must be non-negative, got ${offset}`);\n }\n if (limit <= 0) {\n return undefined;\n }\n\n const db = await this.getDb();\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(this.table, \"readonly\");\n const store = transaction.objectStore(this.table);\n const request = store.openCursor();\n const entities: Entity[] = [];\n let skipped = false;\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n const cursor = request.result;\n if (cursor) {\n // Skip to offset using advance\n if (!skipped && offset > 0) {\n skipped = true;\n cursor.advance(offset);\n return;\n }\n\n // Collect records up to the limit\n entities.push(cursor.value);\n if (entities.length === limit) {\n resolve(entities);\n return;\n }\n cursor.continue();\n } else {\n // No more records\n resolve(entities.length > 0 ? entities : undefined);\n }\n };\n });\n }\n\n /**\n * Checks if a record matches all criteria conditions.\n * @param record - The record to check\n * @param criteria - The search criteria\n * @returns true if all conditions match\n */\n private matchesCriteria(record: Entity, criteria: DeleteSearchCriteria<Entity>): boolean {\n for (const column of Object.keys(criteria) as Array<keyof Entity>) {\n const criterion = criteria[column];\n const recordValue = record[column];\n\n let operator: SearchOperator = \"=\";\n let value: Entity[keyof Entity];\n\n if (isSearchCondition(criterion)) {\n operator = criterion.operator;\n value = criterion.value as Entity[keyof Entity];\n } else {\n value = criterion as Entity[keyof Entity];\n }\n\n // Skip null values for comparison operators\n if (operator !== \"=\" && (recordValue === null || recordValue === undefined)) {\n return false;\n }\n\n switch (operator) {\n case \"=\":\n if (recordValue !== value) return false;\n break;\n case \"<\":\n if (!(recordValue < value)) return false;\n break;\n case \"<=\":\n if (!(recordValue <= value)) return false;\n break;\n case \">\":\n if (!(recordValue > value)) return false;\n break;\n case \">=\":\n if (!(recordValue >= value)) return false;\n break;\n default:\n return false;\n }\n }\n return true;\n }\n\n /**\n * Deletes all entries matching the specified search criteria.\n * Supports multiple columns with optional comparison operators.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n */\n async deleteSearch(criteria: DeleteSearchCriteria<Entity>): Promise<void> {\n const criteriaKeys = Object.keys(criteria) as Array<keyof Entity>;\n if (criteriaKeys.length === 0) {\n return;\n }\n\n const db = await this.getDb();\n\n return new Promise(async (resolve, reject) => {\n try {\n const transaction = db.transaction(this.table, \"readwrite\");\n const store = transaction.objectStore(this.table);\n\n // Set up transaction event handlers\n transaction.oncomplete = () => {\n this.events.emit(\"delete\", criteriaKeys[0] as keyof Entity);\n // Notify hybrid manager of local change\n this.hybridManager?.notifyLocalChange();\n resolve();\n };\n\n transaction.onerror = () => {\n reject(transaction.error);\n };\n\n // Get all records and filter\n const getAllRequest = store.getAll();\n\n getAllRequest.onsuccess = () => {\n const allRecords: Entity[] = getAllRequest.result;\n\n // Filter records that match all criteria\n const recordsToDelete = allRecords.filter((record) =>\n this.matchesCriteria(record, criteria)\n );\n\n if (recordsToDelete.length === 0) {\n // No records to delete\n return;\n }\n\n // Delete each record that matches the criteria\n for (const record of recordsToDelete) {\n // Extract the primary key from the record\n const primaryKey = this.primaryKeyColumns().reduce((key, col) => {\n // @ts-ignore - We know these properties exist on the record\n key[col] = record[col];\n return key;\n }, {} as PrimaryKey);\n\n // Delete the record using the primary key\n const request = store.delete(this.getIndexedKey(primaryKey));\n\n request.onerror = () => {\n console.error(\"Error deleting record:\", request.error);\n };\n }\n };\n\n getAllRequest.onerror = () => {\n reject(getAllRequest.error);\n };\n } catch (error) {\n reject(error);\n }\n });\n }\n\n private getEqualityCriterionValue(\n criteria: SearchCriteria<Entity>,\n column: keyof Entity\n ): Entity[keyof Entity] | undefined {\n const criterion = criteria[column];\n if (criterion === undefined) return undefined;\n if (isSearchCondition(criterion)) {\n return criterion.operator === \"=\" ? (criterion.value as Entity[keyof Entity]) : undefined;\n }\n return criterion as Entity[keyof Entity];\n }\n\n private compareByOrder(a: Entity, b: Entity, options?: QueryOptions<Entity>): number {\n if (!options?.orderBy) return 0;\n for (const { column, direction } of options.orderBy) {\n const aVal = a[column] as string | number | null | undefined;\n const bVal = b[column] as string | number | null | undefined;\n if (aVal == null && bVal == null) continue;\n if (aVal == null) return direction === \"ASC\" ? -1 : 1;\n if (bVal == null) return direction === \"ASC\" ? 1 : -1;\n if (aVal < bVal) return direction === \"ASC\" ? -1 : 1;\n if (aVal > bVal) return direction === \"ASC\" ? 1 : -1;\n }\n return 0;\n }\n\n private createIndexedQuery(\n store: IDBObjectStore,\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): {\n source: IDBObjectStore | IDBIndex;\n range: IDBKeyRange | undefined;\n direction: IDBCursorDirection;\n satisfiesOrder: boolean;\n appliedLimit: boolean;\n appliedOffset: boolean;\n skipRemaining: number;\n } {\n const orderBy = options?.orderBy ?? [];\n let best:\n | {\n indexName: string;\n prefixValues: unknown[];\n fullMatch: boolean;\n satisfiesOrder: boolean;\n direction: IDBCursorDirection;\n }\n | undefined;\n\n for (const indexColumns of this.getCursorSafeIndexes()) {\n const prefixValues: unknown[] = [];\n for (const column of indexColumns) {\n const value = this.getEqualityCriterionValue(criteria, column);\n if (value === undefined) break;\n prefixValues.push(value);\n }\n\n if (prefixValues.length === 0) continue;\n\n const remainingColumns = indexColumns.slice(prefixValues.length);\n let redundantOrderPrefixLength = 0;\n while (\n redundantOrderPrefixLength < orderBy.length &&\n redundantOrderPrefixLength < prefixValues.length &&\n orderBy[redundantOrderPrefixLength]?.column === indexColumns[redundantOrderPrefixLength]\n ) {\n redundantOrderPrefixLength++;\n }\n const normalizedOrderBy = orderBy.slice(redundantOrderPrefixLength);\n const satisfiesOrder =\n normalizedOrderBy.length === 0 ||\n (normalizedOrderBy.length <= remainingColumns.length &&\n normalizedOrderBy.every((order, index) => order.column === remainingColumns[index]) &&\n orderBy.every((order) => order.direction === orderBy[0]?.direction));\n\n if (!satisfiesOrder && best) continue;\n\n if (\n !best ||\n (satisfiesOrder && !best.satisfiesOrder) ||\n prefixValues.length > best.prefixValues.length\n ) {\n best = {\n indexName: indexColumns.map((column) => String(column)).join(\"_\"),\n prefixValues,\n fullMatch: prefixValues.length === indexColumns.length,\n satisfiesOrder,\n direction: orderBy[0]?.direction === \"DESC\" ? \"prev\" : \"next\",\n };\n }\n }\n\n const appliedLimit = Boolean(best?.satisfiesOrder && options?.limit !== undefined);\n const appliedOffset = Boolean(best?.satisfiesOrder && options?.offset !== undefined);\n\n if (!best) {\n return {\n source: store,\n range: undefined,\n direction: orderBy[0]?.direction === \"DESC\" ? \"prev\" : \"next\",\n satisfiesOrder: false,\n appliedLimit: false,\n appliedOffset: false,\n skipRemaining: 0,\n };\n }\n\n const source = store.index(best.indexName);\n // See createIndexedRange for the `[]` upper-bound sentinel rationale.\n const keyRange = best.fullMatch\n ? IDBKeyRange.only(best.prefixValues.length === 1 ? best.prefixValues[0] : best.prefixValues)\n : IDBKeyRange.bound(best.prefixValues, [...best.prefixValues, []]);\n\n return {\n source,\n range: keyRange,\n direction: best.direction,\n satisfiesOrder: best.satisfiesOrder,\n appliedLimit,\n appliedOffset,\n skipRemaining: appliedOffset ? (options?.offset ?? 0) : 0,\n };\n }\n\n /**\n * Queries entries matching the specified search criteria with optional ordering, limit, and offset.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @param options - Optional ordering, limit, and offset options\n * @returns Array of matching entities or undefined if no matches found\n */\n async query(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined> {\n this.validateQueryParams(criteria, options);\n const db = await this.getDb();\n\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(this.table, \"readonly\");\n const store = transaction.objectStore(this.table);\n const indexedQuery = this.createIndexedQuery(store, criteria, options);\n const results: Entity[] = [];\n const request = indexedQuery.source.openCursor(indexedQuery.range, indexedQuery.direction);\n\n request.onsuccess = () => {\n const cursor = request.result;\n if (!cursor) {\n let finalResults = results;\n\n if (!indexedQuery.satisfiesOrder && options?.orderBy && options.orderBy.length > 0) {\n finalResults = [...finalResults].sort((a, b) => this.compareByOrder(a, b, options));\n }\n\n if (!indexedQuery.appliedOffset && options?.offset !== undefined) {\n finalResults = finalResults.slice(options.offset);\n }\n\n if (!indexedQuery.appliedLimit && options?.limit !== undefined) {\n finalResults = finalResults.slice(0, options.limit);\n }\n\n const result = finalResults.length > 0 ? finalResults : undefined;\n this.events.emit(\"query\", criteria as Partial<Entity>, result);\n resolve(result);\n return;\n }\n\n const record = cursor.value as Entity;\n if (this.matchesCriteria(record, criteria)) {\n if (indexedQuery.skipRemaining > 0) {\n indexedQuery.skipRemaining -= 1;\n } else {\n results.push(record);\n if (indexedQuery.appliedLimit && results.length === options?.limit) {\n const result = results.length > 0 ? results : undefined;\n this.events.emit(\"query\", criteria as Partial<Entity>, result);\n resolve(result);\n return;\n }\n }\n }\n\n cursor.continue();\n };\n\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Strict, projected query served entirely by a covering compound index.\n * Uses `openKeyCursor` — never reads `cursor.value` — so only the index key\n * bytes are loaded into memory. Ideal for tables with large value blobs.\n *\n * Throws {@link CoveringIndexMissingError} when no registered index can serve\n * the request (i.e. the index does not cover all select + orderBy columns).\n */\n override async queryIndex<K extends keyof Entity & string>(\n criteria: SearchCriteria<Entity>,\n options: CoveringIndexQueryOptions<Entity, K>\n ): Promise<Pick<Entity, K>[]> {\n this.validateSelect(options);\n this.validateQueryParams(criteria, options);\n\n const registered = this.indexes.map((cols) => {\n const cs = Array.isArray(cols) ? (cols as string[]) : [cols as string];\n return { name: cs.join(\"_\"), keyPath: cs };\n });\n\n const picked = pickCoveringIndex({\n table: this.table,\n indexes: registered,\n criteriaColumns: Object.keys(criteria),\n orderByColumns: (options.orderBy ?? []).map((o) => ({\n column: String(o.column),\n direction: o.direction,\n })),\n selectColumns: options.select.map(String),\n primaryKeyColumns: this.primaryKeyColumns().map(String),\n });\n\n const db = await this.getDb();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(this.table, \"readonly\");\n const store = tx.objectStore(this.table);\n const idx = store.index(picked.name);\n\n // Build the equality prefix key range from criteria values.\n // Only push columns whose criterion is a direct equality value (plain value or operator \"=\").\n // Any non-equality operator (>, <, etc.) breaks the prefix; the existing in-cursor\n // compareWithOperator filter below handles those columns.\n const prefix: unknown[] = [];\n for (const col of picked.keyPath) {\n const c = (criteria as Record<string, unknown>)[col];\n if (c === undefined && !(col in (criteria as Record<string, unknown>))) break;\n if (isSearchCondition(c)) {\n if (c.operator !== \"=\") break;\n prefix.push(c.value);\n } else {\n prefix.push(c);\n }\n }\n // Use IDBKeyRange.bound with [] upper sentinel for prefix scans on compound keys.\n // `[...prefix, []]` is greater than any key starting with prefix in IDB's comparison.\n const range =\n prefix.length === 0\n ? undefined\n : prefix.length === picked.keyPath.length\n ? IDBKeyRange.only(prefix.length === 1 ? prefix[0] : prefix)\n : IDBKeyRange.bound(prefix, [...prefix, []]);\n\n const direction: IDBCursorDirection = picked.reverseDirection ? \"prev\" : \"next\";\n // openKeyCursor — never reads cursor.value, only the index key and primaryKey\n const request = idx.openKeyCursor(range, direction);\n const out: Pick<Entity, K>[] = [];\n let toSkip = options.offset ?? 0;\n\n // Precompute lookup maps for the cursor loop (O(keyPath) once, not per-row)\n const keyPathPositions = new Map<string, number>();\n picked.keyPath.forEach((col, i) => keyPathPositions.set(col, i));\n\n const pkCols = this.primaryKeyColumns().map(String);\n const pkPositions = new Map<string, number>();\n pkCols.forEach((col, i) => pkPositions.set(col, i));\n\n request.onsuccess = () => {\n const cursor = request.result;\n if (!cursor) {\n resolve(out);\n return;\n }\n\n const key = cursor.key;\n\n // Decode projected columns from the index key array and primaryKey\n const row = {} as Record<string, unknown>;\n for (const col of options.select) {\n const colStr = String(col);\n const pos = keyPathPositions.get(colStr);\n if (pos !== undefined) {\n // Column is in the index key path — extract from key array\n row[colStr] = Array.isArray(key) ? key[pos] : key;\n } else {\n // Column must be a primary key column — use cursor.primaryKey\n if (pkCols.length === 1 && colStr === pkCols[0]) {\n row[colStr] = cursor.primaryKey;\n } else {\n const pkPos = pkPositions.get(colStr);\n if (pkPos !== undefined) {\n row[colStr] = Array.isArray(cursor.primaryKey)\n ? (cursor.primaryKey as unknown[])[pkPos]\n : cursor.primaryKey;\n }\n }\n }\n }\n\n // Apply criteria for index positions beyond the equality prefix.\n // Positions [0, prefix.length) are guaranteed-equal by the IDBKeyRange,\n // so they don't need re-checking. Positions >= prefix.length must be\n // filtered here — this includes plain-value equalities trailing a\n // non-equality break (criteria like `{a:1, b:>=5, c:3}` with prefix=[1]\n // need an in-cursor `c === 3` check) and any SearchCondition.\n let matches = true;\n for (const [col, crit] of Object.entries(criteria as Record<string, unknown>)) {\n const pos = keyPathPositions.get(col);\n if (pos === undefined) continue;\n if (pos < prefix.length) continue; // covered by IDBKeyRange\n const valFromKey = Array.isArray(key) ? (key as unknown[])[pos] : key;\n const op: SearchOperator = isSearchCondition(crit) ? crit.operator : \"=\";\n const val = isSearchCondition(crit) ? crit.value : crit;\n if (!compareWithOperator(valFromKey, op, val)) {\n matches = false;\n break;\n }\n }\n\n if (matches) {\n if (toSkip > 0) {\n toSkip -= 1;\n } else {\n out.push(row as Pick<Entity, K>);\n if (options.limit !== undefined && out.length >= options.limit) {\n resolve(out);\n return;\n }\n }\n }\n\n cursor.continue();\n };\n\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Gets or creates the shared hybrid subscription manager.\n * This ensures all subscriptions share a single manager.\n */\n private getHybridManager(): HybridSubscriptionManager<\n Entity,\n string,\n TabularChangePayload<Entity>\n > {\n if (!this.hybridManager) {\n // Generate unique channel name based on table name\n const channelName = `indexeddb-tabular-${this.table}`;\n\n this.hybridManager = new HybridSubscriptionManager<\n Entity,\n string,\n TabularChangePayload<Entity>\n >(\n channelName,\n async () => {\n // Fetch all entities and create a map keyed by entity fingerprint\n const entities = (await this.getAll()) || [];\n const map = new Map<string, Entity>();\n for (const entity of entities) {\n const { key } = this.separateKeyValueFromCombined(entity);\n const fingerprint = await makeFingerprint(key);\n map.set(fingerprint, entity);\n }\n return map;\n },\n compareEntitiesForChange,\n {\n insert: (item) => ({ type: \"INSERT\" as const, new: item }),\n update: (oldItem, newItem) => ({ type: \"UPDATE\" as const, old: oldItem, new: newItem }),\n delete: (item) => ({ type: \"DELETE\" as const, old: item }),\n },\n {\n defaultIntervalMs: 1000,\n useBroadcastChannel: this.hybridOptions.useBroadcastChannel,\n backupPollingIntervalMs: this.hybridOptions.backupPollingIntervalMs,\n }\n );\n }\n return this.hybridManager;\n }\n\n /**\n * Subscribes to changes in the repository.\n * Uses polling since IndexedDB has no native cross-tab change notifications.\n *\n * @param callback - Function called when a change occurs\n * @param options - Optional subscription options including polling interval\n * @returns Unsubscribe function\n */\n public override subscribeToChanges(\n callback: (change: TabularChangePayload<Entity>) => void,\n options?: TabularSubscribeOptions\n ): () => void {\n // Note: We don't await setupDatabase() here to keep the method synchronous\n // The getAll() method in the hybrid manager will call setupDatabase() when needed\n const intervalMs = options?.pollingIntervalMs ?? 1000;\n const manager = this.getHybridManager();\n return manager.subscribe(callback, { intervalMs });\n }\n\n /**\n * Destroys this repository and frees up resources.\n */\n public override destroy(): void {\n if (this.hybridManager) {\n this.hybridManager.destroy();\n this.hybridManager = null;\n }\n this.db?.close();\n }\n}\n\n/**\n * Compare two values using a SearchOperator. Used by queryIndex to evaluate\n * non-equality criteria that cannot be expressed as an IDBKeyRange prefix.\n */\nfunction compareWithOperator(a: unknown, op: SearchOperator, b: unknown): boolean {\n const av = a as string | number | null | undefined;\n const bv = b as string | number;\n switch (op) {\n case \"=\":\n return av === bv;\n case \"<\":\n return av !== null && av !== undefined && av < bv;\n case \"<=\":\n return av !== null && av !== undefined && av <= bv;\n case \">\":\n return av !== null && av !== undefined && av > bv;\n case \">=\":\n return av !== null && av !== undefined && av >= bv;\n }\n}\n",
|
|
8
|
+
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { createServiceToken, deepEqual, makeFingerprint, uuid4 } from \"@workglow/util\";\nimport { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from \"@workglow/util/schema\";\nimport {\n HybridSubscriptionManager,\n BaseTabularStorage,\n ClientProvidedKeysOption,\n KeyGenerationStrategy,\n AnyTabularStorage,\n AutoGeneratedKeys,\n CoveringIndexQueryOptions,\n DeleteSearchCriteria,\n InsertEntity,\n isSearchCondition,\n QueryOptions,\n SearchCriteria,\n SearchOperator,\n SimplifyPrimaryKey,\n TabularChangePayload,\n TabularSubscribeOptions,\n pickCoveringIndex,\n type ITabularMigration,\n type ITabularMigrationApplier,\n} from \"@workglow/storage\";\nimport {\n IndexedDbTabularMigrationApplier,\n idbObjectStoreExists,\n} from \"./IndexedDbTabularMigrationApplier\";\nimport { ensureIndexedDbTable, ExpectedIndexDefinition, MigrationOptions } from \"./IndexedDbTable\";\n\nexport const IDB_TABULAR_REPOSITORY = createServiceToken<AnyTabularStorage>(\n \"storage.tabularRepository.indexedDb\"\n);\n\n/**\n * Polling change-detection comparator for the hybrid subscription manager.\n *\n * Naively comparing entities via serialized equality is correct but\n * O(size-of-entity) per poll. That falls apart for tables\n * holding large blobs — e.g. an `activities` row whose `output_data` carries\n * a multi-megabyte `Uint8ClampedArray`: each poll cycle (default 1 s) would\n * stringify ~16 MB per row × N rows, locking the main thread.\n *\n * Fast path: if both rows expose a string `updated_at`, compare just those.\n * The repositories in this codebase bump `updated_at` on every write (see\n * `ActivityRepository.updateActivity`), so the timestamp is a sufficient\n * change witness. Fall back to the structural compare for tables that don't\n * follow that convention so correctness is preserved everywhere.\n */\nfunction compareEntitiesForChange<T>(a: T, b: T): boolean {\n const au = (a as { updated_at?: unknown })?.updated_at;\n const bu = (b as { updated_at?: unknown })?.updated_at;\n if (typeof au === \"string\" && typeof bu === \"string\") {\n return au === bu;\n }\n return deepEqual(a, b);\n}\n\n/**\n * A tabular repository implementation using IndexedDB for browser-based storage.\n *\n * @template Schema - The schema definition for the entity\n * @template PrimaryKeyNames - Array of property names that form the primary key\n */\nexport class IndexedDbTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>,\n InsertType extends InsertEntity<Entity, AutoGeneratedKeys<Schema>> = InsertEntity<\n Entity,\n AutoGeneratedKeys<Schema>\n >,\n> extends BaseTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, Value, InsertType> {\n /** Promise that resolves to the IndexedDB database instance */\n private db: IDBDatabase | undefined;\n /**\n * True between `setupDatabase`'s call to `applyTabularMigrations` and its\n * completion. Backfill ops inside the orchestrator may close `this.db`\n * (via `onversionchange` from the next migration's upgrade) and trigger\n * a re-entrant `setupDatabase` call from `getDb()`. The re-entrant call\n * must only re-open the IDB connection — it MUST NOT recurse into the\n * orchestrator, or migrations will be replayed mid-flight.\n */\n private applyingMigrations = false;\n /** Promise to track ongoing database setup to prevent concurrent setup calls */\n private setupPromise: Promise<IDBDatabase> | null = null;\n /** Migration options for database schema changes */\n private migrationOptions: MigrationOptions;\n /** Shared hybrid subscription manager */\n private hybridManager: HybridSubscriptionManager<\n Entity,\n string,\n TabularChangePayload<Entity>\n > | null = null;\n /** Hybrid subscription options */\n private readonly hybridOptions: {\n readonly useBroadcastChannel: boolean;\n readonly backupPollingIntervalMs: number;\n };\n /**\n * Indexes safe for cursor-based narrowing. An IDB index excludes records\n * whose keyPath has any undefined component, so iterating an index can miss\n * records when an indexed column is optional in the schema. Native\n * `count(range)` and cursor scans are only correct over indexes whose every\n * column is required. Computed lazily on first use.\n */\n private cursorSafeIndexes: Array<Array<keyof Entity>> | undefined;\n\n /**\n * Creates a new IndexedDB-based tabular repository.\n * @param table - Name of the IndexedDB store to use.\n * @param schema - Schema defining the structure of the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param indexes - Array of columns or column arrays to make searchable. Each string or single column creates a single-column index,\n * while each array creates a compound index with columns in the specified order.\n * @param migrationOptions - Options for handling database schema migrations\n * @param clientProvidedKeys - How to handle client-provided values for auto-generated keys\n */\n constructor(\n public table: string = \"tabular_store\",\n schema: Schema,\n primaryKeyNames: PrimaryKeyNames,\n indexes: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[] = [],\n migrationOptions: MigrationOptions & {\n readonly useBroadcastChannel?: boolean;\n readonly backupPollingIntervalMs?: number;\n } = {},\n clientProvidedKeys: ClientProvidedKeysOption = \"if-missing\",\n tabularMigrations?: ReadonlyArray<ITabularMigration>\n ) {\n super(schema, primaryKeyNames, indexes, clientProvidedKeys, tabularMigrations, table);\n this.migrationOptions = migrationOptions;\n this.hybridOptions = {\n useBroadcastChannel: migrationOptions.useBroadcastChannel ?? true,\n backupPollingIntervalMs: migrationOptions.backupPollingIntervalMs ?? 5000,\n };\n }\n\n /**\n * Internal method to get the database, setting it up if needed.\n * This ensures lazy initialization of the database.\n */\n private async getDb(): Promise<IDBDatabase> {\n if (this.db) return this.db;\n await this.setupDatabase();\n return this.db!;\n }\n\n /**\n * Sets up the IndexedDB database table with the required schema and indexes.\n * Must be called before using any other methods.\n */\n public override async setupDatabase(): Promise<void> {\n if (this.db) return;\n if (this.setupPromise) {\n await this.setupPromise;\n return;\n }\n\n // Re-entrant guard: when called from inside an in-flight migration\n // (typically because a backfill's `getPage` hit a closed connection\n // after the previous migration's upgrade-tx version bump), only\n // re-open the IDB connection. Re-running `applyTabularMigrations`\n // here would replay the orchestrator and double-apply migrations.\n if (this.applyingMigrations) {\n this.setupPromise = this.performSetup();\n try {\n this.db = await this.setupPromise;\n this.rewireOnVersionChange();\n } finally {\n this.setupPromise = null;\n }\n return;\n }\n\n // Probe whether the object store already exists BEFORE performSetup\n // creates it. The orchestrator needs this signal to take the\n // mark-all-applied fast path on a brand-new DB; otherwise it would\n // see the (just-created) store and try to apply ops against the\n // already-current target schema.\n const freshTable =\n this.tabularMigrations && this.tabularMigrations.length > 0\n ? !(await this.probeObjectStoreExists())\n : false;\n\n this.setupPromise = this.performSetup();\n try {\n this.db = await this.setupPromise;\n } finally {\n this.setupPromise = null;\n }\n\n if (this.tabularMigrations && this.tabularMigrations.length > 0) {\n this.rewireOnVersionChange();\n this.applyingMigrations = true;\n try {\n await this.applyTabularMigrations({ freshTable });\n } finally {\n this.applyingMigrations = false;\n }\n // After migrations, this.db may be undefined (closed by onversionchange).\n // Reopen so the storage is ready for use.\n if (!this.db) {\n this.db = await this.performSetup();\n }\n }\n }\n\n /**\n * Wires `onversionchange` so that when the migration runner bumps the IDB\n * version (currentVersion + 1 to trigger onupgradeneeded), the current\n * open connection is both closed AND cleared from `this.db`. Without the\n * clear, getDb() would return a stale closed connection and throw\n * InvalidStateError on the next transaction attempt (e.g. during backfill).\n */\n private rewireOnVersionChange(): void {\n if (!this.db) return;\n this.db.onversionchange = () => {\n this.db?.close();\n this.db = undefined;\n };\n }\n\n /**\n * Read-only probe: does the object store already exist in the IDB\n * database? Used by `setupDatabase` to decide between the orchestrator's\n * fresh-DB fast path (mark-all-applied) and the run-pending path.\n * Delegates to the shared `idbObjectStoreExists` helper.\n */\n private async probeObjectStoreExists(): Promise<boolean> {\n return idbObjectStoreExists(this.table, this.table);\n }\n\n /**\n * Internal method to perform the actual database setup\n */\n private async performSetup(): Promise<IDBDatabase> {\n const pkColumns = super.primaryKeyColumns() as string[];\n\n // Create index definitions for both single and compound indexes\n const expectedIndexes: ExpectedIndexDefinition[] = [];\n\n for (const spec of this.indexes) {\n // Handle compound index\n const columns = spec as Array<keyof Entity>;\n // Skip if this is just the primary key or a prefix of it\n if (columns.length <= pkColumns.length) {\n const isPkPrefix = columns.every((col, idx) => col === pkColumns[idx]);\n if (isPkPrefix) continue;\n }\n\n // Create compound index name and keyPath\n const columnNames = columns.map((col) => String(col));\n const indexName = columnNames.join(\"_\");\n expectedIndexes.push({\n name: indexName,\n keyPath: columnNames.length === 1 ? columnNames[0] : columnNames,\n options: { unique: false },\n });\n }\n\n const primaryKey = pkColumns.length === 1 ? pkColumns[0] : pkColumns;\n\n // Determine if we should use autoIncrement\n // IndexedDB autoIncrement only works with single numeric keys\n const useAutoIncrement =\n this.hasAutoGeneratedKey() &&\n this.autoGeneratedKeyStrategy === \"autoincrement\" &&\n pkColumns.length === 1;\n\n // Ensure that our table is created/upgraded only if the structure (indexes) has changed.\n return await ensureIndexedDbTable(\n this.table,\n primaryKey,\n expectedIndexes,\n this.migrationOptions,\n useAutoIncrement\n );\n }\n\n public override getMigrationApplier(): ITabularMigrationApplier | null {\n // The IDB tabular storage uses `this.table` as both the database name\n // and the object store name (see `ensureIndexedDbTable`'s call to\n // `openIdb(tableName, ...)`).\n return new IndexedDbTabularMigrationApplier(this.table, this.table, {\n getPage: (req) =>\n // The applier types `cursor` as `unknown` to avoid coupling to the\n // PageCursor branded type; cast at the boundary.\n this.getPage(req as Parameters<typeof this.getPage>[0]) as Promise<{\n items: Array<Record<string, unknown>>;\n nextCursor?: unknown;\n }>,\n put: (row) => this.put(row as Parameters<typeof this.put>[0]),\n delete: (row) => this.delete(row as Parameters<typeof this.delete>[0]),\n });\n }\n\n /**\n * Generates a key value for UUID keys\n * Integer autoincrement keys are handled by IndexedDB's autoIncrement\n * @param columnName - Name of the column to generate a key for\n * @param strategy - The generation strategy to use\n * @returns The generated key value\n */\n protected override generateKeyValue(\n columnName: string,\n strategy: KeyGenerationStrategy\n ): string | number {\n if (strategy === \"uuid\") {\n return uuid4();\n }\n // autoincrement is handled by IndexedDB's autoIncrement option\n throw new Error(\n `IndexedDB autoincrement keys are generated by the database, not client-side. Column: ${columnName}`\n );\n }\n\n /**\n * Stores a row in the repository.\n * @param record - The entity to store (may be missing auto-generated keys).\n * @returns The stored entity\n * @emits put - Emitted when the value is successfully stored\n */\n async put(record: InsertType): Promise<Entity> {\n const db = await this.getDb();\n let recordToStore = record as unknown as Entity;\n\n // Handle auto-generated keys\n if (this.hasAutoGeneratedKey() && this.autoGeneratedKeyName) {\n const keyName = String(this.autoGeneratedKeyName);\n const clientProvidedValue = (record as Record<string, unknown>)[keyName];\n const hasClientValue = clientProvidedValue !== undefined && clientProvidedValue !== null;\n\n if (this.autoGeneratedKeyStrategy === \"uuid\") {\n // UUID generation - must be done client-side\n let shouldGenerate = false;\n if (this.clientProvidedKeys === \"never\") {\n shouldGenerate = true;\n } else if (this.clientProvidedKeys === \"always\") {\n if (!hasClientValue) {\n throw new Error(\n `Auto-generated key \"${keyName}\" is required when clientProvidedKeys is \"always\"`\n );\n }\n shouldGenerate = false;\n } else {\n // \"if-missing\"\n shouldGenerate = !hasClientValue;\n }\n\n if (shouldGenerate) {\n const generatedValue = this.generateKeyValue(keyName, \"uuid\");\n recordToStore = { ...record, [keyName]: generatedValue } as Entity;\n }\n } else if (this.autoGeneratedKeyStrategy === \"autoincrement\") {\n // Autoincrement handled by IndexedDB\n // If clientProvidedKeys is \"always\", require the value\n if (this.clientProvidedKeys === \"always\" && !hasClientValue) {\n throw new Error(\n `Auto-generated key \"${keyName}\" is required when clientProvidedKeys is \"always\"`\n );\n }\n // If clientProvidedKeys is \"never\", omit the key to let IDB generate\n if (this.clientProvidedKeys === \"never\") {\n const { [keyName]: _, ...rest } = record as Record<string, unknown>;\n recordToStore = rest as Entity;\n }\n // \"if-missing\": use client value if provided, omit if not\n }\n }\n\n // Merge key and value, ensuring all fields are at the root level for indexing\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(this.table, \"readwrite\");\n const store = transaction.objectStore(this.table);\n const request = store.put(recordToStore);\n request.onerror = () => {\n reject(request.error);\n };\n request.onsuccess = () => {\n // For autoincrement keys, we need to update the record with the generated key\n if (\n this.hasAutoGeneratedKey() &&\n this.autoGeneratedKeyName &&\n this.autoGeneratedKeyStrategy === \"autoincrement\"\n ) {\n const keyName = String(this.autoGeneratedKeyName);\n if (recordToStore[keyName as keyof Entity] === undefined) {\n // Get the generated key from the request result\n recordToStore = { ...recordToStore, [keyName]: request.result } as Entity;\n }\n }\n this.events.emit(\"put\", recordToStore);\n resolve(recordToStore);\n };\n transaction.oncomplete = () => {\n // Notify hybrid manager of local change\n this.hybridManager?.notifyLocalChange();\n };\n });\n }\n\n /**\n * Stores multiple rows in the repository in a bulk operation.\n * @param records - Array of entities to store (may be missing auto-generated keys).\n * @returns Array of stored entities\n * @emits put - Emitted for each record successfully stored\n */\n async putBulk(records: InsertType[]): Promise<Entity[]> {\n // Use individual put calls to ensure auto-generated keys are handled correctly\n return await Promise.all(records.map((record) => this.put(record)));\n }\n\n protected override getPrimaryKeyAsOrderedArray(key: PrimaryKey) {\n return super\n .getPrimaryKeyAsOrderedArray(key)\n .map((value) => (typeof value === \"bigint\" ? value.toString() : value));\n }\n\n private getIndexedKey(key: PrimaryKey): any {\n const keys = super\n .getPrimaryKeyAsOrderedArray(key)\n .map((value) => (typeof value === \"bigint\" ? value.toString() : value));\n return keys.length === 1 ? keys[0] : keys;\n }\n\n /**\n * Retrieves a value from the repository by its key.\n * @param key - The key object.\n * @returns The value object or undefined if not found.\n * @emits get - Emitted when the value is successfully retrieved\n */\n async get(key: PrimaryKey): Promise<Entity | undefined> {\n const db = await this.getDb();\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(this.table, \"readonly\");\n const store = transaction.objectStore(this.table);\n const request = store.get(this.getIndexedKey(key));\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n if (!request.result) {\n this.events.emit(\"get\", key, undefined);\n resolve(undefined);\n return;\n }\n this.events.emit(\"get\", key, request.result);\n resolve(request.result);\n };\n });\n }\n\n /**\n * Returns an array of all entries in the repository, with optional ordering, offset, and limit.\n * @param options - Optional ordering, limit, and offset options\n * @returns Array of all entries in the repository.\n */\n async getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined> {\n this.validateGetAllOptions(options);\n const db = await this.getDb();\n const transaction = db.transaction(this.table, \"readonly\");\n const store = transaction.objectStore(this.table);\n const request = store.getAll();\n return new Promise((resolve, reject) => {\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n let values: Entity[] = request.result;\n if (values.length === 0) {\n resolve(undefined);\n return;\n }\n\n if (options?.orderBy && options.orderBy.length > 0) {\n values.sort((a, b) => {\n for (const { column, direction } of options.orderBy!) {\n const aVal = a[column] as string | number | null | undefined;\n const bVal = b[column] as string | number | null | undefined;\n if (aVal == null && bVal == null) continue;\n if (aVal == null) return direction === \"ASC\" ? -1 : 1;\n if (bVal == null) return direction === \"ASC\" ? 1 : -1;\n if (aVal < bVal) return direction === \"ASC\" ? -1 : 1;\n if (aVal > bVal) return direction === \"ASC\" ? 1 : -1;\n }\n return 0;\n });\n }\n\n if (options?.offset !== undefined) {\n values = values.slice(options.offset);\n }\n\n if (options?.limit !== undefined) {\n values = values.slice(0, options.limit);\n }\n\n resolve(values.length > 0 ? values : undefined);\n };\n });\n }\n\n /**\n * Deletes a row from the repository.\n * @param key - The key object to delete.\n */\n async delete(key: PrimaryKey): Promise<void> {\n const db = await this.getDb();\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(this.table, \"readwrite\");\n const store = transaction.objectStore(this.table);\n const request = store.delete(this.getIndexedKey(key));\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n this.events.emit(\"delete\", key as keyof Entity);\n resolve();\n };\n transaction.oncomplete = () => {\n // Notify hybrid manager of local change\n this.hybridManager?.notifyLocalChange();\n };\n });\n }\n\n /**\n * Deletes all records from the repository.\n * @emits clearall - Emitted when all values are deleted\n */\n async deleteAll(): Promise<void> {\n const db = await this.getDb();\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(this.table, \"readwrite\");\n const store = transaction.objectStore(this.table);\n const request = store.clear();\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n this.events.emit(\"clearall\");\n resolve();\n };\n transaction.oncomplete = () => {\n // Notify hybrid manager of local change\n this.hybridManager?.notifyLocalChange();\n };\n });\n }\n\n /**\n * Returns the total number of rows in the repository.\n * @returns Count of stored items.\n */\n async size(): Promise<number> {\n const db = await this.getDb();\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(this.table, \"readonly\");\n const store = transaction.objectStore(this.table);\n const request = store.count();\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result);\n });\n }\n\n /**\n * Returns the subset of configured indexes safe to use for cursor-based\n * narrowing — those whose every column is required by the schema. IDB\n * excludes records with undefined keyPath components from indexes, so\n * iterating an index would silently miss records when any indexed column\n * is optional.\n */\n private getCursorSafeIndexes(): Array<Array<keyof Entity>> {\n if (this.cursorSafeIndexes) return this.cursorSafeIndexes;\n const required = new Set(this.schema.required ?? []);\n this.cursorSafeIndexes = this.indexes.filter((columns) =>\n columns.every((column) => required.has(String(column)))\n );\n return this.cursorSafeIndexes;\n }\n\n /**\n * Picks the best index to narrow a criteria-based scan, preferring indexes\n * whose prefix covers every criteria column (so callers can use the native\n * `count()` API) and falling back to the longest equality-prefix match.\n *\n * Upper bound uses `[]` as a sentinel that compares greater than any\n * primitive value at the next index position (per IndexedDB key ordering:\n * array > binary > string > date > number). Assumes subsequent indexed\n * columns hold primitive values — array-valued columns would compare\n * greater than `[]` and slip outside the range.\n */\n private createIndexedRange(\n store: IDBObjectStore,\n criteria: SearchCriteria<Entity>\n ):\n | {\n source: IDBObjectStore | IDBIndex;\n range: IDBKeyRange | undefined;\n coversCriteria: boolean;\n }\n | undefined {\n const criteriaColumns = Object.keys(criteria) as Array<keyof Entity>;\n if (criteriaColumns.length === 0) return undefined;\n\n let best:\n | {\n indexName: string;\n prefixValues: unknown[];\n fullMatch: boolean;\n coversCriteria: boolean;\n }\n | undefined;\n\n for (const indexColumns of this.getCursorSafeIndexes()) {\n const prefixValues: unknown[] = [];\n for (const column of indexColumns) {\n const value = this.getEqualityCriterionValue(criteria, column);\n if (value === undefined) break;\n prefixValues.push(value);\n }\n\n if (prefixValues.length === 0) continue;\n\n const indexedPrefix = indexColumns.slice(0, prefixValues.length);\n const coversCriteria = criteriaColumns.every((column) => indexedPrefix.includes(column));\n\n const better =\n !best ||\n (coversCriteria && !best.coversCriteria) ||\n (coversCriteria === best.coversCriteria && prefixValues.length > best.prefixValues.length);\n\n if (better) {\n best = {\n indexName: indexColumns.map((column) => String(column)).join(\"_\"),\n prefixValues,\n fullMatch: prefixValues.length === indexColumns.length,\n coversCriteria,\n };\n }\n }\n\n if (!best) return undefined;\n\n const range = best.fullMatch\n ? IDBKeyRange.only(best.prefixValues.length === 1 ? best.prefixValues[0] : best.prefixValues)\n : IDBKeyRange.bound(best.prefixValues, [...best.prefixValues, []]);\n\n return {\n source: store.index(best.indexName),\n range,\n coversCriteria: best.coversCriteria,\n };\n }\n\n /**\n * Counts rows matching the specified search criteria.\n *\n * Uses the native `count()` API when an index prefix covers every criteria\n * column. Otherwise narrows via the longest matching index prefix and\n * filters the remaining columns during cursor iteration. Falls back to a\n * full store scan only when no index applies.\n */\n override async count(criteria?: SearchCriteria<Entity>): Promise<number> {\n if (!criteria || Object.keys(criteria).length === 0) {\n return await this.size();\n }\n\n this.validateQueryParams(criteria);\n const db = await this.getDb();\n\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(this.table, \"readonly\");\n const store = transaction.objectStore(this.table);\n const plan = this.createIndexedRange(store, criteria);\n\n if (plan?.coversCriteria) {\n const request = plan.source.count(plan.range);\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result);\n return;\n }\n\n const source = plan?.source ?? store;\n const range = plan?.range;\n let count = 0;\n const request = source.openCursor(range);\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n const cursor = request.result;\n if (!cursor) {\n resolve(count);\n return;\n }\n if (this.matchesCriteria(cursor.value as Entity, criteria)) {\n count += 1;\n }\n cursor.continue();\n };\n });\n }\n\n /**\n * Fetches a page of records from the repository.\n * @param offset - Number of records to skip\n * @param limit - Maximum number of records to return\n * @returns Array of entities or undefined if no records found\n */\n async getBulk(offset: number, limit: number): Promise<Entity[] | undefined> {\n if (offset < 0) {\n throw new RangeError(`offset must be non-negative, got ${offset}`);\n }\n if (limit <= 0) {\n return undefined;\n }\n\n const db = await this.getDb();\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(this.table, \"readonly\");\n const store = transaction.objectStore(this.table);\n const request = store.openCursor();\n const entities: Entity[] = [];\n let skipped = false;\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n const cursor = request.result;\n if (cursor) {\n // Skip to offset using advance\n if (!skipped && offset > 0) {\n skipped = true;\n cursor.advance(offset);\n return;\n }\n\n // Collect records up to the limit\n entities.push(cursor.value);\n if (entities.length === limit) {\n resolve(entities);\n return;\n }\n cursor.continue();\n } else {\n // No more records\n resolve(entities.length > 0 ? entities : undefined);\n }\n };\n });\n }\n\n /**\n * Checks if a record matches all criteria conditions.\n * @param record - The record to check\n * @param criteria - The search criteria\n * @returns true if all conditions match\n */\n private matchesCriteria(record: Entity, criteria: DeleteSearchCriteria<Entity>): boolean {\n for (const column of Object.keys(criteria) as Array<keyof Entity>) {\n const criterion = criteria[column];\n const recordValue = record[column];\n\n let operator: SearchOperator = \"=\";\n let value: Entity[keyof Entity];\n\n if (isSearchCondition(criterion)) {\n operator = criterion.operator;\n value = criterion.value as Entity[keyof Entity];\n } else {\n value = criterion as Entity[keyof Entity];\n }\n\n // Skip null values for comparison operators\n if (operator !== \"=\" && (recordValue === null || recordValue === undefined)) {\n return false;\n }\n\n switch (operator) {\n case \"=\":\n if (recordValue !== value) return false;\n break;\n case \"<\":\n if (!(recordValue < value)) return false;\n break;\n case \"<=\":\n if (!(recordValue <= value)) return false;\n break;\n case \">\":\n if (!(recordValue > value)) return false;\n break;\n case \">=\":\n if (!(recordValue >= value)) return false;\n break;\n default:\n return false;\n }\n }\n return true;\n }\n\n /**\n * Deletes all entries matching the specified search criteria.\n * Supports multiple columns with optional comparison operators.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n */\n async deleteSearch(criteria: DeleteSearchCriteria<Entity>): Promise<void> {\n const criteriaKeys = Object.keys(criteria) as Array<keyof Entity>;\n if (criteriaKeys.length === 0) {\n return;\n }\n\n const db = await this.getDb();\n\n return new Promise(async (resolve, reject) => {\n try {\n const transaction = db.transaction(this.table, \"readwrite\");\n const store = transaction.objectStore(this.table);\n\n // Set up transaction event handlers\n transaction.oncomplete = () => {\n this.events.emit(\"delete\", criteriaKeys[0] as keyof Entity);\n // Notify hybrid manager of local change\n this.hybridManager?.notifyLocalChange();\n resolve();\n };\n\n transaction.onerror = () => {\n reject(transaction.error);\n };\n\n // Get all records and filter\n const getAllRequest = store.getAll();\n\n getAllRequest.onsuccess = () => {\n const allRecords: Entity[] = getAllRequest.result;\n\n // Filter records that match all criteria\n const recordsToDelete = allRecords.filter((record) =>\n this.matchesCriteria(record, criteria)\n );\n\n if (recordsToDelete.length === 0) {\n // No records to delete\n return;\n }\n\n // Delete each record that matches the criteria\n for (const record of recordsToDelete) {\n // Extract the primary key from the record\n const primaryKey = this.primaryKeyColumns().reduce((key, col) => {\n // @ts-ignore - We know these properties exist on the record\n key[col] = record[col];\n return key;\n }, {} as PrimaryKey);\n\n // Delete the record using the primary key\n const request = store.delete(this.getIndexedKey(primaryKey));\n\n request.onerror = () => {\n console.error(\"Error deleting record:\", request.error);\n };\n }\n };\n\n getAllRequest.onerror = () => {\n reject(getAllRequest.error);\n };\n } catch (error) {\n reject(error);\n }\n });\n }\n\n private getEqualityCriterionValue(\n criteria: SearchCriteria<Entity>,\n column: keyof Entity\n ): Entity[keyof Entity] | undefined {\n const criterion = criteria[column];\n if (criterion === undefined) return undefined;\n if (isSearchCondition(criterion)) {\n return criterion.operator === \"=\" ? (criterion.value as Entity[keyof Entity]) : undefined;\n }\n return criterion as Entity[keyof Entity];\n }\n\n private compareByOrder(a: Entity, b: Entity, options?: QueryOptions<Entity>): number {\n if (!options?.orderBy) return 0;\n for (const { column, direction } of options.orderBy) {\n const aVal = a[column] as string | number | null | undefined;\n const bVal = b[column] as string | number | null | undefined;\n if (aVal == null && bVal == null) continue;\n if (aVal == null) return direction === \"ASC\" ? -1 : 1;\n if (bVal == null) return direction === \"ASC\" ? 1 : -1;\n if (aVal < bVal) return direction === \"ASC\" ? -1 : 1;\n if (aVal > bVal) return direction === \"ASC\" ? 1 : -1;\n }\n return 0;\n }\n\n private createIndexedQuery(\n store: IDBObjectStore,\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): {\n source: IDBObjectStore | IDBIndex;\n range: IDBKeyRange | undefined;\n direction: IDBCursorDirection;\n satisfiesOrder: boolean;\n appliedLimit: boolean;\n appliedOffset: boolean;\n skipRemaining: number;\n } {\n const orderBy = options?.orderBy ?? [];\n let best:\n | {\n indexName: string;\n prefixValues: unknown[];\n fullMatch: boolean;\n satisfiesOrder: boolean;\n direction: IDBCursorDirection;\n }\n | undefined;\n\n for (const indexColumns of this.getCursorSafeIndexes()) {\n const prefixValues: unknown[] = [];\n for (const column of indexColumns) {\n const value = this.getEqualityCriterionValue(criteria, column);\n if (value === undefined) break;\n prefixValues.push(value);\n }\n\n if (prefixValues.length === 0) continue;\n\n const remainingColumns = indexColumns.slice(prefixValues.length);\n let redundantOrderPrefixLength = 0;\n while (\n redundantOrderPrefixLength < orderBy.length &&\n redundantOrderPrefixLength < prefixValues.length &&\n orderBy[redundantOrderPrefixLength]?.column === indexColumns[redundantOrderPrefixLength]\n ) {\n redundantOrderPrefixLength++;\n }\n const normalizedOrderBy = orderBy.slice(redundantOrderPrefixLength);\n const satisfiesOrder =\n normalizedOrderBy.length === 0 ||\n (normalizedOrderBy.length <= remainingColumns.length &&\n normalizedOrderBy.every((order, index) => order.column === remainingColumns[index]) &&\n orderBy.every((order) => order.direction === orderBy[0]?.direction));\n\n if (!satisfiesOrder && best) continue;\n\n if (\n !best ||\n (satisfiesOrder && !best.satisfiesOrder) ||\n prefixValues.length > best.prefixValues.length\n ) {\n best = {\n indexName: indexColumns.map((column) => String(column)).join(\"_\"),\n prefixValues,\n fullMatch: prefixValues.length === indexColumns.length,\n satisfiesOrder,\n direction: orderBy[0]?.direction === \"DESC\" ? \"prev\" : \"next\",\n };\n }\n }\n\n const appliedLimit = Boolean(best?.satisfiesOrder && options?.limit !== undefined);\n const appliedOffset = Boolean(best?.satisfiesOrder && options?.offset !== undefined);\n\n if (!best) {\n return {\n source: store,\n range: undefined,\n direction: orderBy[0]?.direction === \"DESC\" ? \"prev\" : \"next\",\n satisfiesOrder: false,\n appliedLimit: false,\n appliedOffset: false,\n skipRemaining: 0,\n };\n }\n\n const source = store.index(best.indexName);\n // See createIndexedRange for the `[]` upper-bound sentinel rationale.\n const keyRange = best.fullMatch\n ? IDBKeyRange.only(best.prefixValues.length === 1 ? best.prefixValues[0] : best.prefixValues)\n : IDBKeyRange.bound(best.prefixValues, [...best.prefixValues, []]);\n\n return {\n source,\n range: keyRange,\n direction: best.direction,\n satisfiesOrder: best.satisfiesOrder,\n appliedLimit,\n appliedOffset,\n skipRemaining: appliedOffset ? (options?.offset ?? 0) : 0,\n };\n }\n\n /**\n * Queries entries matching the specified search criteria with optional ordering, limit, and offset.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @param options - Optional ordering, limit, and offset options\n * @returns Array of matching entities or undefined if no matches found\n */\n async query(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined> {\n this.validateQueryParams(criteria, options);\n const db = await this.getDb();\n\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(this.table, \"readonly\");\n const store = transaction.objectStore(this.table);\n const indexedQuery = this.createIndexedQuery(store, criteria, options);\n const results: Entity[] = [];\n const request = indexedQuery.source.openCursor(indexedQuery.range, indexedQuery.direction);\n\n request.onsuccess = () => {\n const cursor = request.result;\n if (!cursor) {\n let finalResults = results;\n\n if (!indexedQuery.satisfiesOrder && options?.orderBy && options.orderBy.length > 0) {\n finalResults = [...finalResults].sort((a, b) => this.compareByOrder(a, b, options));\n }\n\n if (!indexedQuery.appliedOffset && options?.offset !== undefined) {\n finalResults = finalResults.slice(options.offset);\n }\n\n if (!indexedQuery.appliedLimit && options?.limit !== undefined) {\n finalResults = finalResults.slice(0, options.limit);\n }\n\n const result = finalResults.length > 0 ? finalResults : undefined;\n this.events.emit(\"query\", criteria as Partial<Entity>, result);\n resolve(result);\n return;\n }\n\n const record = cursor.value as Entity;\n if (this.matchesCriteria(record, criteria)) {\n if (indexedQuery.skipRemaining > 0) {\n indexedQuery.skipRemaining -= 1;\n } else {\n results.push(record);\n if (indexedQuery.appliedLimit && results.length === options?.limit) {\n const result = results.length > 0 ? results : undefined;\n this.events.emit(\"query\", criteria as Partial<Entity>, result);\n resolve(result);\n return;\n }\n }\n }\n\n cursor.continue();\n };\n\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Strict, projected query served entirely by a covering compound index.\n * Uses `openKeyCursor` — never reads `cursor.value` — so only the index key\n * bytes are loaded into memory. Ideal for tables with large value blobs.\n *\n * Throws {@link CoveringIndexMissingError} when no registered index can serve\n * the request (i.e. the index does not cover all select + orderBy columns).\n */\n override async queryIndex<K extends keyof Entity & string>(\n criteria: SearchCriteria<Entity>,\n options: CoveringIndexQueryOptions<Entity, K>\n ): Promise<Pick<Entity, K>[]> {\n this.validateSelect(options);\n this.validateQueryParams(criteria, options);\n\n const registered = this.indexes.map((cols) => {\n const cs = Array.isArray(cols) ? (cols as string[]) : [cols as string];\n return { name: cs.join(\"_\"), keyPath: cs };\n });\n\n const picked = pickCoveringIndex({\n table: this.table,\n indexes: registered,\n criteriaColumns: Object.keys(criteria),\n orderByColumns: (options.orderBy ?? []).map((o) => ({\n column: String(o.column),\n direction: o.direction,\n })),\n selectColumns: options.select.map(String),\n primaryKeyColumns: this.primaryKeyColumns().map(String),\n });\n\n const db = await this.getDb();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(this.table, \"readonly\");\n const store = tx.objectStore(this.table);\n const idx = store.index(picked.name);\n\n // Build the equality prefix key range from criteria values.\n // Only push columns whose criterion is a direct equality value (plain value or operator \"=\").\n // Any non-equality operator (>, <, etc.) breaks the prefix; the existing in-cursor\n // compareWithOperator filter below handles those columns.\n const prefix: unknown[] = [];\n for (const col of picked.keyPath) {\n const c = (criteria as Record<string, unknown>)[col];\n if (c === undefined && !(col in (criteria as Record<string, unknown>))) break;\n if (isSearchCondition(c)) {\n if (c.operator !== \"=\") break;\n prefix.push(c.value);\n } else {\n prefix.push(c);\n }\n }\n // Use IDBKeyRange.bound with [] upper sentinel for prefix scans on compound keys.\n // `[...prefix, []]` is greater than any key starting with prefix in IDB's comparison.\n const range =\n prefix.length === 0\n ? undefined\n : prefix.length === picked.keyPath.length\n ? IDBKeyRange.only(prefix.length === 1 ? prefix[0] : prefix)\n : IDBKeyRange.bound(prefix, [...prefix, []]);\n\n const direction: IDBCursorDirection = picked.reverseDirection ? \"prev\" : \"next\";\n // openKeyCursor — never reads cursor.value, only the index key and primaryKey\n const request = idx.openKeyCursor(range, direction);\n const out: Pick<Entity, K>[] = [];\n let toSkip = options.offset ?? 0;\n\n // Precompute lookup maps for the cursor loop (O(keyPath) once, not per-row)\n const keyPathPositions = new Map<string, number>();\n picked.keyPath.forEach((col, i) => keyPathPositions.set(col, i));\n\n const pkCols = this.primaryKeyColumns().map(String);\n const pkPositions = new Map<string, number>();\n pkCols.forEach((col, i) => pkPositions.set(col, i));\n\n request.onsuccess = () => {\n const cursor = request.result;\n if (!cursor) {\n resolve(out);\n return;\n }\n\n const key = cursor.key;\n\n // Decode projected columns from the index key array and primaryKey\n const row = {} as Record<string, unknown>;\n for (const col of options.select) {\n const colStr = String(col);\n const pos = keyPathPositions.get(colStr);\n if (pos !== undefined) {\n // Column is in the index key path — extract from key array\n row[colStr] = Array.isArray(key) ? key[pos] : key;\n } else {\n // Column must be a primary key column — use cursor.primaryKey\n if (pkCols.length === 1 && colStr === pkCols[0]) {\n row[colStr] = cursor.primaryKey;\n } else {\n const pkPos = pkPositions.get(colStr);\n if (pkPos !== undefined) {\n row[colStr] = Array.isArray(cursor.primaryKey)\n ? (cursor.primaryKey as unknown[])[pkPos]\n : cursor.primaryKey;\n }\n }\n }\n }\n\n // Apply criteria for index positions beyond the equality prefix.\n // Positions [0, prefix.length) are guaranteed-equal by the IDBKeyRange,\n // so they don't need re-checking. Positions >= prefix.length must be\n // filtered here — this includes plain-value equalities trailing a\n // non-equality break (criteria like `{a:1, b:>=5, c:3}` with prefix=[1]\n // need an in-cursor `c === 3` check) and any SearchCondition.\n let matches = true;\n for (const [col, crit] of Object.entries(criteria as Record<string, unknown>)) {\n const pos = keyPathPositions.get(col);\n if (pos === undefined) continue;\n if (pos < prefix.length) continue; // covered by IDBKeyRange\n const valFromKey = Array.isArray(key) ? (key as unknown[])[pos] : key;\n const op: SearchOperator = isSearchCondition(crit) ? crit.operator : \"=\";\n const val = isSearchCondition(crit) ? crit.value : crit;\n if (!compareWithOperator(valFromKey, op, val)) {\n matches = false;\n break;\n }\n }\n\n if (matches) {\n if (toSkip > 0) {\n toSkip -= 1;\n } else {\n out.push(row as Pick<Entity, K>);\n if (options.limit !== undefined && out.length >= options.limit) {\n resolve(out);\n return;\n }\n }\n }\n\n cursor.continue();\n };\n\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Gets or creates the shared hybrid subscription manager.\n * This ensures all subscriptions share a single manager.\n */\n private getHybridManager(): HybridSubscriptionManager<\n Entity,\n string,\n TabularChangePayload<Entity>\n > {\n if (!this.hybridManager) {\n // Generate unique channel name based on table name\n const channelName = `indexeddb-tabular-${this.table}`;\n\n this.hybridManager = new HybridSubscriptionManager<\n Entity,\n string,\n TabularChangePayload<Entity>\n >(\n channelName,\n async () => {\n // Fetch all entities and create a map keyed by entity fingerprint\n const entities = (await this.getAll()) || [];\n const map = new Map<string, Entity>();\n for (const entity of entities) {\n const { key } = this.separateKeyValueFromCombined(entity);\n const fingerprint = await makeFingerprint(key);\n map.set(fingerprint, entity);\n }\n return map;\n },\n compareEntitiesForChange,\n {\n insert: (item) => ({ type: \"INSERT\" as const, new: item }),\n update: (oldItem, newItem) => ({ type: \"UPDATE\" as const, old: oldItem, new: newItem }),\n delete: (item) => ({ type: \"DELETE\" as const, old: item }),\n },\n {\n defaultIntervalMs: 1000,\n useBroadcastChannel: this.hybridOptions.useBroadcastChannel,\n backupPollingIntervalMs: this.hybridOptions.backupPollingIntervalMs,\n }\n );\n }\n return this.hybridManager;\n }\n\n /**\n * Subscribes to changes in the repository.\n * Uses polling since IndexedDB has no native cross-tab change notifications.\n *\n * @param callback - Function called when a change occurs\n * @param options - Optional subscription options including polling interval\n * @returns Unsubscribe function\n */\n public override subscribeToChanges(\n callback: (change: TabularChangePayload<Entity>) => void,\n options?: TabularSubscribeOptions\n ): () => void {\n // Note: We don't await setupDatabase() here to keep the method synchronous\n // The getAll() method in the hybrid manager will call setupDatabase() when needed\n const intervalMs = options?.pollingIntervalMs ?? 1000;\n const manager = this.getHybridManager();\n return manager.subscribe(callback, { intervalMs });\n }\n\n /**\n * Destroys this repository and frees up resources.\n */\n public override destroy(): void {\n if (this.hybridManager) {\n this.hybridManager.destroy();\n this.hybridManager = null;\n }\n this.db?.close();\n }\n}\n\n/**\n * Compare two values using a SearchOperator. Used by queryIndex to evaluate\n * non-equality criteria that cannot be expressed as an IDBKeyRange prefix.\n */\nfunction compareWithOperator(a: unknown, op: SearchOperator, b: unknown): boolean {\n const av = a as string | number | null | undefined;\n const bv = b as string | number;\n switch (op) {\n case \"=\":\n return av === bv;\n case \"<\":\n return av !== null && av !== undefined && av < bv;\n case \"<=\":\n return av !== null && av !== undefined && av <= bv;\n case \">\":\n return av !== null && av !== undefined && av > bv;\n case \">=\":\n return av !== null && av !== undefined && av >= bv;\n }\n}\n",
|
|
9
|
+
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport {\n type IMigration,\n type IMigrationRunner,\n type RunMigrationsOptions,\n MIGRATIONS_TABLE,\n sortMigrations,\n} from \"@workglow/storage\";\n\n/**\n * Context handed to an IndexedDB migration's `up()` body. IDB's spec only\n * permits schema changes (createObjectStore, createIndex, etc.) inside an\n * `onupgradeneeded` callback, so migrations receive both the {@link IDBDatabase}\n * and the version-change {@link IDBTransaction} active during that callback.\n *\n * `up()` MUST be synchronous — IDB upgrade transactions auto-commit as soon as\n * the callback returns control to the event loop, so any awaited Promise\n * between IDB requests would silently lose the rest of the migration.\n */\nexport interface IndexedDbUpgradeContext {\n readonly db: IDBDatabase;\n readonly tx: IDBTransaction;\n readonly oldVersion: number;\n readonly newVersion: number;\n}\n\n/** Convenience alias for IndexedDB-flavoured migrations. */\nexport type IndexedDbMigration = IMigration<IndexedDbUpgradeContext>;\n\nfunction getIndexedDb(): IDBFactory {\n // Node test environments (vitest + fake-indexeddb) install a global; browsers\n // expose it natively. We don't pick a fallback because callers in unsupported\n // runtimes (Bun without polyfill, Node without setup) need a clear failure.\n const idb = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!idb) {\n throw new Error(\n \"indexedDB is not available in this environment. Provide one via the IndexedDbMigrationRunner constructor or polyfill globalThis.indexedDB.\"\n );\n }\n return idb;\n}\n\n/**\n * Thrown when an in-flight migration is interrupted by another tab opening\n * the same database at a higher version. Surfaces as the `run()` rejection\n * so callers can distinguish a tab-coordination failure from a programming\n * error in `up()`.\n */\nexport class MigrationAbortedByOtherTabError extends Error {\n constructor(dbName: string) {\n super(`IndexedDB ${dbName} migration aborted: another tab requested a higher version`);\n this.name = \"MigrationAbortedByOtherTabError\";\n }\n}\n\n/**\n * Per-DB serialization for `run()`. IDB's open-with-higher-version semantics\n * mean two concurrent migrations of the same database can deadlock each other\n * (each holds an open connection that blocks the other's upgrade). We\n * serialize at the JS layer per-dbName so back-to-back `runIndexedDbMigrationGroups`\n * calls (which construct fresh runner instances) cooperate.\n *\n * Module-scoped so it spans runner instances; safe because the only state is\n * a Promise chain keyed by `dbName`.\n */\nconst RUN_LOCKS = new Map<string, Promise<unknown>>();\n\n/**\n * Runs versioned migrations against a single IndexedDB database.\n *\n * Bookkeeping-driven dispatch (NOT IDB-version-ordinal driven). The runner:\n * 1. Probes the DB to read the `_storage_migrations` object store, building\n * a set of already-applied `(component, version)` pairs. The probe\n * registers an `onupgradeneeded` handler so that if the DB doesn't yet\n * exist, IDB's auto-creation path also creates the bookkeeping store\n * atomically (rather than leaving an empty v1 DB with no schema).\n * 2. Computes the pending migrations as `sorted \\ alreadyApplied` —\n * identifying each migration by `(component, version)`, NOT by its\n * position in the sorted array. This means inserting a new migration\n * that sorts BEFORE existing ones (e.g. a new component name that's\n * lexicographically earlier) is still correctly detected as pending.\n * 3. If pending is empty, returns without bumping the IDB version.\n * 4. Otherwise reopens at `currentVersion + 1` and runs every pending\n * migration inside the upgrade transaction, recording each in the\n * bookkeeping store as it goes.\n *\n * Concurrent `run()` calls against the same dbName (across instances) are\n * serialized via {@link RUN_LOCKS} so that two callers don't deadlock each\n * other on the open-at-higher-version handshake.\n *\n * The IDB database version is treated purely as a monotonic trigger for\n * `onupgradeneeded`; we never use it to decide which migrations to apply.\n *\n * Caveats:\n * - `up()` must be synchronous. IDB upgrade transactions auto-commit as\n * soon as control returns to the event loop, so awaited Promises between\n * IDB requests would silently lose the rest of the migration. The runner\n * throws if `up()` returns a Promise.\n */\nexport class IndexedDbMigrationRunner implements IMigrationRunner<IndexedDbUpgradeContext> {\n constructor(\n private readonly dbName: string,\n private readonly idb: IDBFactory = getIndexedDb()\n ) {}\n\n /**\n * Probe the DB without forcing a schema change. Reads the bookkeeping\n * store (creating it on first run via the auto-fired upgrade callback)\n * and returns the current IDB version + the set of applied migrations\n * keyed by `${component}@${version}`.\n */\n private async probe(): Promise<{ currentVersion: number; applied: Set<string> }> {\n return new Promise((resolve, reject) => {\n // Multiple IDB callbacks (onupgradeneeded, onsuccess + nested getAll\n // success/error, onerror, onblocked) can fire in surprising orders —\n // e.g. an orphaned `onerror` after we've already returned a result.\n // Gate every settlement through `finalize` so the Promise resolves or\n // rejects exactly once, and so the open connection is closed exactly\n // once on the way out.\n let settled = false;\n const finalize = (\n db: IDBDatabase | undefined,\n outcome:\n | { ok: true; value: { currentVersion: number; applied: Set<string> } }\n | { ok: false; error: unknown }\n ): void => {\n if (settled) return;\n settled = true;\n if (db) {\n try {\n db.close();\n } catch {\n // ignore — close is best-effort\n }\n }\n if (outcome.ok) resolve(outcome.value);\n else reject(outcome.error);\n };\n\n const req = this.idb.open(this.dbName);\n req.onupgradeneeded = () => {\n if (settled) return;\n // Fires only when the DB doesn't already exist (oldVersion = 0).\n // We seed the bookkeeping store atomically with DB creation so a\n // fresh DB never lacks it.\n const u = req.result;\n if (!u.objectStoreNames.contains(MIGRATIONS_TABLE)) {\n u.createObjectStore(MIGRATIONS_TABLE, { keyPath: [\"component\", \"version\"] });\n }\n };\n req.onsuccess = () => {\n if (settled) return;\n const db = req.result;\n const currentVersion = db.version;\n if (!db.objectStoreNames.contains(MIGRATIONS_TABLE)) {\n // Existed prior to this runner ever being used — no bookkeeping yet.\n finalize(db, { ok: true, value: { currentVersion, applied: new Set() } });\n return;\n }\n const tx = db.transaction(MIGRATIONS_TABLE, \"readonly\");\n const store = tx.objectStore(MIGRATIONS_TABLE);\n const getAll = store.getAll();\n getAll.onsuccess = () => {\n if (settled) return;\n const rows = getAll.result as Array<{ component: string; version: number }>;\n finalize(db, {\n ok: true,\n value: {\n currentVersion,\n applied: new Set(rows.map((r) => `${r.component}@${r.version}`)),\n },\n });\n };\n getAll.onerror = () => {\n if (settled) return;\n finalize(db, { ok: false, error: getAll.error });\n };\n };\n req.onerror = () => {\n if (settled) return;\n finalize(undefined, { ok: false, error: req.error });\n };\n req.onblocked = () => {\n if (settled) return;\n finalize(undefined, {\n ok: false,\n error: new Error(`IndexedDB ${this.dbName} blocked while probing for bookkeeping store`),\n });\n };\n });\n }\n\n /** Ensures the bookkeeping object store exists (created lazily by {@link probe}). */\n async ensureBookkeepingTable(): Promise<void> {\n await this.probe();\n }\n\n async appliedVersions(component: string): Promise<Set<number>> {\n const { applied } = await this.probe();\n const versions = new Set<number>();\n for (const key of applied) {\n const at = key.lastIndexOf(\"@\");\n if (at < 0) continue;\n const c = key.slice(0, at);\n const v = Number(key.slice(at + 1));\n if (c === component && Number.isFinite(v)) versions.add(v);\n }\n return versions;\n }\n\n async run(\n migrations: ReadonlyArray<IndexedDbMigration>,\n options: RunMigrationsOptions = {}\n ): Promise<ReadonlyArray<IndexedDbMigration>> {\n const prev = RUN_LOCKS.get(this.dbName) ?? Promise.resolve();\n const next = prev.catch(() => undefined).then(() => this.runLocked(migrations, options));\n RUN_LOCKS.set(this.dbName, next);\n try {\n return await next;\n } finally {\n if (RUN_LOCKS.get(this.dbName) === next) RUN_LOCKS.delete(this.dbName);\n }\n }\n\n /**\n * Body of `run()` that executes under the per-dbName lock. Probes the\n * bookkeeping store *after* acquiring the lock so each serialized caller\n * sees rows committed by the previous one — without re-probing, a queued\n * caller would re-run already-applied migrations from a stale snapshot.\n */\n private async runLocked(\n migrations: ReadonlyArray<IndexedDbMigration>,\n options: RunMigrationsOptions\n ): Promise<ReadonlyArray<IndexedDbMigration>> {\n const sorted = sortMigrations(migrations);\n if (sorted.length === 0) return [];\n\n const { currentVersion, applied: alreadyApplied } = await this.probe();\n const pending = sorted.filter((m) => !alreadyApplied.has(`${m.component}@${m.version}`));\n if (pending.length === 0) return [];\n\n // Bump the IDB version monotonically — its only role is to trigger\n // onupgradeneeded. The bookkeeping store decides what actually runs.\n const targetVersion = currentVersion + 1;\n const applied: IndexedDbMigration[] = [];\n // Buffer events emitted from inside `onupgradeneeded` and flush after the\n // open request settles. Calling user code (the listener) inside the IDB\n // upgrade transaction can let microtasks slip in and auto-commit the\n // transaction before our migrations are done.\n const onProgress = options.onProgress;\n const buffered: Parameters<NonNullable<typeof onProgress>>[0][] = [];\n const emitLater = onProgress\n ? (ev: Parameters<NonNullable<typeof onProgress>>[0]) => buffered.push(ev)\n : undefined;\n\n await new Promise<void>((resolve, reject) => {\n // Same orphan-callback hazard as `probe()`, plus an extra one: aborting\n // the upgrade transaction in our catch path queues an `onerror` that\n // would otherwise re-reject after we've already settled with the real\n // cause. Funnel everything through `finalize`.\n let settled = false;\n let upgradeDb: IDBDatabase | undefined;\n const finalize = (outcome: { ok: true } | { ok: false; error: unknown }): void => {\n if (settled) return;\n settled = true;\n if (upgradeDb) {\n try {\n upgradeDb.close();\n } catch {\n // ignore — close is best-effort\n }\n }\n if (outcome.ok) resolve();\n else reject(outcome.error);\n };\n\n const upreq = this.idb.open(this.dbName, targetVersion);\n upreq.onupgradeneeded = (ev) => {\n if (settled) return;\n try {\n const db = upreq.result;\n upgradeDb = db;\n const tx = upreq.transaction!;\n const oldVersion = ev.oldVersion;\n const newVersion = ev.newVersion ?? targetVersion;\n\n // Another tab opening at a still-higher version mid-upgrade: abort\n // our work and surface a typed error rather than blocking forever.\n db.onversionchange = () => {\n try {\n tx.abort();\n } catch {\n // ignore — tx may already be settling\n }\n finalize({ ok: false, error: new MigrationAbortedByOtherTabError(this.dbName) });\n };\n\n if (!db.objectStoreNames.contains(MIGRATIONS_TABLE)) {\n db.createObjectStore(MIGRATIONS_TABLE, { keyPath: [\"component\", \"version\"] });\n }\n const meta = tx.objectStore(MIGRATIONS_TABLE);\n\n for (const m of pending) {\n emitLater?.({\n component: m.component,\n version: m.version,\n phase: \"starting\",\n description: m.description,\n });\n const ctx: IndexedDbUpgradeContext = { db, tx, oldVersion, newVersion };\n const result = m.up(ctx, (fraction) => {\n emitLater?.({\n component: m.component,\n version: m.version,\n phase: \"running\",\n description: m.description,\n fraction,\n });\n });\n if (result instanceof Promise) {\n throw new Error(\n `IndexedDB migration \"${m.component}@${m.version}\" returned a Promise; ` +\n `IDB upgrade transactions cannot span async work.`\n );\n }\n meta.add({\n component: m.component,\n version: m.version,\n description: m.description ?? null,\n applied_at: new Date().toISOString(),\n });\n applied.push(m);\n emitLater?.({\n component: m.component,\n version: m.version,\n phase: \"completed\",\n description: m.description,\n fraction: 1,\n });\n }\n } catch (err) {\n // Force the open request to fail by aborting the upgrade transaction.\n // The orphan `onerror` it triggers will see `settled` and bail.\n try {\n upreq.transaction?.abort();\n } catch {\n // ignore\n }\n // Tag which migration failed (best-effort: the one whose `starting`\n // we last buffered without a matching `completed`).\n const lastStart = [...buffered].reverse().find((e) => e.phase === \"starting\");\n if (lastStart) {\n emitLater?.({\n component: lastStart.component,\n version: lastStart.version,\n phase: \"failed\",\n description: lastStart.description,\n error: err,\n });\n }\n finalize({ ok: false, error: err });\n }\n };\n upreq.onsuccess = () => {\n if (settled) return;\n upgradeDb = upreq.result;\n finalize({ ok: true });\n };\n upreq.onerror = () => {\n if (settled) return;\n finalize({ ok: false, error: upreq.error });\n };\n upreq.onblocked = () => {\n if (settled) return;\n finalize({\n ok: false,\n error: new Error(`IndexedDB ${this.dbName} upgrade blocked — close other tabs.`),\n });\n };\n }).finally(() => {\n // Flush buffered events after the upgrade settles, regardless of\n // success/failure. Listeners always see a consistent \"starting →\n // {completed | failed}\" sequence.\n if (onProgress) {\n for (const ev of buffered) onProgress(ev);\n }\n });\n\n return applied;\n }\n}\n\n/**\n * Convenience helper used by storage classes — runs the supplied migration\n * groups (one per IDB database) against fresh runners and returns an array\n * of the applied migrations across all groups.\n *\n * IndexedDB groups exist because some storages (e.g. the rate limiter) span\n * multiple databases and expose them as separate `(dbName, migrations)` pairs\n * via `getMigrations()`.\n */\nexport interface IndexedDbMigrationGroup {\n readonly dbName: string;\n readonly migrations: ReadonlyArray<IndexedDbMigration>;\n}\n\nexport async function runIndexedDbMigrationGroups(\n groups: ReadonlyArray<IndexedDbMigrationGroup>,\n options: RunMigrationsOptions & { idb?: IDBFactory } = {}\n): Promise<ReadonlyArray<IndexedDbMigration>> {\n const { idb, onProgress } = options;\n const all: IndexedDbMigration[] = [];\n for (const group of groups) {\n const runner = idb\n ? new IndexedDbMigrationRunner(group.dbName, idb)\n : new IndexedDbMigrationRunner(group.dbName);\n const applied = await runner.run(group.migrations, { onProgress });\n all.push(...applied);\n }\n return all;\n}\n",
|
|
10
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { type ITabularMigrationApplier, type TabularMigrationOp } from \"@workglow/storage\";\nimport {\n IndexedDbMigrationRunner,\n type IndexedDbUpgradeContext,\n} from \"../migrations/IndexedDbMigrationRunner\";\n\ninterface BackfillCapableStorage {\n getPage: (req?: { limit?: number; cursor?: unknown }) => Promise<{\n items: Array<Record<string, unknown>>;\n nextCursor?: unknown;\n }>;\n put: (row: Record<string, unknown>) => Promise<unknown>;\n delete: (row: Record<string, unknown>) => Promise<unknown>;\n}\n\n/**\n * Read-only existence probe shared by `IndexedDbTabularMigrationApplier.tableExists`\n * and `IndexedDbTabularStorage.probeObjectStoreExists`. Opens the IDB\n * database without a version (which is the only documented way to read the\n * existing version + object stores) and inspects `objectStoreNames`.\n *\n * **Side effect:** if the database does not yet exist, IDB's spec says\n * `open(name)` *creates* it at version 1 with no object stores. The caller\n * must be prepared to handle that — `ensureIndexedDbTable` already does, by\n * deleting and recreating the empty DB before bumping its version.\n */\nexport async function idbObjectStoreExists(dbName: string, storeName: string): Promise<boolean> {\n const idb = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!idb) throw new Error(\"indexedDB is not available in this environment\");\n return new Promise<boolean>((resolve, reject) => {\n const req = idb.open(dbName);\n req.onsuccess = () => {\n const db = req.result;\n const exists = db.objectStoreNames.contains(storeName);\n db.close();\n resolve(exists);\n };\n req.onerror = () => reject(req.error);\n req.onblocked = () =>\n reject(new Error(`IndexedDB ${dbName} blocked while probing for object store`));\n });\n}\n\n/**\n * IndexedDB applier for tabular migrations.\n *\n * Op handling matches `InMemoryTabularMigrationApplier`'s schemaless model\n * (column ops are no-ops) and `SqlTabularMigrationApplier`'s SQL-backed\n * model (DDL is real) where each makes sense:\n *\n * - `addColumn` / `dropColumn` / `renameColumn` — **no-ops.** IDB stores\n * arbitrary JS objects, so there is no schema to evolve. Authors who\n * need to populate or rewrite a property on existing rows must pair\n * the column op with an explicit `backfill` op (which runs on every\n * backend, including SQL, where the backfill is in addition to the\n * real ALTER TABLE). This keeps a single migration spec portable\n * across backends.\n * - `addIndex` / `dropIndex` — **real DDL.** Run inside an upgrade\n * transaction issued by {@link IndexedDbMigrationRunner}, which writes\n * the `_storage_migrations` row in the same transaction so DDL +\n * bookkeeping commit atomically. `createIndex` / `deleteIndex` are\n * guarded by `objectStore.indexNames.contains` so a re-run does not\n * throw on already-existing / already-deleted indexes — mirroring\n * SQL's `IF NOT EXISTS` / `IF EXISTS` semantics.\n * - `backfill` — runs on a **separate** readwrite transaction *before*\n * the upgrade tx, because IDB upgrade transactions cannot span async\n * work. This means the backfill and the bookkeeping write are NOT\n * atomic on this backend: if the upgrade tx fails after a partial\n * backfill, the row mutations persist while no `(component, version)`\n * row is written, and the next run re-invokes `transform()` on the\n * already-mutated rows. **Backfill `transform`s on this applier MUST\n * therefore be idempotent.** SQL backends wrap everything in\n * `withTransaction` and do not have this requirement.\n *\n * Order of execution within a single migration: backfill first, then DDL.\n * This means a `[backfill, addIndex]` pair sees the new index applied to\n * the rewritten rows; the reverse order (`[addIndex, backfill]`) would\n * still execute backfill first because the runner buffers DDL until the\n * upgrade tx.\n */\nexport class IndexedDbTabularMigrationApplier implements ITabularMigrationApplier {\n private readonly runner: IndexedDbMigrationRunner;\n constructor(\n private readonly dbName: string,\n private readonly storeName: string,\n private readonly storage: BackfillCapableStorage,\n runner?: IndexedDbMigrationRunner\n ) {\n this.runner = runner ?? new IndexedDbMigrationRunner(dbName);\n }\n\n async ensureBookkeeping(): Promise<void> {\n await this.runner.ensureBookkeepingTable();\n }\n\n async appliedVersions(component: string): Promise<Set<number>> {\n return this.runner.appliedVersions(component);\n }\n\n async tableExists(): Promise<boolean> {\n return idbObjectStoreExists(this.dbName, this.storeName);\n }\n\n async markAllApplied(\n component: string,\n versions: ReadonlyArray<{ version: number; description: string | undefined }>\n ): Promise<void> {\n if (versions.length === 0) return;\n await this.runner.run(\n versions.map((v) => ({\n component,\n version: v.version,\n description: v.description,\n up: () => undefined,\n }))\n );\n }\n\n async applyMigration(\n component: string,\n version: number,\n description: string | undefined,\n ops: ReadonlyArray<TabularMigrationOp>,\n onProgress?: (fraction: number) => void\n ): Promise<void> {\n // Exhaustive partition. The `never` default makes adding a new op\n // kind to TabularMigrationOp a TS error here rather than a silent\n // drop into one of two filters, which is how the previous version\n // of this code lost addColumn / dropColumn / renameColumn ops.\n type AddIndexOp = Extract<TabularMigrationOp, { kind: \"addIndex\" }>;\n type DropIndexOp = Extract<TabularMigrationOp, { kind: \"dropIndex\" }>;\n type BackfillOp = Extract<TabularMigrationOp, { kind: \"backfill\" }>;\n const ddlOps: Array<AddIndexOp | DropIndexOp> = [];\n const backfills: BackfillOp[] = [];\n for (const op of ops) {\n switch (op.kind) {\n case \"addIndex\":\n case \"dropIndex\":\n ddlOps.push(op);\n break;\n case \"backfill\":\n backfills.push(op);\n break;\n case \"addColumn\":\n case \"dropColumn\":\n case \"renameColumn\":\n // Schemaless backend — no schema to evolve. Pair with `backfill`\n // when row data needs to change. See class JSDoc.\n break;\n default: {\n const _exhaustive: never = op;\n throw new Error(\n `IndexedDbTabularMigrationApplier: unhandled op kind ${(_exhaustive as { kind: string }).kind}`\n );\n }\n }\n }\n\n // Run backfills FIRST on a normal readwrite tx. If any throws, no\n // bookkeeping is written, so the migration is retried on the next run\n // — which is why backfill transforms must be idempotent on this\n // backend (see class JSDoc).\n let processed = 0;\n const total = Math.max(ops.length, 1);\n for (const op of backfills) {\n const batchSize = op.batchSize ?? 500;\n let cursor: unknown;\n while (true) {\n const page = await this.storage.getPage({ limit: batchSize, cursor });\n for (const row of page.items) {\n const out = await op.transform(row);\n if (out === row) continue;\n if (out === undefined) {\n await this.storage.delete(row);\n } else {\n await this.storage.put(out);\n }\n }\n if (!page.nextCursor) break;\n cursor = page.nextCursor;\n }\n processed++;\n onProgress?.(processed / total);\n }\n\n // Then run DDL + bookkeeping atomically inside an upgrade transaction.\n // The runner.run() writes the `_storage_migrations` row inside the same\n // upgrade tx as our `up` callback, so DDL failure = no bookkeeping.\n // We always invoke runner.run() (even when ddlOps is empty) so that\n // bookkeeping is recorded for backfill-only and column-op-only\n // migrations.\n const storeName = this.storeName;\n await this.runner.run([\n {\n component,\n version,\n description,\n up: (ctx: IndexedDbUpgradeContext) => {\n if (!ctx.db.objectStoreNames.contains(storeName)) return;\n const store = ctx.tx.objectStore(storeName);\n for (const op of ddlOps) {\n if (op.kind === \"addIndex\") {\n if (store.indexNames.contains(op.name)) continue;\n const keyPath = op.columns.length === 1 ? op.columns[0] : [...op.columns];\n store.createIndex(op.name, keyPath, { unique: op.unique ?? false });\n } else {\n if (!store.indexNames.contains(op.name)) continue;\n store.deleteIndex(op.name);\n }\n }\n },\n },\n ]);\n processed += ddlOps.length;\n onProgress?.(processed / total);\n }\n}\n",
|
|
8
11
|
"/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { createServiceToken } from \"@workglow/util\";\nimport type {\n DataPortSchemaObject,\n FromSchema,\n TypedArray,\n TypedArrayConstructor,\n TypedArraySchemaOptions,\n} from \"@workglow/util/schema\";\nimport { cosineSimilarity } from \"@workglow/util/schema\";\nimport { IndexedDbTabularStorage } from \"./IndexedDbTabularStorage\";\nimport { getMetadataProperty, getVectorProperty } from \"@workglow/storage\";\nimport type {\n ClientProvidedKeysOption,\n AnyVectorStorage,\n HybridSearchOptions,\n IVectorStorage,\n VectorSearchOptions,\n} from \"@workglow/storage\";\nimport type { MigrationOptions } from \"./IndexedDbTable\";\n\nexport const IDB_VECTOR_REPOSITORY = createServiceToken<AnyVectorStorage>(\n \"storage.vectorRepository.indexedDb\"\n);\n\n/**\n * Check if metadata matches filter\n */\nfunction matchesFilter<Metadata>(metadata: Metadata, filter: Partial<Metadata>): boolean {\n for (const [key, value] of Object.entries(filter)) {\n if (metadata[key as keyof Metadata] !== value) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Simple full-text search scoring (keyword matching)\n */\nfunction textRelevance(text: string, query: string): number {\n const textLower = text.toLowerCase();\n const queryLower = query.toLowerCase();\n const queryWords = queryLower.split(/\\s+/).filter((w) => w.length > 0);\n if (queryWords.length === 0) {\n return 0;\n }\n let matches = 0;\n for (const word of queryWords) {\n if (textLower.includes(word)) {\n matches++;\n }\n }\n return matches / queryWords.length;\n}\n\n/**\n * IndexedDB vector storage implementation.\n * Extends IndexedDbTabularStorage for storage.\n * Suitable for browser applications that need persistent vector storage.\n * No vector serialization needed since IndexedDB supports TypedArrays\n * natively via structured clone.\n *\n * @template Schema - The schema definition for the entity\n * @template PrimaryKeyNames - The primary key names\n * @template Metadata - The metadata type for the vector\n * @template VectorCtor - Constructor for stored vectors (default {@link typeof Float32Array})\n * @template Entity - The entity type\n */\nexport class IndexedDbVectorStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n Metadata extends Record<string, unknown> = Record<string, unknown>,\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n>\n extends IndexedDbTabularStorage<Schema, PrimaryKeyNames, Entity>\n implements IVectorStorage<Metadata, Schema, Entity, PrimaryKeyNames>\n{\n private vectorDimensions: number;\n private vectorPropertyName: keyof Entity;\n private metadataPropertyName: keyof Entity | undefined;\n\n /**\n * Creates a new IndexedDB vector storage\n * @param table - The name of the IndexedDB store (defaults to 'vectors')\n * @param schema - The schema definition for the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param indexes - Array of columns or column arrays to make searchable\n * @param dimensions - The number of dimensions of the vector\n * @param _vectorCtor - TypedArray constructor (unused, IndexedDB stores typed arrays natively)\n * @param migrationOptions - Options for handling database schema migrations\n * @param clientProvidedKeys - How to handle client-provided values for auto-generated keys\n */\n constructor(\n table: string = \"vectors\",\n schema: Schema,\n primaryKeyNames: PrimaryKeyNames,\n indexes: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[] = [],\n dimensions: number,\n _vectorCtor: TypedArrayConstructor = Float32Array,\n migrationOptions: MigrationOptions = {},\n clientProvidedKeys: ClientProvidedKeysOption = \"if-missing\"\n ) {\n super(table, schema, primaryKeyNames, indexes, migrationOptions, clientProvidedKeys);\n\n this.vectorDimensions = dimensions;\n\n // Cache vector and metadata property names from schema\n const vectorProp = getVectorProperty(schema);\n if (!vectorProp) {\n throw new Error(\"Schema must have a property with type array and format TypedArray\");\n }\n this.vectorPropertyName = vectorProp as keyof Entity;\n this.metadataPropertyName = getMetadataProperty(schema) as keyof Entity | undefined;\n }\n\n /**\n * Get the vector dimensions\n * @returns The vector dimensions\n */\n getVectorDimensions(): number {\n return this.vectorDimensions;\n }\n\n async similaritySearch(\n query: TypedArray,\n options: VectorSearchOptions<Record<string, unknown>> = {}\n ) {\n const { topK = 10, filter, scoreThreshold = 0 } = options;\n const results: Array<Entity & { score: number }> = [];\n\n const allEntities = (await this.getAll()) || [];\n\n for (const entity of allEntities) {\n // IndexedDB stores TypedArrays natively via structured clone (no deserialization needed)\n const vector = entity[this.vectorPropertyName] as TypedArray;\n const metadata = this.metadataPropertyName\n ? (entity[this.metadataPropertyName] as Metadata)\n : ({} as Metadata);\n\n // Apply filter if provided\n if (filter && !matchesFilter(metadata, filter)) {\n continue;\n }\n\n // Calculate similarity\n const score = cosineSimilarity(query, vector);\n\n // Apply threshold\n if (score < scoreThreshold) {\n continue;\n }\n\n results.push({\n ...entity,\n score,\n } as Entity & { score: number });\n }\n\n // Sort by score descending and take top K\n results.sort((a, b) => b.score - a.score);\n const topResults = results.slice(0, topK);\n\n return topResults;\n }\n\n async hybridSearch(query: TypedArray, options: HybridSearchOptions<Record<string, unknown>>) {\n const { topK = 10, filter, scoreThreshold = 0, textQuery, vectorWeight = 0.7 } = options;\n\n if (!textQuery || textQuery.trim().length === 0) {\n // Fall back to regular vector search if no text query\n return this.similaritySearch(query, { topK, filter, scoreThreshold });\n }\n\n const results: Array<Entity & { score: number }> = [];\n const allEntities = (await this.getAll()) || [];\n\n for (const entity of allEntities) {\n // IndexedDB stores TypedArrays natively via structured clone (no deserialization needed)\n const vector = entity[this.vectorPropertyName] as TypedArray;\n const metadata = this.metadataPropertyName\n ? (entity[this.metadataPropertyName] as Metadata)\n : ({} as Metadata);\n\n // Apply filter if provided\n if (filter && !matchesFilter(metadata, filter)) {\n continue;\n }\n\n // Calculate vector similarity\n const vectorScore = cosineSimilarity(query, vector);\n\n // Calculate text relevance (simple keyword matching)\n const metadataText = Object.values(metadata).join(\" \").toLowerCase();\n const textScore = textRelevance(metadataText, textQuery);\n\n // Combine scores\n const combinedScore = vectorWeight * vectorScore + (1 - vectorWeight) * textScore;\n\n // Apply threshold\n if (combinedScore < scoreThreshold) {\n continue;\n }\n\n results.push({\n ...entity,\n score: combinedScore,\n } as Entity & { score: number });\n }\n\n // Sort by combined score descending and take top K\n results.sort((a, b) => b.score - a.score);\n const topResults = results.slice(0, topK);\n\n return topResults;\n }\n}\n"
|
|
9
12
|
],
|
|
10
|
-
"mappings": "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",
|
|
11
|
-
"debugId": "
|
|
13
|
+
"mappings": "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",
|
|
14
|
+
"debugId": "CCF175469329EA4F64756E2164756E21",
|
|
12
15
|
"names": []
|
|
13
16
|
}
|