refactor: share IndexedDB transaction execution

This commit is contained in:
Dariusz L
2026-08-13 08:16:48 +02:00
parent 38886f346d
commit 5b90135533
8 changed files with 146 additions and 92 deletions
+9 -33
View File
@@ -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.");
}
+7
View File
@@ -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) => {
+2 -5
View File
@@ -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');
+73 -42
View File
@@ -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<any | null> {
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<CanvasStateDB | undefined>(
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<void> {
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<void>(
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<void> {
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<void>(
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<void> {
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<void>(
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<string | ImageBitmap | null> {
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<CanvasImageDB | undefined>(
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<void> {
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<void>(
log,
ALL_STORES,
IMAGE_STORE,
'readwrite',
'delete',
imageId,
"Error removing image"
);
log.debug(`Remove image success for id: ${imageId}`);
}
export async function getAllImageIds(): Promise<string[]> {
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<string[]>(
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<void> {
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<void>(
log,
ALL_STORES,
STATE_STORE,
'readwrite',
'clear',
null,
"Error clearing canvas states"
);
log.info("All canvas states cleared successfully.");
}
+18
View File
@@ -78,6 +78,24 @@ export function createDBRequest(
});
}
/** Open the shared database, execute one store operation, and preserve request error handling. */
export async function executeDBStoreRequest<T>(
log: DBLogger,
stores: readonly DBStoreDefinition[],
storeDefinition: DBStoreDefinition,
mode: IDBTransactionMode,
operation: DBRequestOperation,
data: any,
errorMessage: string,
openOptions: DBOpenOptions = {},
): Promise<T> {
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<T>;
}
/** Open the shared LayerForge database and create only the stores needed by the caller. */
export function openLayerForgeDB(
log: DBLogger,
+16 -10
View File
@@ -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<void> {
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<void>(
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<void> {
+17 -1
View File
@@ -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<void>\(\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</g) ?? []).length, 8);
assert.doesNotMatch(dbSource, /const transaction = db\.transaction\(/);
assert.doesNotMatch(dbSource, /const store = transaction\.objectStore\(/);
assert.match(workerSource, /executeDBStoreRequest<void>\(/);
assert.doesNotMatch(workerSource, /const transaction = db\.transaction\(/);
assert.doesNotMatch(workerSource, /const store = transaction\.objectStore\(/);
assert.match(workerSource, /openingMessage: null/);
assert.match(workerSource, /logStoreCreation: false/);
});
@@ -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<T>\(/);
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<string\[\]>\([\s\S]*?'getAllKeys',[\s\S]*?"Error getting all image IDs"/);
assert.doesNotMatch(dbSource, /const request = store\.getAllKeys\(\);/);
});