@workglow/indexeddb 0.2.28
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/dist/job-queue/IndexedDbQueueStorage.d.ts +167 -0
- package/dist/job-queue/IndexedDbQueueStorage.d.ts.map +1 -0
- package/dist/job-queue/IndexedDbRateLimiterStorage.d.ts +79 -0
- package/dist/job-queue/IndexedDbRateLimiterStorage.d.ts.map +1 -0
- package/dist/job-queue/browser.d.ts +7 -0
- package/dist/job-queue/browser.d.ts.map +1 -0
- package/dist/job-queue/browser.js +1195 -0
- package/dist/job-queue/browser.js.map +12 -0
- package/dist/job-queue/bun.d.ts +7 -0
- package/dist/job-queue/bun.d.ts.map +1 -0
- package/dist/job-queue/common.d.ts +8 -0
- package/dist/job-queue/common.d.ts.map +1 -0
- package/dist/job-queue/node.d.ts +7 -0
- package/dist/job-queue/node.d.ts.map +1 -0
- package/dist/job-queue/node.js +1195 -0
- package/dist/job-queue/node.js.map +12 -0
- package/dist/storage/IndexedDbKvStorage.d.ts +26 -0
- package/dist/storage/IndexedDbKvStorage.d.ts.map +1 -0
- package/dist/storage/IndexedDbTable.d.ts +40 -0
- package/dist/storage/IndexedDbTable.d.ts.map +1 -0
- package/dist/storage/IndexedDbTabularStorage.d.ts +198 -0
- package/dist/storage/IndexedDbTabularStorage.d.ts.map +1 -0
- package/dist/storage/IndexedDbVectorStorage.d.ts +52 -0
- package/dist/storage/IndexedDbVectorStorage.d.ts.map +1 -0
- package/dist/storage/browser.d.ts +7 -0
- package/dist/storage/browser.d.ts.map +1 -0
- package/dist/storage/browser.js +1182 -0
- package/dist/storage/browser.js.map +13 -0
- package/dist/storage/bun.d.ts +7 -0
- package/dist/storage/bun.d.ts.map +1 -0
- package/dist/storage/common.d.ts +10 -0
- package/dist/storage/common.d.ts.map +1 -0
- package/dist/storage/node.d.ts +7 -0
- package/dist/storage/node.d.ts.map +1 -0
- package/dist/storage/node.js +1182 -0
- package/dist/storage/node.js.map +13 -0
- package/package.json +74 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/job-queue/IndexedDbQueueStorage.ts", "../../src/storage/IndexedDbTable.ts", "../../src/job-queue/IndexedDbRateLimiterStorage.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\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 { HybridSubscriptionManager } from \"@workglow/storage\";\nimport {\n ensureIndexedDbTable,\n ExpectedIndexDefinition,\n MigrationOptions,\n} from \"../storage/IndexedDbTable\";\nimport { JobStatus } from \"@workglow/job-queue\";\nimport type {\n IQueueStorage,\n JobStorageFormat,\n PrefixColumn,\n QueueChangePayload,\n QueueStorageOptions,\n QueueSubscribeOptions,\n} from \"@workglow/job-queue\";\n\nexport const INDEXED_DB_QUEUE_STORAGE = createServiceToken<IQueueStorage<any, any>>(\n \"jobqueue.storage.indexedDb\"\n);\n\n/**\n * Extended options for IndexedDB queue storage including prefix support\n */\nexport interface IndexedDbQueueStorageOptions extends QueueStorageOptions, MigrationOptions {\n /** Enable BroadcastChannel notifications (default: true) */\n readonly useBroadcastChannel?: boolean;\n /** Backup polling interval in ms (default: 5000, 0 to disable) */\n readonly backupPollingIntervalMs?: number;\n}\n\n/**\n * IndexedDB implementation of a job queue storage.\n * Provides storage and retrieval for job execution states using IndexedDB.\n */\nexport class IndexedDbQueueStorage<Input, Output> implements IQueueStorage<Input, Output> {\n public readonly scope = \"process\" as const;\n private db: IDBDatabase | undefined;\n private readonly tableName: string;\n private readonly migrationOptions: MigrationOptions;\n /** The prefix column definitions */\n protected readonly prefixes: readonly PrefixColumn[];\n /** The prefix values for filtering */\n protected readonly prefixValues: Readonly<Record<string, string | number>>;\n /** Shared hybrid subscription manager */\n private hybridManager: HybridSubscriptionManager<\n JobStorageFormat<Input, Output>,\n unknown,\n QueueChangePayload<Input, Output>\n > | null = null;\n /** Hybrid subscription options */\n private readonly hybridOptions: {\n readonly useBroadcastChannel: boolean;\n readonly backupPollingIntervalMs: number;\n };\n\n constructor(\n public readonly queueName: string,\n options: IndexedDbQueueStorageOptions = {}\n ) {\n this.migrationOptions = options;\n this.prefixes = options.prefixes ?? [];\n this.prefixValues = options.prefixValues ?? {};\n this.hybridOptions = {\n useBroadcastChannel: options.useBroadcastChannel ?? true,\n backupPollingIntervalMs: options.backupPollingIntervalMs ?? 5000,\n };\n // Generate table name based on prefix configuration to avoid conflicts\n if (this.prefixes.length > 0) {\n const prefixNames = this.prefixes.map((p) => p.name).join(\"_\");\n this.tableName = `jobs_${prefixNames}`;\n } else {\n this.tableName = \"jobs\";\n }\n }\n\n /**\n * Gets prefix column names for use in indexes\n */\n private getPrefixColumnNames(): string[] {\n return this.prefixes.map((p) => p.name);\n }\n\n /**\n * Checks if a job matches the current prefix values\n */\n private matchesPrefixes(job: JobStorageFormat<Input, Output> & Record<string, unknown>): boolean {\n for (const [key, value] of Object.entries(this.prefixValues)) {\n if (job[key] !== value) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * Gets prefix values as an array in column order for index key construction\n */\n private getPrefixKeyValues(): Array<string | number> {\n return this.prefixes.map((p) => this.prefixValues[p.name]);\n }\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 async setupDatabase(): Promise<void> {\n const prefixColumnNames = this.getPrefixColumnNames();\n\n // Build index key paths with prefixes prepended\n const buildKeyPath = (basePath: string[]): string[] => {\n return [...prefixColumnNames, ...basePath];\n };\n\n const expectedIndexes: ExpectedIndexDefinition[] = [\n {\n name: \"queue_status\",\n keyPath: buildKeyPath([\"queue\", \"status\"]),\n options: { unique: false },\n },\n {\n name: \"queue_status_run_after\",\n keyPath: buildKeyPath([\"queue\", \"status\", \"run_after\"]),\n options: { unique: false },\n },\n {\n name: \"queue_job_run_id\",\n keyPath: buildKeyPath([\"queue\", \"job_run_id\"]),\n options: { unique: false },\n },\n {\n name: \"queue_fingerprint_status\",\n keyPath: buildKeyPath([\"queue\", \"fingerprint\", \"status\"]),\n options: { unique: false },\n },\n ];\n\n this.db = await ensureIndexedDbTable(\n this.tableName,\n \"id\",\n expectedIndexes,\n this.migrationOptions\n );\n }\n\n /**\n * Adds a job to the queue.\n * @param job - The job to add to the queue.\n * @returns A promise that resolves to the job id.\n */\n public async add(job: JobStorageFormat<Input, Output>): Promise<unknown> {\n const db = await this.getDb();\n const now = new Date().toISOString();\n const jobWithPrefixes = job as JobStorageFormat<Input, Output> & Record<string, unknown>;\n jobWithPrefixes.id = jobWithPrefixes.id ?? uuid4();\n jobWithPrefixes.job_run_id = jobWithPrefixes.job_run_id ?? uuid4();\n jobWithPrefixes.queue = this.queueName;\n jobWithPrefixes.fingerprint = await makeFingerprint(jobWithPrefixes.input);\n jobWithPrefixes.status = JobStatus.PENDING;\n jobWithPrefixes.progress = 0;\n jobWithPrefixes.progress_message = \"\";\n jobWithPrefixes.progress_details = null;\n jobWithPrefixes.created_at = now;\n jobWithPrefixes.run_after = now;\n\n // Add prefix values to the job\n for (const [key, value] of Object.entries(this.prefixValues)) {\n jobWithPrefixes[key] = value;\n }\n\n const tx = db.transaction(this.tableName, \"readwrite\");\n const store = tx.objectStore(this.tableName);\n\n return new Promise((resolve, reject) => {\n const request = store.add(jobWithPrefixes);\n\n // Don't resolve until transaction is complete\n tx.oncomplete = () => {\n // Notify hybrid manager of local change\n this.hybridManager?.notifyLocalChange();\n resolve(jobWithPrefixes.id);\n };\n tx.onerror = () => reject(tx.error);\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Retrieves a job from the queue by its id.\n * @param id - The id of the job to retrieve.\n * @returns A promise that resolves to the job or undefined if the job is not found.\n */\n async get(id: unknown): Promise<JobStorageFormat<Input, Output> | undefined> {\n const db = await this.getDb();\n const tx = db.transaction(this.tableName, \"readonly\");\n const store = tx.objectStore(this.tableName);\n const request = store.get(id as string);\n return new Promise((resolve, reject) => {\n request.onsuccess = () => {\n const job = request.result as\n | (JobStorageFormat<Input, Output> & Record<string, unknown>)\n | undefined;\n // Filter by queue name and prefix values to ensure job belongs to this queue\n if (job && job.queue === this.queueName && this.matchesPrefixes(job)) {\n resolve(job);\n } else {\n resolve(undefined);\n }\n };\n request.onerror = () => reject(request.error);\n tx.onerror = () => reject(tx.error);\n });\n }\n\n /**\n * Retrieves a slice of jobs from the queue.\n * @param status - The status of the jobs to retrieve.\n * @param num - The number of jobs to retrieve.\n * @returns A promise that resolves to an array of jobs.\n */\n public async peek(\n status: JobStatus = JobStatus.PENDING,\n num: number = 100\n ): Promise<JobStorageFormat<Input, Output>[]> {\n const db = await this.getDb();\n const tx = db.transaction(this.tableName, \"readonly\");\n const store = tx.objectStore(this.tableName);\n const index = store.index(\"queue_status_run_after\");\n const prefixKeyValues = this.getPrefixKeyValues();\n\n return new Promise((resolve, reject) => {\n const ret = new Map<unknown, JobStorageFormat<Input, Output>>();\n // Create a key range for the compound index: from [prefixes..., queue, status, \"\"] to [prefixes..., queue, status, \"\\uffff\"]\n const keyRange = IDBKeyRange.bound(\n [...prefixKeyValues, this.queueName, status, \"\"],\n [...prefixKeyValues, this.queueName, status, \"\\uffff\"]\n );\n const cursorRequest = index.openCursor(keyRange);\n\n const handleCursor = (e: Event) => {\n const cursor = (e.target as IDBRequest<IDBCursorWithValue>).result;\n if (!cursor || ret.size >= num) {\n resolve(Array.from(ret.values()));\n return;\n }\n const job = cursor.value as JobStorageFormat<Input, Output> & Record<string, unknown>;\n // Verify prefix match and use Map to ensure no duplicates by job ID\n if (this.matchesPrefixes(job)) {\n ret.set(cursor.value.id, cursor.value);\n }\n cursor.continue();\n };\n\n cursorRequest.onsuccess = handleCursor;\n cursorRequest.onerror = () => reject(cursorRequest.error);\n tx.onerror = () => reject(tx.error);\n });\n }\n\n /**\n * Retrieves the next job from the queue using optimistic locking. In case multiple workers\n * claim the same job, the first worker to claim it will process it and the other workers will return undefined.\n * This ONLY happens if workers are running in multiple tabs.\n *\n * IndexedDB uses snapshot isolation, so concurrent transactions can both see the same\n * PENDING job. To prevent processing the same job multiple times, this method:\n * 1. Claims a job by setting it to PROCESSING with a unique claim token\n * 2. After the transaction completes, re-reads the job to verify the claim succeeded\n * 3. If another worker claimed it first (different claim token), returns undefined\n *\n * @param workerId - Worker ID to associate with the job (required)\n * @returns A promise that resolves to the next job or undefined if the queue is empty.\n */\n public async next(workerId: string): Promise<JobStorageFormat<Input, Output> | undefined> {\n const db = await this.getDb();\n const tx = db.transaction(this.tableName, \"readwrite\");\n const store = tx.objectStore(this.tableName);\n const index = store.index(\"queue_status_run_after\");\n const now = new Date().toISOString();\n const prefixKeyValues = this.getPrefixKeyValues();\n\n // This ensures we can verify that we actually won the race to claim this job\n const claimToken = workerId;\n\n const jobToReturn = await new Promise<JobStorageFormat<Input, Output> | undefined>(\n (resolve, reject) => {\n const cursorRequest = index.openCursor(\n IDBKeyRange.bound(\n [...prefixKeyValues, this.queueName, JobStatus.PENDING, \"\"],\n [...prefixKeyValues, this.queueName, JobStatus.PENDING, now],\n false,\n false\n )\n );\n\n let claimedJob: JobStorageFormat<Input, Output> | undefined;\n let cursorStopped = false;\n\n cursorRequest.onsuccess = (e) => {\n const cursor = (e.target as IDBRequest<IDBCursorWithValue>).result;\n if (!cursor) {\n // Cursor exhausted - resolve with whatever we found (or undefined)\n return;\n }\n\n // If we already found and updated a job, stop iterating\n if (cursorStopped) {\n return;\n }\n\n const job = cursor.value as JobStorageFormat<Input, Output> & Record<string, unknown>;\n // Verify the job belongs to this queue, matches prefixes, and is still in PENDING state\n if (\n job.queue !== this.queueName ||\n job.status !== JobStatus.PENDING ||\n !this.matchesPrefixes(job)\n ) {\n cursor.continue();\n return;\n }\n\n // Claim the job with our unique token\n job.status = JobStatus.PROCESSING;\n job.last_ran_at = now;\n job.worker_id = claimToken;\n\n try {\n const updateRequest = store.put(job);\n updateRequest.onsuccess = () => {\n claimedJob = job;\n cursorStopped = true;\n // Stop cursor iteration - we've claimed a job\n };\n updateRequest.onerror = (err) => {\n console.error(\"Failed to update job status:\", err);\n cursor.continue();\n };\n } catch (err) {\n console.error(\"Error updating job:\", err);\n cursor.continue();\n }\n };\n\n cursorRequest.onerror = () => reject(cursorRequest.error);\n\n // Wait for transaction to complete before resolving\n tx.oncomplete = () => {\n // Notify hybrid manager of local change\n if (claimedJob) {\n this.hybridManager?.notifyLocalChange();\n }\n resolve(claimedJob);\n };\n tx.onerror = () => reject(tx.error);\n }\n );\n\n // If we didn't find any job to claim, return undefined\n if (!jobToReturn) {\n return undefined;\n }\n\n // Verify we actually won the race by re-reading the job\n // This is the optimistic locking check - if another worker claimed it first,\n // their claim token will be there instead of ours\n const verifiedJob = await this.get(jobToReturn.id);\n\n if (!verifiedJob) {\n // Job was deleted - we lost the race\n return undefined;\n }\n\n if (verifiedJob.worker_id !== claimToken) {\n // Another worker claimed this job - we lost the race\n return undefined;\n }\n\n if (verifiedJob.status !== JobStatus.PROCESSING) {\n // Job status changed (e.g., another worker completed it already) - we lost the race\n return undefined;\n }\n\n // We successfully claimed the job\n return verifiedJob;\n }\n\n /**\n * Retrieves the number of jobs in the queue.\n * Returns the count of jobs in the queue.\n */\n public async size(status = JobStatus.PENDING): Promise<number> {\n const db = await this.getDb();\n const prefixKeyValues = this.getPrefixKeyValues();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(this.tableName, \"readonly\");\n const store = tx.objectStore(this.tableName);\n const index = store.index(\"queue_status\");\n const keyRange = IDBKeyRange.only([...prefixKeyValues, this.queueName, status]);\n const request = index.count(keyRange);\n\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n tx.onerror = () => reject(tx.error);\n });\n }\n\n /**\n * Marks a job as complete with its output or error.\n */\n public async complete(job: JobStorageFormat<Input, Output>): Promise<void> {\n const db = await this.getDb();\n const tx = db.transaction(this.tableName, \"readwrite\");\n const store = tx.objectStore(this.tableName);\n\n return new Promise((resolve, reject) => {\n const getReq = store.get(job.id as string);\n getReq.onsuccess = () => {\n const existing = getReq.result as\n | (JobStorageFormat<Input, Output> & Record<string, unknown>)\n | undefined;\n // Verify job belongs to this queue and matches prefixes\n if (!existing || existing.queue !== this.queueName || !this.matchesPrefixes(existing)) {\n reject(\n new Error(`Job ${job.id} not found or does not belong to queue ${this.queueName}`)\n );\n return;\n }\n const currentAttempts = existing.run_attempts ?? 0;\n job.run_attempts = currentAttempts + 1;\n // Ensure queue is set correctly\n job.queue = this.queueName;\n\n // Ensure prefix values are preserved\n const jobWithPrefixes = job as JobStorageFormat<Input, Output> & Record<string, unknown>;\n for (const [key, value] of Object.entries(this.prefixValues)) {\n jobWithPrefixes[key] = value;\n }\n\n const putReq = store.put(jobWithPrefixes);\n putReq.onsuccess = () => {};\n putReq.onerror = () => reject(putReq.error);\n };\n getReq.onerror = () => reject(getReq.error);\n\n // Don't resolve until transaction is complete\n tx.oncomplete = () => {\n // Notify hybrid manager of local change\n this.hybridManager?.notifyLocalChange();\n resolve();\n };\n tx.onerror = () => reject(tx.error);\n });\n }\n\n /**\n * Releases a claimed job without consuming a retry attempt.\n */\n public async release(id: unknown): Promise<void> {\n const job = await this.get(id);\n if (!job) return;\n\n job.status = JobStatus.PENDING;\n job.worker_id = null;\n job.progress = 0;\n job.progress_message = \"\";\n job.progress_details = null;\n\n await this.put(job);\n }\n\n /**\n * Aborts a job in the queue.\n */\n public async abort(id: unknown): Promise<void> {\n const job = await this.get(id);\n if (!job) return;\n\n job.status = JobStatus.ABORTING;\n await this.complete(job);\n }\n\n /**\n * Gets jobs by their run ID.\n */\n public async getByRunId(job_run_id: string): Promise<JobStorageFormat<Input, Output>[]> {\n const db = await this.getDb();\n const tx = db.transaction(this.tableName, \"readonly\");\n const store = tx.objectStore(this.tableName);\n const index = store.index(\"queue_job_run_id\");\n const prefixKeyValues = this.getPrefixKeyValues();\n const keyRange = IDBKeyRange.only([...prefixKeyValues, this.queueName, job_run_id]);\n const request = index.getAll(keyRange);\n\n return new Promise((resolve, reject) => {\n request.onsuccess = () => {\n // Filter results to ensure they match prefixes\n const results = (request.result || []).filter(\n (job: JobStorageFormat<Input, Output> & Record<string, unknown>) =>\n this.matchesPrefixes(job)\n );\n resolve(results);\n };\n request.onerror = () => reject(request.error);\n tx.onerror = () => reject(tx.error);\n });\n }\n\n /**\n * Deletes all jobs from the queue.\n */\n public async deleteAll(): Promise<void> {\n const db = await this.getDb();\n const tx = db.transaction(this.tableName, \"readwrite\");\n const store = tx.objectStore(this.tableName);\n const index = store.index(\"queue_status\");\n const prefixKeyValues = this.getPrefixKeyValues();\n\n return new Promise((resolve, reject) => {\n // Use a cursor to iterate through all jobs for this queue with prefix\n const keyRange = IDBKeyRange.bound(\n [...prefixKeyValues, this.queueName, \"\"],\n [...prefixKeyValues, this.queueName, \"\\uffff\"]\n );\n const request = index.openCursor(keyRange);\n\n request.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n const job = cursor.value as JobStorageFormat<Input, Output> & Record<string, unknown>;\n // Verify job belongs to this queue and matches prefixes before deleting\n if (job.queue === this.queueName && this.matchesPrefixes(job)) {\n const deleteRequest = cursor.delete();\n deleteRequest.onsuccess = () => {\n cursor.continue();\n };\n deleteRequest.onerror = () => {\n // Continue even if delete fails\n cursor.continue();\n };\n } else {\n cursor.continue();\n }\n }\n };\n\n tx.oncomplete = () => {\n // Notify hybrid manager of local change\n this.hybridManager?.notifyLocalChange();\n resolve();\n };\n tx.onerror = () => reject(tx.error);\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Gets the output for a given input.\n */\n public async outputForInput(input: Input): Promise<Output | null> {\n const fingerprint = await makeFingerprint(input);\n const db = await this.getDb();\n const tx = db.transaction(this.tableName, \"readonly\");\n const store = tx.objectStore(this.tableName);\n const index = store.index(\"queue_fingerprint_status\");\n const prefixKeyValues = this.getPrefixKeyValues();\n const request = index.get([\n ...prefixKeyValues,\n this.queueName,\n fingerprint,\n JobStatus.COMPLETED,\n ]);\n\n return new Promise((resolve, reject) => {\n request.onsuccess = () => {\n const job = request.result as\n | (JobStorageFormat<Input, Output> & Record<string, unknown>)\n | undefined;\n if (job && this.matchesPrefixes(job)) {\n resolve(job.output ?? null);\n } else {\n resolve(null);\n }\n };\n request.onerror = () => reject(request.error);\n tx.onerror = () => reject(tx.error);\n });\n }\n\n /**\n * Saves progress updates for a job.\n */\n public async saveProgress(\n id: unknown,\n progress: number,\n message: string,\n details: Record<string, any> | null\n ): Promise<void> {\n const job = await this.get(id);\n if (!job) throw new Error(`Job ${id} not found`);\n\n job.progress = progress;\n job.progress_message = message;\n job.progress_details = details;\n\n await this.put(job);\n }\n\n /**\n * Persists a job to the store without modifying run_attempts or other completion logic.\n */\n private async put(job: JobStorageFormat<Input, Output>): Promise<void> {\n const db = await this.getDb();\n const tx = db.transaction(this.tableName, \"readwrite\");\n const store = tx.objectStore(this.tableName);\n\n // Ensure queue is set correctly\n job.queue = this.queueName;\n\n // Ensure prefix values are preserved\n const jobWithPrefixes = job as JobStorageFormat<Input, Output> & Record<string, unknown>;\n for (const [key, value] of Object.entries(this.prefixValues)) {\n jobWithPrefixes[key] = value;\n }\n\n return new Promise((resolve, reject) => {\n const putReq = store.put(jobWithPrefixes);\n putReq.onerror = () => reject(putReq.error);\n tx.oncomplete = () => {\n // Notify hybrid manager of local change\n this.hybridManager?.notifyLocalChange();\n resolve();\n };\n tx.onerror = () => reject(tx.error);\n });\n }\n\n /**\n * Deletes a job by its ID.\n */\n public async delete(id: unknown): Promise<void> {\n const job = await this.get(id);\n if (!job) return;\n\n const db = await this.getDb();\n const tx = db.transaction(this.tableName, \"readwrite\");\n const store = tx.objectStore(this.tableName);\n const request = store.delete(id as string);\n\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n tx.oncomplete = () => {\n // Notify hybrid manager of local change\n this.hybridManager?.notifyLocalChange();\n };\n tx.onerror = () => reject(tx.error);\n });\n }\n\n /**\n * Delete jobs with a specific status older than a cutoff date\n * @param status - Status of jobs to delete\n * @param olderThanMs - Delete jobs completed more than this many milliseconds ago\n */\n public async deleteJobsByStatusAndAge(status: JobStatus, olderThanMs: number): Promise<void> {\n const db = await this.getDb();\n const tx = db.transaction(this.tableName, \"readwrite\");\n const store = tx.objectStore(this.tableName);\n const index = store.index(\"queue_status\");\n const cutoffDate = new Date(Date.now() - olderThanMs).toISOString();\n const prefixKeyValues = this.getPrefixKeyValues();\n const keyRange = IDBKeyRange.only([...prefixKeyValues, this.queueName, status]);\n\n return new Promise((resolve, reject) => {\n const request = index.openCursor(keyRange);\n\n request.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n const job = cursor.value as JobStorageFormat<Input, Output> & Record<string, unknown>;\n // Verify job belongs to this queue, matches prefixes, and matches criteria\n if (\n job.queue === this.queueName &&\n this.matchesPrefixes(job) &&\n job.status === status &&\n job.completed_at &&\n job.completed_at <= cutoffDate\n ) {\n cursor.delete();\n }\n cursor.continue();\n }\n };\n\n tx.oncomplete = () => {\n // Notify hybrid manager of local change\n this.hybridManager?.notifyLocalChange();\n resolve();\n };\n tx.onerror = () => reject(tx.error);\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Gets all jobs from the queue that match the current prefix values.\n * Used internally for normal polling-based subscriptions (efficient - filters at DB level).\n *\n * @returns A promise that resolves to an array of jobs\n */\n private async getAllJobs(): Promise<Array<JobStorageFormat<Input, Output>>> {\n const db = await this.getDb();\n const tx = db.transaction(this.tableName, \"readonly\");\n const store = tx.objectStore(this.tableName);\n const index = store.index(\"queue_status\");\n const prefixKeyValues = this.getPrefixKeyValues();\n\n return new Promise((resolve, reject) => {\n const jobs: Array<JobStorageFormat<Input, Output>> = [];\n // Use a key range that covers all statuses for this queue with prefixes\n const keyRange = IDBKeyRange.bound(\n [...prefixKeyValues, this.queueName, \"\"],\n [...prefixKeyValues, this.queueName, \"\\uffff\"]\n );\n const request = index.openCursor(keyRange);\n\n request.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n const job = cursor.value as JobStorageFormat<Input, Output> & Record<string, unknown>;\n if (job.queue === this.queueName && this.matchesPrefixes(job)) {\n jobs.push(job);\n }\n cursor.continue();\n }\n };\n\n tx.oncomplete = () => resolve(jobs);\n tx.onerror = () => reject(tx.error);\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Gets all jobs from the queue with a custom prefix filter.\n * Used for subscriptions with custom prefix filters (filters at DB level where possible).\n *\n * @param prefixFilter - The prefix values to filter by (empty object = all jobs)\n * @returns A promise that resolves to an array of jobs\n */\n private async getAllJobsWithFilter(\n prefixFilter: Readonly<Record<string, string | number>>\n ): Promise<Array<JobStorageFormat<Input, Output>>> {\n const db = await this.getDb();\n const tx = db.transaction(this.tableName, \"readonly\");\n const store = tx.objectStore(this.tableName);\n\n return new Promise((resolve, reject) => {\n const jobs: Array<JobStorageFormat<Input, Output>> = [];\n const request = store.openCursor();\n\n request.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n const job = cursor.value as JobStorageFormat<Input, Output> & Record<string, unknown>;\n // Filter by queue name\n if (job.queue !== this.queueName) {\n cursor.continue();\n return;\n }\n // If empty filter, include all jobs for this queue\n if (Object.keys(prefixFilter).length === 0) {\n jobs.push(job);\n } else {\n // Check each filter value\n let matches = true;\n for (const [key, value] of Object.entries(prefixFilter)) {\n if (job[key] !== value) {\n matches = false;\n break;\n }\n }\n if (matches) {\n jobs.push(job);\n }\n }\n cursor.continue();\n }\n };\n\n tx.oncomplete = () => resolve(jobs);\n tx.onerror = () => reject(tx.error);\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Checks if a prefix filter is custom (different from instance's prefixes).\n */\n private isCustomPrefixFilter(prefixFilter?: Readonly<Record<string, string | number>>): boolean {\n // No filter specified - use instance prefixes (not custom)\n if (prefixFilter === undefined) {\n return false;\n }\n // Empty filter - receive all (custom)\n if (Object.keys(prefixFilter).length === 0) {\n return true;\n }\n // Check if filter matches instance prefixes exactly\n const instanceKeys = Object.keys(this.prefixValues);\n const filterKeys = Object.keys(prefixFilter);\n if (instanceKeys.length !== filterKeys.length) {\n return true; // Different number of keys = custom\n }\n for (const key of instanceKeys) {\n if (this.prefixValues[key] !== prefixFilter[key]) {\n return true; // Different value = custom\n }\n }\n return false; // Matches instance prefixes exactly\n }\n\n /**\n * Gets or creates the shared hybrid subscription manager for normal subscriptions.\n * This ensures all normal subscriptions share a single manager.\n */\n private getHybridManager(): HybridSubscriptionManager<\n JobStorageFormat<Input, Output>,\n unknown,\n QueueChangePayload<Input, Output>\n > {\n if (!this.hybridManager) {\n // Generate unique channel name based on queue name and table name\n const channelName = `indexeddb-queue-${this.tableName}-${this.queueName}`;\n\n this.hybridManager = new HybridSubscriptionManager<\n JobStorageFormat<Input, Output>,\n unknown,\n QueueChangePayload<Input, Output>\n >(\n channelName,\n async () => {\n // Fetch jobs with instance's prefix filter (efficient DB-level filtering)\n const jobs = await this.getAllJobs();\n return new Map(jobs.map((j) => [j.id, j]));\n },\n (a, b) => deepEqual(a, b),\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 * Creates a dedicated polling subscription for custom prefix filters.\n * This runs separately from the normal polling manager.\n */\n private subscribeWithCustomPrefixFilter(\n callback: (change: QueueChangePayload<Input, Output>) => void,\n prefixFilter: Readonly<Record<string, string | number>>,\n intervalMs: number\n ): () => void {\n let lastKnownJobs = new Map<unknown, JobStorageFormat<Input, Output>>();\n let cancelled = false;\n\n const poll = async () => {\n if (cancelled) return;\n try {\n const currentJobs = await this.getAllJobsWithFilter(prefixFilter);\n if (cancelled) return;\n const currentMap = new Map(currentJobs.map((j) => [j.id, j]));\n\n // Detect changes\n for (const [id, job] of currentMap) {\n const old = lastKnownJobs.get(id);\n if (!old) {\n callback({ type: \"INSERT\", new: job });\n } else if (!deepEqual(old, job)) {\n callback({ type: \"UPDATE\", old, new: job });\n }\n }\n\n for (const [id, job] of lastKnownJobs) {\n if (!currentMap.has(id)) {\n callback({ type: \"DELETE\", old: job });\n }\n }\n\n lastKnownJobs = currentMap;\n } catch {\n // Ignore polling errors\n }\n };\n\n const intervalId = setInterval(poll, intervalMs);\n poll(); // Initial poll\n\n return () => {\n cancelled = true;\n clearInterval(intervalId);\n };\n }\n\n /**\n * Subscribes to changes in the queue.\n * Uses polling since IndexedDB has no native cross-tab change notifications.\n *\n * Normal subscriptions (no custom prefix filter) share a single polling loop for efficiency.\n * Custom prefix filter subscriptions get their own dedicated polling loop with DB-level filtering.\n *\n * @param callback - Function called when a change occurs\n * @param options - Subscription options including polling interval and prefix filter\n * @returns Unsubscribe function\n */\n public subscribeToChanges(\n callback: (change: QueueChangePayload<Input, Output>) => void,\n options?: QueueSubscribeOptions\n ): () => void {\n const intervalMs = options?.pollingIntervalMs ?? 1000;\n\n // Check if this is a custom prefix filter subscription\n if (this.isCustomPrefixFilter(options?.prefixFilter)) {\n // Custom prefix filter - use dedicated polling with DB-level filtering\n return this.subscribeWithCustomPrefixFilter(callback, options!.prefixFilter!, intervalMs);\n }\n\n // Normal subscription - use shared hybrid manager (efficient)\n const manager = this.getHybridManager();\n return manager.subscribe(callback, { intervalMs });\n }\n\n /**\n * Cleanup method to destroy the hybrid manager\n */\n destroy(): void {\n if (this.hybridManager) {\n this.hybridManager.destroy();\n this.hybridManager = null;\n }\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\";\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",
|
|
7
|
+
"/**\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 {\n ensureIndexedDbTable,\n ExpectedIndexDefinition,\n MigrationOptions,\n} from \"../storage/IndexedDbTable\";\nimport type { PrefixColumn } from \"@workglow/job-queue\";\nimport type {\n IRateLimiterStorage,\n RateLimiterStorageOptions,\n RateLimiterStorageScope,\n} from \"@workglow/job-queue\";\n\nexport const INDEXED_DB_RATE_LIMITER_STORAGE = createServiceToken<IRateLimiterStorage>(\n \"ratelimiter.storage.indexedDb\"\n);\n\n/**\n * Extended options for IndexedDB rate limiter storage including prefix support.\n */\nexport interface IndexedDbRateLimiterStorageOptions\n extends RateLimiterStorageOptions, MigrationOptions {}\n\n/**\n * Execution record stored in IndexedDB.\n */\ninterface ExecutionRecord {\n readonly id?: string;\n readonly queue_name: string;\n readonly executed_at: string;\n readonly [key: string]: unknown;\n}\n\n/**\n * Next available record stored in IndexedDB.\n */\ninterface NextAvailableRecord {\n readonly queue_name: string;\n readonly next_available_at: string;\n readonly [key: string]: unknown;\n}\n\n/**\n * IndexedDB implementation of rate limiter storage.\n * Manages execution records and next available times for rate limiting.\n *\n * Atomicity is `\"process\"`-scoped (browser tab) and the `next_available_at`\n * pre-check inside {@link tryReserveExecution} runs in a separate IDB\n * transaction from the count-and-insert. See {@link tryReserveExecution} for\n * the full caveat — do not assume cross-store atomicity.\n */\nexport class IndexedDbRateLimiterStorage implements IRateLimiterStorage {\n /**\n * `\"process\"` — IndexedDB is per-origin and shared across tabs but not across\n * processes (different machines, server processes). Within a single tab the\n * `readwrite` transaction below provides atomicity.\n */\n public readonly scope: RateLimiterStorageScope = \"process\";\n private executionDb: IDBDatabase | undefined;\n private nextAvailableDb: IDBDatabase | undefined;\n private readonly executionTableName: string;\n private readonly nextAvailableTableName: string;\n private readonly migrationOptions: MigrationOptions;\n /** The prefix column definitions */\n protected readonly prefixes: readonly PrefixColumn[];\n /** The prefix values for filtering */\n protected readonly prefixValues: Readonly<Record<string, string | number>>;\n\n constructor(options: IndexedDbRateLimiterStorageOptions = {}) {\n this.migrationOptions = options;\n this.prefixes = options.prefixes ?? [];\n this.prefixValues = options.prefixValues ?? {};\n\n // Generate table names based on prefix configuration\n if (this.prefixes.length > 0) {\n const prefixNames = this.prefixes.map((p) => p.name).join(\"_\");\n this.executionTableName = `rate_limit_executions_${prefixNames}`;\n this.nextAvailableTableName = `rate_limit_next_available_${prefixNames}`;\n } else {\n this.executionTableName = \"rate_limit_executions\";\n this.nextAvailableTableName = \"rate_limit_next_available\";\n }\n }\n\n /**\n * Gets prefix column names for use in indexes.\n */\n private getPrefixColumnNames(): string[] {\n return this.prefixes.map((p) => p.name);\n }\n\n /**\n * Checks if a record matches the current prefix values.\n */\n private matchesPrefixes(record: Record<string, unknown>): boolean {\n for (const [key, value] of Object.entries(this.prefixValues)) {\n if (record[key] !== value) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * Gets prefix values as an array in column order for index key construction.\n */\n private getPrefixKeyValues(): Array<string | number> {\n return this.prefixes.map((p) => this.prefixValues[p.name]);\n }\n\n private async getExecutionDb(): Promise<IDBDatabase> {\n if (this.executionDb) return this.executionDb;\n await this.setupDatabase();\n return this.executionDb!;\n }\n\n private async getNextAvailableDb(): Promise<IDBDatabase> {\n if (this.nextAvailableDb) return this.nextAvailableDb;\n await this.setupDatabase();\n return this.nextAvailableDb!;\n }\n\n public async setupDatabase(): Promise<void> {\n const prefixColumnNames = this.getPrefixColumnNames();\n\n // Build index key paths with prefixes prepended\n const buildKeyPath = (basePath: string[]): string[] => {\n return [...prefixColumnNames, ...basePath];\n };\n\n const executionIndexes: ExpectedIndexDefinition[] = [\n {\n name: \"queue_executed_at\",\n keyPath: buildKeyPath([\"queue_name\", \"executed_at\"]),\n options: { unique: false },\n },\n ];\n\n this.executionDb = await ensureIndexedDbTable(\n this.executionTableName,\n \"id\",\n executionIndexes,\n this.migrationOptions\n );\n\n const nextAvailableIndexes: ExpectedIndexDefinition[] = [\n {\n name: \"queue_name\",\n keyPath: buildKeyPath([\"queue_name\"]),\n options: { unique: true },\n },\n ];\n\n this.nextAvailableDb = await ensureIndexedDbTable(\n this.nextAvailableTableName,\n buildKeyPath([\"queue_name\"]).join(\"_\"),\n nextAvailableIndexes,\n this.migrationOptions\n );\n }\n\n /**\n * Atomically reserves an execution slot in IndexedDB.\n *\n * Atomicity caveat: this implementation only serializes the `count + insert`\n * pair (under a single `readwrite` IDB transaction over the executions\n * store). The `next_available_at` lookup is a soft pre-check performed\n * BEFORE the tx, because the executions and next-available object stores\n * live in separate `IDBDatabase` instances by design (one\n * `ensureIndexedDbTable` call each) and IDB transaction scope cannot cross\n * databases. This is acceptable for IndexedDB's `\"process\"` scope: rate-\n * limit overshoot from a stale next-available read is bounded by single-\n * tab serial execution, and the stricter count check is what protects the\n * primary maxExecutions invariant.\n */\n public async tryReserveExecution(\n queueName: string,\n maxExecutions: number,\n windowMs: number\n ): Promise<unknown | null> {\n // Soft pre-check against external backoff. Done before opening the tx\n // because next-available lives in a separate IDBDatabase.\n const nextIso = await this.getNextAvailableTime(queueName);\n if (nextIso && new Date(nextIso).getTime() > Date.now()) {\n return null;\n }\n\n const execDb = await this.getExecutionDb();\n const prefixKeyValues = this.getPrefixKeyValues();\n const windowStartIso = new Date(Date.now() - windowMs).toISOString();\n const execTx = execDb.transaction(this.executionTableName, \"readwrite\");\n const execStore = execTx.objectStore(this.executionTableName);\n const insertedId = crypto.randomUUID();\n\n return new Promise<unknown | null>((resolve, reject) => {\n let liveCount = 0;\n let didInsert = false;\n const liveRange = IDBKeyRange.bound(\n [...prefixKeyValues, queueName, windowStartIso],\n [...prefixKeyValues, queueName, \"\"],\n true,\n false\n );\n const cursorReq = execStore.index(\"queue_executed_at\").openCursor(liveRange);\n\n cursorReq.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n const record = cursor.value as ExecutionRecord;\n if (this.matchesPrefixes(record)) {\n liveCount++;\n }\n cursor.continue();\n return;\n }\n\n if (liveCount >= maxExecutions) {\n // Aborting fires execTx.onabort below, which resolves with `false`.\n // No INSERT happened, so capacity is preserved.\n execTx.abort();\n return;\n }\n\n const record: ExecutionRecord = {\n id: insertedId,\n queue_name: queueName,\n executed_at: new Date().toISOString(),\n };\n for (const [k, v] of Object.entries(this.prefixValues)) {\n (record as Record<string, unknown>)[k] = v;\n }\n const addReq = execStore.add(record);\n didInsert = true;\n addReq.onerror = () => {\n try {\n execTx.abort();\n } catch {\n // already aborted\n }\n reject(addReq.error);\n };\n };\n\n cursorReq.onerror = () => reject(cursorReq.error);\n execTx.oncomplete = () => resolve(didInsert ? insertedId : null);\n execTx.onerror = () => reject(execTx.error);\n execTx.onabort = () => resolve(null);\n });\n }\n\n public async releaseExecution(queueName: string, token: unknown): Promise<void> {\n if (token === null || token === undefined) return;\n const db = await this.getExecutionDb();\n const tx = db.transaction(this.executionTableName, \"readwrite\");\n const store = tx.objectStore(this.executionTableName);\n return new Promise<void>((resolve, reject) => {\n // Delete by id — NEVER by recency. Two concurrent acquirers can hold\n // tokens for different rows; deleting \"the most recent\" would release\n // the other worker's slot.\n const req = store.delete(token as IDBValidKey);\n req.onerror = () => reject(req.error);\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n }\n\n public async recordExecution(queueName: string): Promise<void> {\n const db = await this.getExecutionDb();\n const tx = db.transaction(this.executionTableName, \"readwrite\");\n const store = tx.objectStore(this.executionTableName);\n\n const record: ExecutionRecord = {\n id: crypto.randomUUID(),\n queue_name: queueName,\n executed_at: new Date().toISOString(),\n };\n\n // Add prefix values to the record\n for (const [key, value] of Object.entries(this.prefixValues)) {\n (record as Record<string, unknown>)[key] = value;\n }\n\n return new Promise((resolve, reject) => {\n const request = store.add(record);\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n request.onerror = () => reject(request.error);\n });\n }\n\n public async getExecutionCount(queueName: string, windowStartTime: string): Promise<number> {\n const db = await this.getExecutionDb();\n const tx = db.transaction(this.executionTableName, \"readonly\");\n const store = tx.objectStore(this.executionTableName);\n const index = store.index(\"queue_executed_at\");\n const prefixKeyValues = this.getPrefixKeyValues();\n\n return new Promise((resolve, reject) => {\n let count = 0;\n const keyRange = IDBKeyRange.bound(\n [...prefixKeyValues, queueName, windowStartTime],\n [...prefixKeyValues, queueName, \"\\uffff\"],\n true, // exclude lower bound (windowStartTime)\n false\n );\n const request = index.openCursor(keyRange);\n\n request.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n const record = cursor.value as ExecutionRecord;\n if (this.matchesPrefixes(record)) {\n count++;\n }\n cursor.continue();\n }\n };\n\n tx.oncomplete = () => resolve(count);\n tx.onerror = () => reject(tx.error);\n request.onerror = () => reject(request.error);\n });\n }\n\n public async getOldestExecutionAtOffset(\n queueName: string,\n offset: number\n ): Promise<string | undefined> {\n const db = await this.getExecutionDb();\n const tx = db.transaction(this.executionTableName, \"readonly\");\n const store = tx.objectStore(this.executionTableName);\n const index = store.index(\"queue_executed_at\");\n const prefixKeyValues = this.getPrefixKeyValues();\n\n return new Promise((resolve, reject) => {\n const executions: string[] = [];\n const keyRange = IDBKeyRange.bound(\n [...prefixKeyValues, queueName, \"\"],\n [...prefixKeyValues, queueName, \"\\uffff\"]\n );\n const request = index.openCursor(keyRange);\n\n request.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n const record = cursor.value as ExecutionRecord;\n if (this.matchesPrefixes(record)) {\n executions.push(record.executed_at);\n }\n cursor.continue();\n }\n };\n\n tx.oncomplete = () => {\n // Sort by executed_at ascending\n executions.sort();\n resolve(executions[offset]);\n };\n tx.onerror = () => reject(tx.error);\n request.onerror = () => reject(request.error);\n });\n }\n\n public async getNextAvailableTime(queueName: string): Promise<string | undefined> {\n const db = await this.getNextAvailableDb();\n const tx = db.transaction(this.nextAvailableTableName, \"readonly\");\n const store = tx.objectStore(this.nextAvailableTableName);\n const prefixKeyValues = this.getPrefixKeyValues();\n const key = [...prefixKeyValues, queueName].join(\"_\");\n\n return new Promise((resolve, reject) => {\n const request = store.get(key);\n request.onsuccess = () => {\n const record = request.result as NextAvailableRecord | undefined;\n if (record && this.matchesPrefixes(record)) {\n resolve(record.next_available_at);\n } else {\n resolve(undefined);\n }\n };\n request.onerror = () => reject(request.error);\n tx.onerror = () => reject(tx.error);\n });\n }\n\n public async setNextAvailableTime(queueName: string, nextAvailableAt: string): Promise<void> {\n const db = await this.getNextAvailableDb();\n const tx = db.transaction(this.nextAvailableTableName, \"readwrite\");\n const store = tx.objectStore(this.nextAvailableTableName);\n const prefixKeyValues = this.getPrefixKeyValues();\n const key = [...prefixKeyValues, queueName].join(\"_\");\n\n const record: NextAvailableRecord & { [key: string]: unknown } = {\n queue_name: queueName,\n next_available_at: nextAvailableAt,\n };\n\n // Add prefix values to the record\n for (const [k, value] of Object.entries(this.prefixValues)) {\n record[k] = value;\n }\n\n // Set the key field\n (record as Record<string, unknown>)[\n this.getPrefixColumnNames().concat([\"queue_name\"]).join(\"_\")\n ] = key;\n\n return new Promise((resolve, reject) => {\n const request = store.put(record);\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n request.onerror = () => reject(request.error);\n });\n }\n\n public async clear(queueName: string): Promise<void> {\n // Clear executions\n const execDb = await this.getExecutionDb();\n const execTx = execDb.transaction(this.executionTableName, \"readwrite\");\n const execStore = execTx.objectStore(this.executionTableName);\n const execIndex = execStore.index(\"queue_executed_at\");\n const prefixKeyValues = this.getPrefixKeyValues();\n\n await new Promise<void>((resolve, reject) => {\n const keyRange = IDBKeyRange.bound(\n [...prefixKeyValues, queueName, \"\"],\n [...prefixKeyValues, queueName, \"\\uffff\"]\n );\n const request = execIndex.openCursor(keyRange);\n\n request.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n const record = cursor.value as ExecutionRecord;\n if (this.matchesPrefixes(record)) {\n cursor.delete();\n }\n cursor.continue();\n }\n };\n\n execTx.oncomplete = () => resolve();\n execTx.onerror = () => reject(execTx.error);\n request.onerror = () => reject(request.error);\n });\n\n // Clear next available\n const nextDb = await this.getNextAvailableDb();\n const nextTx = nextDb.transaction(this.nextAvailableTableName, \"readwrite\");\n const nextStore = nextTx.objectStore(this.nextAvailableTableName);\n const key = [...prefixKeyValues, queueName].join(\"_\");\n\n await new Promise<void>((resolve, reject) => {\n const request = nextStore.delete(key);\n nextTx.oncomplete = () => resolve();\n nextTx.onerror = () => reject(nextTx.error);\n request.onerror = () => reject(request.error);\n });\n }\n}\n"
|
|
8
|
+
],
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "57ACA1D439B2BC0064756E2164756E21",
|
|
11
|
+
"names": []
|
|
12
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { JsonSchema } from "@workglow/util/schema";
|
|
7
|
+
import { IndexedDbTabularStorage } from "./IndexedDbTabularStorage";
|
|
8
|
+
import { DefaultKeyValueKey, DefaultKeyValueSchema, IKvStorage, KvViaTabularStorage } from "@workglow/storage";
|
|
9
|
+
export declare const IDB_KV_REPOSITORY: import("@workglow/util").ServiceToken<IKvStorage<string, any, any>>;
|
|
10
|
+
/**
|
|
11
|
+
* A key-value repository implementation that uses IndexedDB for persistent storage in the browser.
|
|
12
|
+
* Leverages a tabular repository abstraction for IndexedDB operations.
|
|
13
|
+
*
|
|
14
|
+
* @template Key - The type of the primary key
|
|
15
|
+
* @template Value - The type of the value being stored
|
|
16
|
+
* @template Combined - Combined type of Key & Value
|
|
17
|
+
*/
|
|
18
|
+
export declare class IndexedDbKvStorage extends KvViaTabularStorage {
|
|
19
|
+
dbName: string;
|
|
20
|
+
tabularRepository: IndexedDbTabularStorage<typeof DefaultKeyValueSchema, typeof DefaultKeyValueKey>;
|
|
21
|
+
/**
|
|
22
|
+
* Creates a new KvStorage instance
|
|
23
|
+
*/
|
|
24
|
+
constructor(dbName: string, keySchema?: JsonSchema, valueSchema?: JsonSchema);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=IndexedDbKvStorage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexedDbKvStorage.d.ts","sourceRoot":"","sources":["../../src/storage/IndexedDbKvStorage.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,UAAU,EACV,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAE3B,eAAO,MAAM,iBAAiB,qEAE7B,CAAC;AAEF;;;;;;;GAOG;AACH,qBAAa,kBAAmB,SAAQ,mBAAmB;IAUhD,MAAM,EAAE,MAAM;IAThB,iBAAiB,EAAE,uBAAuB,CAC/C,OAAO,qBAAqB,EAC5B,OAAO,kBAAkB,CAC1B,CAAC;IAEF;;OAEG;IACH,YACS,MAAM,EAAE,MAAM,EACrB,SAAS,GAAE,UAA+B,EAC1C,WAAW,GAAE,UAAe,EAQ7B;CACF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
export interface ExpectedIndexDefinition {
|
|
7
|
+
name: string;
|
|
8
|
+
keyPath: string | string[];
|
|
9
|
+
options?: IDBIndexParameters;
|
|
10
|
+
}
|
|
11
|
+
export interface MigrationContext {
|
|
12
|
+
db: IDBDatabase;
|
|
13
|
+
transaction: IDBTransaction;
|
|
14
|
+
oldVersion: number;
|
|
15
|
+
newVersion: number;
|
|
16
|
+
tableName: string;
|
|
17
|
+
}
|
|
18
|
+
export interface DataTransformer {
|
|
19
|
+
(oldData: any): any | Promise<any>;
|
|
20
|
+
}
|
|
21
|
+
export interface MigrationOptions {
|
|
22
|
+
/** Custom data transformer to apply during migration */
|
|
23
|
+
dataTransformer?: DataTransformer;
|
|
24
|
+
/** Whether to allow destructive operations (delete and recreate). Default: false */
|
|
25
|
+
allowDestructiveMigration?: boolean;
|
|
26
|
+
/** Callback for migration progress/logging */
|
|
27
|
+
onMigrationProgress?: (message: string, progress?: number) => void;
|
|
28
|
+
/** Callback for migration errors (non-fatal warnings) */
|
|
29
|
+
onMigrationWarning?: (message: string, error?: Error) => void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Ensures that an IndexedDB table exists with the specified schema.
|
|
33
|
+
* Performs migrations as needed without data loss when possible.
|
|
34
|
+
*/
|
|
35
|
+
export declare function ensureIndexedDbTable(tableName: string, primaryKey: string | string[], expectedIndexes?: ExpectedIndexDefinition[], options?: MigrationOptions, autoIncrement?: boolean): Promise<IDBDatabase>;
|
|
36
|
+
/**
|
|
37
|
+
* Utility function to delete a database (for testing or cleanup)
|
|
38
|
+
*/
|
|
39
|
+
export declare function dropIndexedDbTable(tableName: string): Promise<void>;
|
|
40
|
+
//# sourceMappingURL=IndexedDbTable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexedDbTable.d.ts","sourceRoot":"","sources":["../../src/storage/IndexedDbTable.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,WAAW,CAAC;IAChB,WAAW,EAAE,cAAc,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,CAAC,OAAO,EAAE,GAAG,GAAG,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,oFAAoF;IACpF,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,8CAA8C;IAC9C,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnE,yDAAyD;IACzD,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;CAC/D;AAsZD;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,EAC7B,eAAe,GAAE,uBAAuB,EAAO,EAC/C,OAAO,GAAE,gBAAqB,EAC9B,aAAa,GAAE,OAAe,GAC7B,OAAO,CAAC,WAAW,CAAC,CA6KtB;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzE"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from "@workglow/util/schema";
|
|
7
|
+
import { BaseTabularStorage, ClientProvidedKeysOption, KeyGenerationStrategy, AnyTabularStorage, AutoGeneratedKeys, CoveringIndexQueryOptions, DeleteSearchCriteria, InsertEntity, QueryOptions, SearchCriteria, SimplifyPrimaryKey, TabularChangePayload, TabularSubscribeOptions } from "@workglow/storage";
|
|
8
|
+
import { MigrationOptions } from "./IndexedDbTable";
|
|
9
|
+
export declare const IDB_TABULAR_REPOSITORY: import("@workglow/util").ServiceToken<AnyTabularStorage>;
|
|
10
|
+
/**
|
|
11
|
+
* A tabular repository implementation using IndexedDB for browser-based storage.
|
|
12
|
+
*
|
|
13
|
+
* @template Schema - The schema definition for the entity
|
|
14
|
+
* @template PrimaryKeyNames - Array of property names that form the primary key
|
|
15
|
+
*/
|
|
16
|
+
export declare class IndexedDbTabularStorage<Schema extends DataPortSchemaObject, PrimaryKeyNames extends ReadonlyArray<keyof Schema["properties"]>, Entity = FromSchema<Schema, TypedArraySchemaOptions>, PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>, Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>, InsertType extends InsertEntity<Entity, AutoGeneratedKeys<Schema>> = InsertEntity<Entity, AutoGeneratedKeys<Schema>>> extends BaseTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, Value, InsertType> {
|
|
17
|
+
table: string;
|
|
18
|
+
/** Promise that resolves to the IndexedDB database instance */
|
|
19
|
+
private db;
|
|
20
|
+
/** Promise to track ongoing database setup to prevent concurrent setup calls */
|
|
21
|
+
private setupPromise;
|
|
22
|
+
/** Migration options for database schema changes */
|
|
23
|
+
private migrationOptions;
|
|
24
|
+
/** Shared hybrid subscription manager */
|
|
25
|
+
private hybridManager;
|
|
26
|
+
/** Hybrid subscription options */
|
|
27
|
+
private readonly hybridOptions;
|
|
28
|
+
/**
|
|
29
|
+
* Indexes safe for cursor-based narrowing. An IDB index excludes records
|
|
30
|
+
* whose keyPath has any undefined component, so iterating an index can miss
|
|
31
|
+
* records when an indexed column is optional in the schema. Native
|
|
32
|
+
* `count(range)` and cursor scans are only correct over indexes whose every
|
|
33
|
+
* column is required. Computed lazily on first use.
|
|
34
|
+
*/
|
|
35
|
+
private cursorSafeIndexes;
|
|
36
|
+
/**
|
|
37
|
+
* Creates a new IndexedDB-based tabular repository.
|
|
38
|
+
* @param table - Name of the IndexedDB store to use.
|
|
39
|
+
* @param schema - Schema defining the structure of the entity
|
|
40
|
+
* @param primaryKeyNames - Array of property names that form the primary key
|
|
41
|
+
* @param indexes - Array of columns or column arrays to make searchable. Each string or single column creates a single-column index,
|
|
42
|
+
* while each array creates a compound index with columns in the specified order.
|
|
43
|
+
* @param migrationOptions - Options for handling database schema migrations
|
|
44
|
+
* @param clientProvidedKeys - How to handle client-provided values for auto-generated keys
|
|
45
|
+
*/
|
|
46
|
+
constructor(table: string | undefined, schema: Schema, primaryKeyNames: PrimaryKeyNames, indexes?: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[], migrationOptions?: MigrationOptions & {
|
|
47
|
+
readonly useBroadcastChannel?: boolean;
|
|
48
|
+
readonly backupPollingIntervalMs?: number;
|
|
49
|
+
}, clientProvidedKeys?: ClientProvidedKeysOption);
|
|
50
|
+
private getDb;
|
|
51
|
+
/**
|
|
52
|
+
* Sets up the IndexedDB database table with the required schema and indexes.
|
|
53
|
+
* Must be called before using any other methods.
|
|
54
|
+
*/
|
|
55
|
+
setupDatabase(): Promise<void>;
|
|
56
|
+
private performSetup;
|
|
57
|
+
/**
|
|
58
|
+
* Generates a key value for UUID keys
|
|
59
|
+
* Integer autoincrement keys are handled by IndexedDB's autoIncrement
|
|
60
|
+
* @param columnName - Name of the column to generate a key for
|
|
61
|
+
* @param strategy - The generation strategy to use
|
|
62
|
+
* @returns The generated key value
|
|
63
|
+
*/
|
|
64
|
+
protected generateKeyValue(columnName: string, strategy: KeyGenerationStrategy): string | number;
|
|
65
|
+
/**
|
|
66
|
+
* Stores a row in the repository.
|
|
67
|
+
* @param record - The entity to store (may be missing auto-generated keys).
|
|
68
|
+
* @returns The stored entity
|
|
69
|
+
* @emits put - Emitted when the value is successfully stored
|
|
70
|
+
*/
|
|
71
|
+
put(record: InsertType): Promise<Entity>;
|
|
72
|
+
/**
|
|
73
|
+
* Stores multiple rows in the repository in a bulk operation.
|
|
74
|
+
* @param records - Array of entities to store (may be missing auto-generated keys).
|
|
75
|
+
* @returns Array of stored entities
|
|
76
|
+
* @emits put - Emitted for each record successfully stored
|
|
77
|
+
*/
|
|
78
|
+
putBulk(records: InsertType[]): Promise<Entity[]>;
|
|
79
|
+
protected getPrimaryKeyAsOrderedArray(key: PrimaryKey): (string | number | boolean | Uint8Array<ArrayBufferLike> | null)[];
|
|
80
|
+
private getIndexedKey;
|
|
81
|
+
/**
|
|
82
|
+
* Retrieves a value from the repository by its key.
|
|
83
|
+
* @param key - The key object.
|
|
84
|
+
* @returns The value object or undefined if not found.
|
|
85
|
+
* @emits get - Emitted when the value is successfully retrieved
|
|
86
|
+
*/
|
|
87
|
+
get(key: PrimaryKey): Promise<Entity | undefined>;
|
|
88
|
+
/**
|
|
89
|
+
* Returns an array of all entries in the repository, with optional ordering, offset, and limit.
|
|
90
|
+
* @param options - Optional ordering, limit, and offset options
|
|
91
|
+
* @returns Array of all entries in the repository.
|
|
92
|
+
*/
|
|
93
|
+
getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined>;
|
|
94
|
+
/**
|
|
95
|
+
* Deletes a row from the repository.
|
|
96
|
+
* @param key - The key object to delete.
|
|
97
|
+
*/
|
|
98
|
+
delete(key: PrimaryKey): Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Deletes all records from the repository.
|
|
101
|
+
* @emits clearall - Emitted when all values are deleted
|
|
102
|
+
*/
|
|
103
|
+
deleteAll(): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Returns the total number of rows in the repository.
|
|
106
|
+
* @returns Count of stored items.
|
|
107
|
+
*/
|
|
108
|
+
size(): Promise<number>;
|
|
109
|
+
/**
|
|
110
|
+
* Returns the subset of configured indexes safe to use for cursor-based
|
|
111
|
+
* narrowing — those whose every column is required by the schema. IDB
|
|
112
|
+
* excludes records with undefined keyPath components from indexes, so
|
|
113
|
+
* iterating an index would silently miss records when any indexed column
|
|
114
|
+
* is optional.
|
|
115
|
+
*/
|
|
116
|
+
private getCursorSafeIndexes;
|
|
117
|
+
/**
|
|
118
|
+
* Picks the best index to narrow a criteria-based scan, preferring indexes
|
|
119
|
+
* whose prefix covers every criteria column (so callers can use the native
|
|
120
|
+
* `count()` API) and falling back to the longest equality-prefix match.
|
|
121
|
+
*
|
|
122
|
+
* Upper bound uses `[]` as a sentinel that compares greater than any
|
|
123
|
+
* primitive value at the next index position (per IndexedDB key ordering:
|
|
124
|
+
* array > binary > string > date > number). Assumes subsequent indexed
|
|
125
|
+
* columns hold primitive values — array-valued columns would compare
|
|
126
|
+
* greater than `[]` and slip outside the range.
|
|
127
|
+
*/
|
|
128
|
+
private createIndexedRange;
|
|
129
|
+
/**
|
|
130
|
+
* Counts rows matching the specified search criteria.
|
|
131
|
+
*
|
|
132
|
+
* Uses the native `count()` API when an index prefix covers every criteria
|
|
133
|
+
* column. Otherwise narrows via the longest matching index prefix and
|
|
134
|
+
* filters the remaining columns during cursor iteration. Falls back to a
|
|
135
|
+
* full store scan only when no index applies.
|
|
136
|
+
*/
|
|
137
|
+
count(criteria?: SearchCriteria<Entity>): Promise<number>;
|
|
138
|
+
/**
|
|
139
|
+
* Fetches a page of records from the repository.
|
|
140
|
+
* @param offset - Number of records to skip
|
|
141
|
+
* @param limit - Maximum number of records to return
|
|
142
|
+
* @returns Array of entities or undefined if no records found
|
|
143
|
+
*/
|
|
144
|
+
getBulk(offset: number, limit: number): Promise<Entity[] | undefined>;
|
|
145
|
+
/**
|
|
146
|
+
* Checks if a record matches all criteria conditions.
|
|
147
|
+
* @param record - The record to check
|
|
148
|
+
* @param criteria - The search criteria
|
|
149
|
+
* @returns true if all conditions match
|
|
150
|
+
*/
|
|
151
|
+
private matchesCriteria;
|
|
152
|
+
/**
|
|
153
|
+
* Deletes all entries matching the specified search criteria.
|
|
154
|
+
* Supports multiple columns with optional comparison operators.
|
|
155
|
+
*
|
|
156
|
+
* @param criteria - Object with column names as keys and values or SearchConditions
|
|
157
|
+
*/
|
|
158
|
+
deleteSearch(criteria: DeleteSearchCriteria<Entity>): Promise<void>;
|
|
159
|
+
private getEqualityCriterionValue;
|
|
160
|
+
private compareByOrder;
|
|
161
|
+
private createIndexedQuery;
|
|
162
|
+
/**
|
|
163
|
+
* Queries entries matching the specified search criteria with optional ordering, limit, and offset.
|
|
164
|
+
*
|
|
165
|
+
* @param criteria - Object with column names as keys and values or SearchConditions
|
|
166
|
+
* @param options - Optional ordering, limit, and offset options
|
|
167
|
+
* @returns Array of matching entities or undefined if no matches found
|
|
168
|
+
*/
|
|
169
|
+
query(criteria: SearchCriteria<Entity>, options?: QueryOptions<Entity>): Promise<Entity[] | undefined>;
|
|
170
|
+
/**
|
|
171
|
+
* Strict, projected query served entirely by a covering compound index.
|
|
172
|
+
* Uses `openKeyCursor` — never reads `cursor.value` — so only the index key
|
|
173
|
+
* bytes are loaded into memory. Ideal for tables with large value blobs.
|
|
174
|
+
*
|
|
175
|
+
* Throws {@link CoveringIndexMissingError} when no registered index can serve
|
|
176
|
+
* the request (i.e. the index does not cover all select + orderBy columns).
|
|
177
|
+
*/
|
|
178
|
+
queryIndex<K extends keyof Entity & string>(criteria: SearchCriteria<Entity>, options: CoveringIndexQueryOptions<Entity, K>): Promise<Pick<Entity, K>[]>;
|
|
179
|
+
/**
|
|
180
|
+
* Gets or creates the shared hybrid subscription manager.
|
|
181
|
+
* This ensures all subscriptions share a single manager.
|
|
182
|
+
*/
|
|
183
|
+
private getHybridManager;
|
|
184
|
+
/**
|
|
185
|
+
* Subscribes to changes in the repository.
|
|
186
|
+
* Uses polling since IndexedDB has no native cross-tab change notifications.
|
|
187
|
+
*
|
|
188
|
+
* @param callback - Function called when a change occurs
|
|
189
|
+
* @param options - Optional subscription options including polling interval
|
|
190
|
+
* @returns Unsubscribe function
|
|
191
|
+
*/
|
|
192
|
+
subscribeToChanges(callback: (change: TabularChangePayload<Entity>) => void, options?: TabularSubscribeOptions): () => void;
|
|
193
|
+
/**
|
|
194
|
+
* Destroys this repository and frees up resources.
|
|
195
|
+
*/
|
|
196
|
+
destroy(): void;
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=IndexedDbTabularStorage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexedDbTabularStorage.d.ts","sourceRoot":"","sources":["../../src/storage/IndexedDbTabularStorage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { DataPortSchemaObject, FromSchema, TypedArray, TypedArrayConstructor, TypedArraySchemaOptions } from "@workglow/util/schema";
|
|
7
|
+
import { IndexedDbTabularStorage } from "./IndexedDbTabularStorage";
|
|
8
|
+
import type { ClientProvidedKeysOption, AnyVectorStorage, HybridSearchOptions, IVectorStorage, VectorSearchOptions } from "@workglow/storage";
|
|
9
|
+
import type { MigrationOptions } from "./IndexedDbTable";
|
|
10
|
+
export declare const IDB_VECTOR_REPOSITORY: import("@workglow/util").ServiceToken<AnyVectorStorage>;
|
|
11
|
+
/**
|
|
12
|
+
* IndexedDB vector storage implementation.
|
|
13
|
+
* Extends IndexedDbTabularStorage for storage.
|
|
14
|
+
* Suitable for browser applications that need persistent vector storage.
|
|
15
|
+
* No vector serialization needed since IndexedDB supports TypedArrays
|
|
16
|
+
* natively via structured clone.
|
|
17
|
+
*
|
|
18
|
+
* @template Schema - The schema definition for the entity
|
|
19
|
+
* @template PrimaryKeyNames - The primary key names
|
|
20
|
+
* @template Metadata - The metadata type for the vector
|
|
21
|
+
* @template VectorCtor - Constructor for stored vectors (default {@link typeof Float32Array})
|
|
22
|
+
* @template Entity - The entity type
|
|
23
|
+
*/
|
|
24
|
+
export declare class IndexedDbVectorStorage<Schema extends DataPortSchemaObject, PrimaryKeyNames extends ReadonlyArray<keyof Schema["properties"]>, Metadata extends Record<string, unknown> = Record<string, unknown>, Entity = FromSchema<Schema, TypedArraySchemaOptions>> extends IndexedDbTabularStorage<Schema, PrimaryKeyNames, Entity> implements IVectorStorage<Metadata, Schema, Entity, PrimaryKeyNames> {
|
|
25
|
+
private vectorDimensions;
|
|
26
|
+
private vectorPropertyName;
|
|
27
|
+
private metadataPropertyName;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a new IndexedDB vector storage
|
|
30
|
+
* @param table - The name of the IndexedDB store (defaults to 'vectors')
|
|
31
|
+
* @param schema - The schema definition for the entity
|
|
32
|
+
* @param primaryKeyNames - Array of property names that form the primary key
|
|
33
|
+
* @param indexes - Array of columns or column arrays to make searchable
|
|
34
|
+
* @param dimensions - The number of dimensions of the vector
|
|
35
|
+
* @param _vectorCtor - TypedArray constructor (unused, IndexedDB stores typed arrays natively)
|
|
36
|
+
* @param migrationOptions - Options for handling database schema migrations
|
|
37
|
+
* @param clientProvidedKeys - How to handle client-provided values for auto-generated keys
|
|
38
|
+
*/
|
|
39
|
+
constructor(table: string | undefined, schema: Schema, primaryKeyNames: PrimaryKeyNames, indexes: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[] | undefined, dimensions: number, _vectorCtor?: TypedArrayConstructor, migrationOptions?: MigrationOptions, clientProvidedKeys?: ClientProvidedKeysOption);
|
|
40
|
+
/**
|
|
41
|
+
* Get the vector dimensions
|
|
42
|
+
* @returns The vector dimensions
|
|
43
|
+
*/
|
|
44
|
+
getVectorDimensions(): number;
|
|
45
|
+
similaritySearch(query: TypedArray, options?: VectorSearchOptions<Record<string, unknown>>): Promise<(Entity & {
|
|
46
|
+
score: number;
|
|
47
|
+
})[]>;
|
|
48
|
+
hybridSearch(query: TypedArray, options: HybridSearchOptions<Record<string, unknown>>): Promise<(Entity & {
|
|
49
|
+
score: number;
|
|
50
|
+
})[]>;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=IndexedDbVectorStorage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexedDbVectorStorage.d.ts","sourceRoot":"","sources":["../../src/storage/IndexedDbVectorStorage.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,EACV,UAAU,EACV,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,OAAO,KAAK,EACV,wBAAwB,EACxB,gBAAgB,EAChB,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,eAAO,MAAM,qBAAqB,yDAEjC,CAAC;AAiCF;;;;;;;;;;;;GAYG;AACH,qBAAa,sBAAsB,CACjC,MAAM,SAAS,oBAAoB,EACnC,eAAe,SAAS,aAAa,CAAC,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC,EACjE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClE,MAAM,GAAG,UAAU,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAEpD,SAAQ,uBAAuB,CAAC,MAAM,EAAE,eAAe,EAAE,MAAM,CAC/D,YAAW,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC;IAEpE,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,kBAAkB,CAAe;IACzC,OAAO,CAAC,oBAAoB,CAA2B;IAEvD;;;;;;;;;;OAUG;IACH,YACE,KAAK,EAAE,MAAM,YAAY,EACzB,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,SAAS,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,YAAK,EACrF,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,qBAAoC,EACjD,gBAAgB,GAAE,gBAAqB,EACvC,kBAAkB,GAAE,wBAAuC,EAa5D;IAED;;;OAGG;IACH,mBAAmB,IAAI,MAAM,CAE5B;IAEK,gBAAgB,CACpB,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,mBAAmB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM;eAGnB,MAAM;UAmC9C;IAEK,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,mBAAmB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;eArClD,MAAM;UAsF9C;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/storage/browser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,UAAU,CAAC"}
|