feat-ai(storage.js): 添加nodejs环境下的sqlite存储支持,调用方法与浏览器环境下保存一致
This commit is contained in:
+204
-46
@@ -7,7 +7,9 @@ var log = console.warn;
|
||||
* @returns {boolean}
|
||||
* @private
|
||||
*/
|
||||
var isString = function (str) { return Object.getTypeString(str) === 'String'; };
|
||||
var isString = function (str) {
|
||||
return Object.getTypeString(str) === "String";
|
||||
};
|
||||
|
||||
/**
|
||||
* IndexedDB 存储实现(大容量存储优先选择)
|
||||
@@ -29,24 +31,37 @@ class IDBStorage {
|
||||
/** @type {number} 数据库版本号 */
|
||||
this.version = version || 1;
|
||||
/** @type {string} 数据库名称 */
|
||||
this.dbName = dbName || 'dbName';
|
||||
this.dbName = dbName || "dbName";
|
||||
/** @type {string} 对象存储名称 */
|
||||
this.storeName = storeName || 'storeName';
|
||||
this.storeName = storeName || "storeName";
|
||||
|
||||
var request = indexedDB.open(this.dbName, this.version);
|
||||
/** @type {Promise<IDBDatabase>} 数据库连接 Promise */
|
||||
this.requestPromise = new Promise(function (resolve, reject) {
|
||||
request.onupgradeneeded = function (e) {
|
||||
if (e && e.target && !request.result.objectStoreNames.contains(this.storeName)) {
|
||||
request.result.createObjectStore(this.storeName, { keyPath: 'key' })
|
||||
.createIndex('key', 'key', { unique: false });
|
||||
}
|
||||
log('[Index][open]-upgrade');
|
||||
resolve(request.result);
|
||||
}.bind(this);
|
||||
request.onsuccess = function () { log('[IndexDB][open]-success'); resolve(request.result); };
|
||||
request.onerror = function (e) { log('[IndexDB][open]-error', 2); reject(e); };
|
||||
}.bind(this));
|
||||
this.requestPromise = new Promise(
|
||||
function (resolve, reject) {
|
||||
request.onupgradeneeded = function (e) {
|
||||
if (
|
||||
e &&
|
||||
e.target &&
|
||||
!request.result.objectStoreNames.contains(this.storeName)
|
||||
) {
|
||||
request.result
|
||||
.createObjectStore(this.storeName, { keyPath: "key" })
|
||||
.createIndex("key", "key", { unique: false });
|
||||
}
|
||||
log("[Index][open]-upgrade");
|
||||
resolve(request.result);
|
||||
}.bind(this);
|
||||
request.onsuccess = function () {
|
||||
log("[IndexDB][open]-success");
|
||||
resolve(request.result);
|
||||
};
|
||||
request.onerror = function (e) {
|
||||
log("[IndexDB][open]-error", 2);
|
||||
reject(e);
|
||||
};
|
||||
}.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,11 +75,16 @@ class IDBStorage {
|
||||
var _this = this;
|
||||
return new Promise(async function (resolve) {
|
||||
var db = await _this.requestPromise;
|
||||
var transaction = db.transaction(_this.storeName, 'readonly');
|
||||
var transaction = db.transaction(_this.storeName, "readonly");
|
||||
var objectStore = transaction.objectStore(_this.storeName);
|
||||
var req = objectStore.get(key);
|
||||
req.onsuccess = function () { resolve(req.result ? req.result.value : (initValue || null)); };
|
||||
req.onerror = function (e) { log('[indexDB][get]-error', 2, e); resolve(initValue || null); };
|
||||
req.onsuccess = function () {
|
||||
resolve(req.result ? req.result.value : initValue || null);
|
||||
};
|
||||
req.onerror = function (e) {
|
||||
log("[indexDB][get]-error", 2, e);
|
||||
resolve(initValue || null);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -79,11 +99,16 @@ class IDBStorage {
|
||||
var _this = this;
|
||||
return new Promise(async function (resolve) {
|
||||
var db = await _this.requestPromise;
|
||||
var transaction = db.transaction(_this.storeName, 'readwrite');
|
||||
var transaction = db.transaction(_this.storeName, "readwrite");
|
||||
var objectStore = transaction.objectStore(_this.storeName);
|
||||
var req = objectStore.put({ key: key, value: value });
|
||||
req.onsuccess = function () { resolve(value); };
|
||||
req.onerror = function (e) { log('[indexDB][set]-error', 2, e); resolve(value); };
|
||||
req.onsuccess = function () {
|
||||
resolve(value);
|
||||
};
|
||||
req.onerror = function (e) {
|
||||
log("[indexDB][set]-error", 2, e);
|
||||
resolve(value);
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -108,11 +133,12 @@ class ILocalStorage {
|
||||
load() {
|
||||
var _this = this;
|
||||
Object.entries(localStorage).forEach(function (entry) {
|
||||
var key = entry[0], value = entry[1];
|
||||
var key = entry[0],
|
||||
value = entry[1];
|
||||
try {
|
||||
_this.cache.set(key, JSON.parse(value));
|
||||
} catch (error) {
|
||||
log('[localStorage][load]-error', 2, error, key, value);
|
||||
log("[localStorage][load]-error", 2, error, key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -125,9 +151,13 @@ class ILocalStorage {
|
||||
* @returns {Promise<T>} 缓存的值或默认值
|
||||
*/
|
||||
get(key, initValue) {
|
||||
return new Promise(function (resolve) {
|
||||
resolve(this.cache.get(key) !== undefined ? this.cache.get(key) : initValue);
|
||||
}.bind(this));
|
||||
return new Promise(
|
||||
function (resolve) {
|
||||
resolve(
|
||||
this.cache.get(key) !== undefined ? this.cache.get(key) : initValue,
|
||||
);
|
||||
}.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,31 +168,159 @@ class ILocalStorage {
|
||||
* @returns {Promise<T>} 返回写入的值
|
||||
*/
|
||||
set(key, value) {
|
||||
return new Promise(function (resolve) {
|
||||
var _value = isString(value) ? value : '';
|
||||
try {
|
||||
_value = JSON.stringify(value);
|
||||
} catch (e) {
|
||||
log('[localStorage][set]-error', 2, e);
|
||||
}
|
||||
localStorage.setItem(key, _value);
|
||||
this.cache.set(key, _value);
|
||||
resolve(value);
|
||||
}.bind(this));
|
||||
return new Promise(
|
||||
function (resolve) {
|
||||
var _value = isString(value) ? value : "";
|
||||
try {
|
||||
_value = JSON.stringify(value);
|
||||
} catch (e) {
|
||||
log("[localStorage][set]-error", 2, e);
|
||||
}
|
||||
localStorage.setItem(key, _value);
|
||||
this.cache.set(key, _value);
|
||||
resolve(value);
|
||||
}.bind(this),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 浏览器环境自动选择存储方式
|
||||
if (typeof window !== 'undefined') {
|
||||
/**
|
||||
* 纯内存存储实现(无持久化,适用于所有环境兜底)
|
||||
*
|
||||
* @class MemoryStorage
|
||||
* @example
|
||||
* const storage = new MemoryStorage();
|
||||
* await storage.set('key', { data: 789 });
|
||||
* await storage.get('key', null);
|
||||
*/
|
||||
class MemoryStorage {
|
||||
constructor() {
|
||||
/** @type {Map<string, *>} 内存缓存 */
|
||||
this.cache = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前环境可用的存储类(IDBStorage 或 ILocalStorage)
|
||||
* @type {Function|null}
|
||||
* @global
|
||||
* 从内存获取值
|
||||
* @template T
|
||||
* @param {string} key - 键名
|
||||
* @param {T} initValue - 默认值
|
||||
* @returns {Promise<T>} 缓存的值或默认值
|
||||
*/
|
||||
window.IStorage = window.indexedDB ? IDBStorage : (window.localStorage ? ILocalStorage : null);
|
||||
if (typeof module !== 'undefined') {
|
||||
get(key, initValue) {
|
||||
return Promise.resolve(
|
||||
this.cache.has(key)
|
||||
? this.cache.get(key)
|
||||
: initValue !== undefined
|
||||
? initValue
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入内存
|
||||
* @template T
|
||||
* @param {string} key - 键名
|
||||
* @param {T} value - 要存储的值
|
||||
* @returns {Promise<T>} 返回写入的值
|
||||
*/
|
||||
set(key, value) {
|
||||
this.cache.set(key, value);
|
||||
return Promise.resolve(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Node.js SQLite 持久化存储实现
|
||||
* 仅在 Node.js 环境下可用,浏览器/webpack 打包中为 null
|
||||
*
|
||||
* @class SqliteStorage
|
||||
* @example
|
||||
* const storage = new SqliteStorage('./data/storage.db');
|
||||
* await storage.set('key', { data: 123 });
|
||||
* await storage.get('key', null);
|
||||
* @private
|
||||
*/
|
||||
var _SqliteStorageClass = function () {
|
||||
var Database = require("better-sqlite3");
|
||||
var pathModule = require("path");
|
||||
var fs = require("fs");
|
||||
|
||||
return class SqliteStorage {
|
||||
/**
|
||||
* 创建 SQLite 存储实例
|
||||
* @param {string} [dbPath='.windychen-storage/storage.db'] - 数据库文件路径
|
||||
*/
|
||||
constructor(dbPath) {
|
||||
this.dbPath =
|
||||
dbPath || pathModule.resolve(".windychen-storage", "storage.db");
|
||||
var dir = pathModule.dirname(this.dbPath);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
this.db = new Database(this.dbPath);
|
||||
this.db.pragma("journal_mode = WAL");
|
||||
this.db.exec(
|
||||
"CREATE TABLE IF NOT EXISTS kv_store (key TEXT PRIMARY KEY, value TEXT)",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储值
|
||||
* @template T
|
||||
* @param {string} key - 键名
|
||||
* @param {T} initValue - 默认值(未找到时返回)
|
||||
* @returns {Promise<T>} 存储的值或默认值
|
||||
*/
|
||||
async get(key, initValue) {
|
||||
var row = this.db
|
||||
.prepare("SELECT value FROM kv_store WHERE key = ?")
|
||||
.get(key);
|
||||
if (row) {
|
||||
try {
|
||||
return JSON.parse(row.value);
|
||||
} catch (e) {
|
||||
log("[SqliteStorage][get]-parse-error", 2, e);
|
||||
}
|
||||
}
|
||||
return initValue !== undefined ? initValue : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置存储值
|
||||
* @template T
|
||||
* @param {string} key - 键名
|
||||
* @param {T} value - 要存储的值
|
||||
* @returns {Promise<T>} 返回写入的值
|
||||
*/
|
||||
async set(key, value) {
|
||||
var json = JSON.stringify(value);
|
||||
this.db
|
||||
.prepare("INSERT OR REPLACE INTO kv_store (key, value) VALUES (?, ?)")
|
||||
.run(key, json);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var SqliteStorage = null;
|
||||
try {
|
||||
SqliteStorage = _SqliteStorageClass();
|
||||
} catch (e) {
|
||||
// better-sqlite3 不可用(浏览器 / webpack 打包环境),SqliteStorage 保持为 null
|
||||
}
|
||||
|
||||
// ===== 环境检测与自动导出 =====
|
||||
if (typeof window !== "undefined") {
|
||||
// 浏览器环境:优先 IndexedDB → localStorage → 内存
|
||||
window.IStorage = window.indexedDB
|
||||
? IDBStorage
|
||||
: window.localStorage
|
||||
? ILocalStorage
|
||||
: MemoryStorage;
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = new window.IStorage();
|
||||
}
|
||||
} else if (typeof module !== 'undefined') {
|
||||
module.exports = null;
|
||||
} else if (typeof module !== "undefined") {
|
||||
// Node.js 环境:SQLite 存储 → 内存
|
||||
module.exports = SqliteStorage ? new SqliteStorage() : new MemoryStorage();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user