aether-shards/src/core/Persistence.js

162 lines
4.1 KiB
JavaScript

/**
* @typedef {import("./types.js").RunData} RunData
* @typedef {import("../units/types.js").RosterSaveData} RosterSaveData
*/
/**
* Persistence.js
* Handles asynchronous saving and loading using IndexedDB.
* Manages both Active Runs and Persistent Roster data.
*/
const DB_NAME = "AetherShardsDB";
const RUN_STORE = "Runs";
const ROSTER_STORE = "Roster";
const VERSION = 2; // Bumped version to add Roster store
/**
* Handles game data persistence using IndexedDB.
* @class
*/
export class Persistence {
constructor() {
/** @type {IDBDatabase | null} */
this.db = null;
}
/**
* Initializes the IndexedDB database.
* @returns {Promise<void>}
*/
async init() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, VERSION);
request.onerror = (e) => reject("DB Error: " + e.target.error);
request.onupgradeneeded = (e) => {
const db = e.target.result;
// Create Runs Store if missing
if (!db.objectStoreNames.contains(RUN_STORE)) {
db.createObjectStore(RUN_STORE, { keyPath: "id" });
}
// Create Roster Store if missing
if (!db.objectStoreNames.contains(ROSTER_STORE)) {
db.createObjectStore(ROSTER_STORE, { keyPath: "id" });
}
};
request.onsuccess = (e) => {
this.db = e.target.result;
resolve();
};
});
}
// --- RUN DATA ---
/**
* Saves run data.
* @param {RunData} runData - Run data to save
* @returns {Promise<void>}
*/
async saveRun(runData) {
if (!this.db) await this.init();
return this._put(RUN_STORE, { ...runData, id: "active_run" });
}
/**
* Loads run data.
* @returns {Promise<RunData | undefined>}
*/
async loadRun() {
if (!this.db) await this.init();
return this._get(RUN_STORE, "active_run");
}
/**
* Clears saved run data.
* @returns {Promise<void>}
*/
async clearRun() {
if (!this.db) await this.init();
return this._delete(RUN_STORE, "active_run");
}
// --- ROSTER DATA ---
/**
* Saves roster data.
* @param {RosterSaveData} rosterData - Roster data to save
* @returns {Promise<void>}
*/
async saveRoster(rosterData) {
if (!this.db) await this.init();
// Wrap the raw data object in an ID for storage
return this._put(ROSTER_STORE, { id: "player_roster", data: rosterData });
}
/**
* Loads roster data.
* @returns {Promise<RosterSaveData | null>}
*/
async loadRoster() {
if (!this.db) await this.init();
const result = await this._get(ROSTER_STORE, "player_roster");
return result ? result.data : null;
}
// --- INTERNAL HELPERS ---
/**
* Internal helper to put data into a store.
* @param {string} storeName - Store name
* @param {unknown} item - Item to store
* @returns {Promise<void>}
* @private
*/
_put(storeName, item) {
return new Promise((resolve, reject) => {
const tx = this.db.transaction([storeName], "readwrite");
const store = tx.objectStore(storeName);
const req = store.put(item);
req.onsuccess = () => resolve();
req.onerror = () => reject(req.error);
});
}
/**
* Internal helper to get data from a store.
* @param {string} storeName - Store name
* @param {string} key - Key to retrieve
* @returns {Promise<unknown>}
* @private
*/
_get(storeName, key) {
return new Promise((resolve, reject) => {
const tx = this.db.transaction([storeName], "readonly");
const store = tx.objectStore(storeName);
const req = store.get(key);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
/**
* Internal helper to delete data from a store.
* @param {string} storeName - Store name
* @param {string} key - Key to delete
* @returns {Promise<void>}
* @private
*/
_delete(storeName, key) {
return new Promise((resolve, reject) => {
const tx = this.db.transaction([storeName], "readwrite");
const store = tx.objectStore(storeName);
const req = store.delete(key);
req.onsuccess = () => resolve();
req.onerror = () => reject(req.error);
});
}
}