From 5b901355331d6b2da5e4cfaf73c33e4de745a500 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Thu, 13 Aug 2026 08:16:48 +0200 Subject: [PATCH] refactor: share IndexedDB transaction execution --- js/db.js | 42 ++----- js/db_shared.js | 7 ++ js/state-saver.worker.js | 7 +- src/db.ts | 115 +++++++++++------- src/db_shared.ts | 18 +++ src/state-saver.worker.ts | 26 ++-- tests/test_db_characterization.mjs | 18 ++- ...iant_and_get_all_keys_characterization.mjs | 5 +- 8 files changed, 146 insertions(+), 92 deletions(-) diff --git a/js/db.js b/js/db.js index ad28417..90179c0 100644 --- a/js/db.js +++ b/js/db.js @@ -1,70 +1,46 @@ import { createModuleLogger } from "./log_system/log_funcs.js"; -import { ALL_STORES, IMAGE_STORE, STATE_STORE, createDBRequest, openLayerForgeDB, } from "./db_shared.js"; +import { ALL_STORES, IMAGE_STORE, STATE_STORE, executeDBStoreRequest, } from "./db_shared.js"; const log = createModuleLogger('db'); export async function getCanvasState(id) { log.info(`Getting state for id: ${id}`); - const db = await openLayerForgeDB(log, ALL_STORES); - const transaction = db.transaction([STATE_STORE.name], 'readonly'); - const store = transaction.objectStore(STATE_STORE.name); - const result = await createDBRequest(store, 'get', id, "Error getting canvas state", log); + const result = await executeDBStoreRequest(log, ALL_STORES, STATE_STORE, 'readonly', 'get', id, "Error getting canvas state"); log.debug(`Get success for id: ${id}`, result ? 'found' : 'not found'); return result ? result.state : null; } export async function setCanvasState(id, state) { log.info(`Setting state for id: ${id}`); - const db = await openLayerForgeDB(log, ALL_STORES); - const transaction = db.transaction([STATE_STORE.name], 'readwrite'); - const store = transaction.objectStore(STATE_STORE.name); - await createDBRequest(store, 'put', { id, state }, "Error setting canvas state", log); + await executeDBStoreRequest(log, ALL_STORES, STATE_STORE, 'readwrite', 'put', { id, state }, "Error setting canvas state"); log.debug(`Set success for id: ${id}`); } export async function removeCanvasState(id) { log.info(`Removing state for id: ${id}`); - const db = await openLayerForgeDB(log, ALL_STORES); - const transaction = db.transaction([STATE_STORE.name], 'readwrite'); - const store = transaction.objectStore(STATE_STORE.name); - await createDBRequest(store, 'delete', id, "Error removing canvas state", log); + await executeDBStoreRequest(log, ALL_STORES, STATE_STORE, 'readwrite', 'delete', id, "Error removing canvas state"); log.debug(`Remove success for id: ${id}`); } export async function saveImage(imageId, imageSrc) { log.info(`Saving image with id: ${imageId}`); - const db = await openLayerForgeDB(log, ALL_STORES); - const transaction = db.transaction([IMAGE_STORE.name], 'readwrite'); - const store = transaction.objectStore(IMAGE_STORE.name); - await createDBRequest(store, 'put', { imageId, imageSrc }, "Error saving image", log); + await executeDBStoreRequest(log, ALL_STORES, IMAGE_STORE, 'readwrite', 'put', { imageId, imageSrc }, "Error saving image"); log.debug(`Image saved successfully for id: ${imageId}`); } export async function getImage(imageId) { log.info(`Getting image with id: ${imageId}`); - const db = await openLayerForgeDB(log, ALL_STORES); - const transaction = db.transaction([IMAGE_STORE.name], 'readonly'); - const store = transaction.objectStore(IMAGE_STORE.name); - const result = await createDBRequest(store, 'get', imageId, "Error getting image", log); + const result = await executeDBStoreRequest(log, ALL_STORES, IMAGE_STORE, 'readonly', 'get', imageId, "Error getting image"); log.debug(`Get image success for id: ${imageId}`, result ? 'found' : 'not found'); return result ? result.imageSrc : null; } export async function removeImage(imageId) { log.info(`Removing image with id: ${imageId}`); - const db = await openLayerForgeDB(log, ALL_STORES); - const transaction = db.transaction([IMAGE_STORE.name], 'readwrite'); - const store = transaction.objectStore(IMAGE_STORE.name); - await createDBRequest(store, 'delete', imageId, "Error removing image", log); + await executeDBStoreRequest(log, ALL_STORES, IMAGE_STORE, 'readwrite', 'delete', imageId, "Error removing image"); log.debug(`Remove image success for id: ${imageId}`); } export async function getAllImageIds() { log.info("Getting all image IDs..."); - const db = await openLayerForgeDB(log, ALL_STORES); - const transaction = db.transaction([IMAGE_STORE.name], 'readonly'); - const store = transaction.objectStore(IMAGE_STORE.name); - const imageIds = await createDBRequest(store, 'getAllKeys', null, "Error getting all image IDs", log); + const imageIds = await executeDBStoreRequest(log, ALL_STORES, IMAGE_STORE, 'readonly', 'getAllKeys', null, "Error getting all image IDs"); log.debug(`Found ${imageIds.length} image IDs in database`); return imageIds; } export async function clearAllCanvasStates() { log.info("Clearing all canvas states..."); - const db = await openLayerForgeDB(log, ALL_STORES); - const transaction = db.transaction([STATE_STORE.name], 'readwrite'); - const store = transaction.objectStore(STATE_STORE.name); - await createDBRequest(store, 'clear', null, "Error clearing canvas states", log); + await executeDBStoreRequest(log, ALL_STORES, STATE_STORE, 'readwrite', 'clear', null, "Error clearing canvas states"); log.info("All canvas states cleared successfully."); } diff --git a/js/db_shared.js b/js/db_shared.js index c9845e2..164b737 100644 --- a/js/db_shared.js +++ b/js/db_shared.js @@ -45,6 +45,13 @@ export function createDBRequest(store, operation, data, errorMessage, log) { }; }); } +/** Open the shared database, execute one store operation, and preserve request error handling. */ +export async function executeDBStoreRequest(log, stores, storeDefinition, mode, operation, data, errorMessage, openOptions = {}) { + const database = await openLayerForgeDB(log, stores, openOptions); + const transaction = database.transaction([storeDefinition.name], mode); + const store = transaction.objectStore(storeDefinition.name); + return createDBRequest(store, operation, data, errorMessage, log); +} /** Open the shared LayerForge database and create only the stores needed by the caller. */ export function openLayerForgeDB(log, stores, options = {}) { return new Promise((resolve, reject) => { diff --git a/js/state-saver.worker.js b/js/state-saver.worker.js index ecd7923..bfa6260 100644 --- a/js/state-saver.worker.js +++ b/js/state-saver.worker.js @@ -1,4 +1,4 @@ -import { STATE_STORE, createDBRequest, openLayerForgeDB } from './db_shared.js'; +import { STATE_STORE, executeDBStoreRequest } from './db_shared.js'; console.log('[StateWorker] Worker script loaded and running.'); function log(...args) { console.log('[StateWorker]', ...args); @@ -8,15 +8,12 @@ function error(...args) { } const dbLogger = { info: log, error }; async function setCanvasState(id, state) { - const db = await openLayerForgeDB(dbLogger, [STATE_STORE], { + await executeDBStoreRequest(dbLogger, [STATE_STORE], STATE_STORE, 'readwrite', 'put', { id, state }, "Error setting canvas state", { openingMessage: null, upgradingMessage: 'Upgrading IndexedDB in worker...', successMessage: 'IndexedDB opened successfully in worker.', logStoreCreation: false, }); - const transaction = db.transaction([STATE_STORE.name], 'readwrite'); - const store = transaction.objectStore(STATE_STORE.name); - await createDBRequest(store, 'put', { id, state }, "Error setting canvas state", dbLogger); } self.onmessage = async function (e) { log('Message received from main thread:', e.data ? 'data received' : 'no data'); diff --git a/src/db.ts b/src/db.ts index d53f577..b7dfcc8 100644 --- a/src/db.ts +++ b/src/db.ts @@ -3,8 +3,7 @@ import { ALL_STORES, IMAGE_STORE, STATE_STORE, - createDBRequest, - openLayerForgeDB, + executeDBStoreRequest, } from "./db_shared.js"; const log = createModuleLogger('db'); @@ -21,83 +20,115 @@ interface CanvasImageDB { export async function getCanvasState(id: string): Promise { log.info(`Getting state for id: ${id}`); - const db = await openLayerForgeDB(log, ALL_STORES); - const transaction = db.transaction([STATE_STORE.name], 'readonly'); - const store = transaction.objectStore(STATE_STORE.name); - - const result = await createDBRequest(store, 'get', id, "Error getting canvas state", log) as CanvasStateDB; + const result = await executeDBStoreRequest( + log, + ALL_STORES, + STATE_STORE, + 'readonly', + 'get', + id, + "Error getting canvas state" + ); log.debug(`Get success for id: ${id}`, result ? 'found' : 'not found'); return result ? result.state : null; } export async function setCanvasState(id: string, state: any): Promise { log.info(`Setting state for id: ${id}`); - const db = await openLayerForgeDB(log, ALL_STORES); - const transaction = db.transaction([STATE_STORE.name], 'readwrite'); - const store = transaction.objectStore(STATE_STORE.name); - - await createDBRequest(store, 'put', {id, state}, "Error setting canvas state", log); + await executeDBStoreRequest( + log, + ALL_STORES, + STATE_STORE, + 'readwrite', + 'put', + {id, state}, + "Error setting canvas state" + ); log.debug(`Set success for id: ${id}`); } export async function removeCanvasState(id: string): Promise { log.info(`Removing state for id: ${id}`); - const db = await openLayerForgeDB(log, ALL_STORES); - const transaction = db.transaction([STATE_STORE.name], 'readwrite'); - const store = transaction.objectStore(STATE_STORE.name); - - await createDBRequest(store, 'delete', id, "Error removing canvas state", log); + await executeDBStoreRequest( + log, + ALL_STORES, + STATE_STORE, + 'readwrite', + 'delete', + id, + "Error removing canvas state" + ); log.debug(`Remove success for id: ${id}`); } export async function saveImage(imageId: string, imageSrc: string | ImageBitmap): Promise { log.info(`Saving image with id: ${imageId}`); - const db = await openLayerForgeDB(log, ALL_STORES); - const transaction = db.transaction([IMAGE_STORE.name], 'readwrite'); - const store = transaction.objectStore(IMAGE_STORE.name); - - await createDBRequest(store, 'put', {imageId, imageSrc}, "Error saving image", log); + await executeDBStoreRequest( + log, + ALL_STORES, + IMAGE_STORE, + 'readwrite', + 'put', + {imageId, imageSrc}, + "Error saving image" + ); log.debug(`Image saved successfully for id: ${imageId}`); } export async function getImage(imageId: string): Promise { log.info(`Getting image with id: ${imageId}`); - const db = await openLayerForgeDB(log, ALL_STORES); - const transaction = db.transaction([IMAGE_STORE.name], 'readonly'); - const store = transaction.objectStore(IMAGE_STORE.name); - - const result = await createDBRequest(store, 'get', imageId, "Error getting image", log) as CanvasImageDB; + const result = await executeDBStoreRequest( + log, + ALL_STORES, + IMAGE_STORE, + 'readonly', + 'get', + imageId, + "Error getting image" + ); log.debug(`Get image success for id: ${imageId}`, result ? 'found' : 'not found'); return result ? result.imageSrc : null; } export async function removeImage(imageId: string): Promise { log.info(`Removing image with id: ${imageId}`); - const db = await openLayerForgeDB(log, ALL_STORES); - const transaction = db.transaction([IMAGE_STORE.name], 'readwrite'); - const store = transaction.objectStore(IMAGE_STORE.name); - - await createDBRequest(store, 'delete', imageId, "Error removing image", log); + await executeDBStoreRequest( + log, + ALL_STORES, + IMAGE_STORE, + 'readwrite', + 'delete', + imageId, + "Error removing image" + ); log.debug(`Remove image success for id: ${imageId}`); } export async function getAllImageIds(): Promise { log.info("Getting all image IDs..."); - const db = await openLayerForgeDB(log, ALL_STORES); - const transaction = db.transaction([IMAGE_STORE.name], 'readonly'); - const store = transaction.objectStore(IMAGE_STORE.name); - - const imageIds = await createDBRequest(store, 'getAllKeys', null, "Error getting all image IDs", log) as string[]; + const imageIds = await executeDBStoreRequest( + log, + ALL_STORES, + IMAGE_STORE, + 'readonly', + 'getAllKeys', + null, + "Error getting all image IDs" + ); log.debug(`Found ${imageIds.length} image IDs in database`); return imageIds; } export async function clearAllCanvasStates(): Promise { log.info("Clearing all canvas states..."); - const db = await openLayerForgeDB(log, ALL_STORES); - const transaction = db.transaction([STATE_STORE.name], 'readwrite'); - const store = transaction.objectStore(STATE_STORE.name); - - await createDBRequest(store, 'clear', null, "Error clearing canvas states", log); + await executeDBStoreRequest( + log, + ALL_STORES, + STATE_STORE, + 'readwrite', + 'clear', + null, + "Error clearing canvas states" + ); log.info("All canvas states cleared successfully."); } diff --git a/src/db_shared.ts b/src/db_shared.ts index af05289..9ff1af8 100644 --- a/src/db_shared.ts +++ b/src/db_shared.ts @@ -78,6 +78,24 @@ export function createDBRequest( }); } +/** Open the shared database, execute one store operation, and preserve request error handling. */ +export async function executeDBStoreRequest( + log: DBLogger, + stores: readonly DBStoreDefinition[], + storeDefinition: DBStoreDefinition, + mode: IDBTransactionMode, + operation: DBRequestOperation, + data: any, + errorMessage: string, + openOptions: DBOpenOptions = {}, +): Promise { + const database = await openLayerForgeDB(log, stores, openOptions); + const transaction = database.transaction([storeDefinition.name], mode); + const store = transaction.objectStore(storeDefinition.name); + + return createDBRequest(store, operation, data, errorMessage, log) as Promise; +} + /** Open the shared LayerForge database and create only the stores needed by the caller. */ export function openLayerForgeDB( log: DBLogger, diff --git a/src/state-saver.worker.ts b/src/state-saver.worker.ts index ddeb06f..57aaa6e 100644 --- a/src/state-saver.worker.ts +++ b/src/state-saver.worker.ts @@ -1,4 +1,4 @@ -import {STATE_STORE, createDBRequest, openLayerForgeDB} from './db_shared.js'; +import {STATE_STORE, executeDBStoreRequest} from './db_shared.js'; console.log('[StateWorker] Worker script loaded and running.'); @@ -13,15 +13,21 @@ function error(...args: any[]): void { const dbLogger = {info: log, error}; async function setCanvasState(id: string, state: any): Promise { - const db = await openLayerForgeDB(dbLogger, [STATE_STORE], { - openingMessage: null, - upgradingMessage: 'Upgrading IndexedDB in worker...', - successMessage: 'IndexedDB opened successfully in worker.', - logStoreCreation: false, - }); - const transaction = db.transaction([STATE_STORE.name], 'readwrite'); - const store = transaction.objectStore(STATE_STORE.name); - await createDBRequest(store, 'put', {id, state}, "Error setting canvas state", dbLogger); + await executeDBStoreRequest( + dbLogger, + [STATE_STORE], + STATE_STORE, + 'readwrite', + 'put', + {id, state}, + "Error setting canvas state", + { + openingMessage: null, + upgradingMessage: 'Upgrading IndexedDB in worker...', + successMessage: 'IndexedDB opened successfully in worker.', + logStoreCreation: false, + } + ); } self.onmessage = async function(e: MessageEvent<{ state: any, nodeId: string }>): Promise { diff --git a/tests/test_db_characterization.mjs b/tests/test_db_characterization.mjs index a8b4ae0..7f42d66 100644 --- a/tests/test_db_characterization.mjs +++ b/tests/test_db_characterization.mjs @@ -173,8 +173,24 @@ test('the worker uses shared database infrastructure for canvas-state persistenc assert.match(sharedSource, /export const IMAGE_STORE_NAME = 'CanvasImages'/); assert.match(sharedSource, /export const DB_VERSION = 3/); assert.match(workerSource, /from '\.\/db_shared\.js'/); - assert.match(workerSource, /openLayerForgeDB\(dbLogger, \[STATE_STORE\]/); + assert.match(workerSource, /executeDBStoreRequest\(\s*dbLogger,\s*\[STATE_STORE\]/s); assert.doesNotMatch(workerSource, /const DB_NAME/); assert.doesNotMatch(workerSource, /function openDB/); assert.doesNotMatch(workerSource, /function createDBRequest/); }); + +test('database callers share one transaction helper while preserving store and worker options', async () => { + const [dbSource, workerSource] = await Promise.all([ + readFile(new URL('../src/db.ts', import.meta.url), 'utf8'), + readFile(new URL('../src/state-saver.worker.ts', import.meta.url), 'utf8'), + ]); + + assert.equal((dbSource.match(/executeDBStoreRequest\(/); + assert.doesNotMatch(workerSource, /const transaction = db\.transaction\(/); + assert.doesNotMatch(workerSource, /const store = transaction\.objectStore\(/); + assert.match(workerSource, /openingMessage: null/); + assert.match(workerSource, /logStoreCreation: false/); +}); diff --git a/tests/test_variant_and_get_all_keys_characterization.mjs b/tests/test_variant_and_get_all_keys_characterization.mjs index 832b7ea..72173a0 100644 --- a/tests/test_variant_and_get_all_keys_characterization.mjs +++ b/tests/test_variant_and_get_all_keys_characterization.mjs @@ -18,7 +18,10 @@ test('canvas export uses the canonical blob variant union', () => { test('image ID lookup uses the shared IndexedDB getAllKeys operation', () => { assert.match(sharedSource, /export type DBRequestOperation = 'get' \| 'put' \| 'delete' \| 'clear' \| 'getAllKeys';/); + assert.match(sharedSource, /export async function executeDBStoreRequest\(/); + assert.match(sharedSource, /const database = await openLayerForgeDB\(log, stores, openOptions\)/); + assert.match(sharedSource, /return createDBRequest\(store, operation, data, errorMessage, log\)/); assert.match(sharedSource, /case 'getAllKeys':[\s\S]*?request = store\.getAllKeys\(\);/); - assert.match(dbSource, /createDBRequest\(store, 'getAllKeys', null, "Error getting all image IDs", log\)/); + assert.match(dbSource, /executeDBStoreRequest\([\s\S]*?'getAllKeys',[\s\S]*?"Error getting all image IDs"/); assert.doesNotMatch(dbSource, /const request = store\.getAllKeys\(\);/); });