UNPKG

@ostore/db

Version:

基于文件系统的轻量级 NoSQL 数据库【Lightweight file-based NoSQL database】

418 lines (410 loc) 12.3 kB
'use strict'; var fs = require('fs'); var path = require('path'); var util = require('util'); function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var fs__namespace = /*#__PURE__*/_interopNamespace(fs); var path__namespace = /*#__PURE__*/_interopNamespace(path); // src/types.ts var TOP_LEVEL_FIELDS = ["_$id", "_$status"]; // src/Document.ts var Document = class { _$status; _$id; content; constructor(data, isExisting = false) { if (isExisting) { this._$id = data._$id; this._$status = data._$status; this.content = data.content; } else { this._$id = Number(process.hrtime.bigint() / 1000n); this._$status = 1; this.content = data; } } }; // src/FileHandler.ts var fsReadFile = util.promisify(fs__namespace.readFile); var fsWriteFile = util.promisify(fs__namespace.writeFile); var fsCopyFile = util.promisify(fs__namespace.copyFile); var fsUnlink = util.promisify(fs__namespace.unlink); var fsReaddir = util.promisify(fs__namespace.readdir); var FileHandler = class { model; databasePath; constructor(config) { const rootPath = config.base || process.cwd(); this.model = config.model; this.databasePath = path__namespace.join(rootPath, "data", this.model); if (!this.model) { throw new Error("Model name is required"); } this.initDatabase(); } /** * 初始化数据库目录 */ initDatabase() { if (!fs__namespace.existsSync(this.databasePath)) { fs__namespace.mkdirSync(this.databasePath, { recursive: true }); console.log(`Database directory created: ${this.databasePath}`); } } /** * 复制文件 */ async copyFile(source, target) { try { await fsCopyFile(source, target); return { code: 200 }; } catch (error) { return { code: 500, error }; } } /** * 删除文件 */ async unlinkFile(target) { try { await fsUnlink(target); return { code: 200 }; } catch (error) { return { code: 500, error }; } } /** * 读取数据库文档 */ async readDatabase(options = {}) { try { const docs = []; if (options._$id) { const filePath = path__namespace.join(this.databasePath, `${options._$id}.json`); if (!fs__namespace.existsSync(filePath)) { return { code: 200, data: [] }; } const fileContent = await fsReadFile(filePath, "utf-8"); const doc = JSON.parse(fileContent); if (doc._$status === 1) { docs.push(doc); } } else { const files = await fsReaddir(this.databasePath); for (const file of files) { if (!file.endsWith(".json") || file.endsWith("_copy.json")) continue; const filePath = path__namespace.join(this.databasePath, file); const fileContent = await fsReadFile(filePath, "utf-8"); const doc = JSON.parse(fileContent); if (doc._$status === 1) { docs.push(doc); } } } return { code: 200, data: docs }; } catch (error) { return { code: 500, error }; } } /** * 写入数据库文档 */ async writeDatabase(data, options = {}) { try { const isExisting = data._$id !== void 0 && data._$status !== void 0; const doc = new Document(data, isExisting); const filename = path__namespace.join(this.databasePath, `${doc._$id}.json`); const filenameCopy = path__namespace.join(this.databasePath, `${doc._$id}_copy.json`); if (options.copy && fs__namespace.existsSync(filename)) { const copyResult = await this.copyFile(filename, filenameCopy); if (copyResult.code !== 200) { return copyResult; } } await fsWriteFile(filename, JSON.stringify(doc, null, 2), "utf-8"); if (options.copy && fs__namespace.existsSync(filenameCopy)) { await this.unlinkFile(filenameCopy); } return { code: 200, doc }; } catch (error) { if (options.copy) { const isExisting = data._$id !== void 0 && data._$status !== void 0; const doc = new Document(data, isExisting); const filename = path__namespace.join(this.databasePath, `${doc._$id}.json`); const filenameCopy = path__namespace.join(this.databasePath, `${doc._$id}_copy.json`); if (fs__namespace.existsSync(filenameCopy)) { await this.copyFile(filenameCopy, filename); await this.unlinkFile(filenameCopy); } } return { code: 500, error }; } } /** * 批量写入数据库(并发优化) */ async writeDatabaseBatch(dataList, options = {}) { const results = await Promise.allSettled( dataList.map((data) => this.writeDatabase(data, options)) ); const errorList = results.filter( (result, index) => result.status === "rejected" || result.status === "fulfilled" && result.value.code !== 200 ).map((result, index) => ({ index, error: result.status === "rejected" ? result.reason : result.value.error })); return { successCount: results.length - errorList.length, errorList }; } }; // src/DB.ts var DB = class extends FileHandler { _id; constructor(config) { super(config); this._id = Date.now(); } /** * 创建文档 */ async create(data) { return this.writeDatabase(data); } /** * 内部查询方法(返回未扁平化的 IDocument[]) */ async _findInternal(params, options = {}) { const { skip = 0, limit = 1e4, sort = -1 } = options; const query = this.buildQuery(params); const result = await this.readDatabase(query); if (result.code !== 200) { return { code: 500, data: [], count: 0, error: result.error }; } let docs = result.data || []; if (Object.keys(query).length > 0) { docs = docs.filter( (item) => Object.keys(query).every((key) => { const queryValue = query[key]; const itemValue = this.getDocumentFieldValue(item, key); if (typeof queryValue === "function") { return queryValue(itemValue); } return queryValue === itemValue; }) ); } const sortedDocs = this.sortDocuments(docs, sort); const paginatedDocs = sortedDocs.slice(skip, skip + limit); return { code: 200, data: paginatedDocs, count: docs.length }; } /** * 查询文档(公开方法,保持向后兼容) */ async find(params, options = {}) { const result = await this._findInternal(params, options); return result; } /** * 查询单个文档 */ async findOne(query, options = {}) { const result = await this._findInternal(query, options); if (result.code !== 200) return { ...result, data: [] }; const firstDoc = result.data[0]; const data = firstDoc ? { ...firstDoc.content, _$id: firstDoc._$id } : null; return { code: 200, data: data ? [data] : [], count: result.count }; } /** * 查询所有文档 */ async findMany(query, options = {}) { const result = await this._findInternal(query, options); if (result.code !== 200) return { ...result, data: [] }; const data = result.data.map((item) => ({ ...item.content, _$id: item._$id })); return { code: 200, data, count: result.count }; } /** * 更新单个文档 * @param target - 更新的数据 * @param source - 查询条件(可选,默认使用 target) */ async updateOne(target, source) { const updateData = { ...target }; let queryCondition; if (source) { queryCondition = { ...source }; } else { if (!target._$id) { return { code: 500, error: "updateOne requires _$id in target when source is not provided" }; } queryCondition = { _$id: target._$id }; delete updateData._$id; } let result; if (queryCondition._$id) { const readResult = await this.readDatabase({ _$id: queryCondition._$id }); result = { code: readResult.code, data: readResult.data || [], count: readResult.data?.length || 0, error: readResult.error }; } else { result = await this._findInternal(queryCondition, {}); } if (result.code !== 200) return { code: 500, error: result.error }; const item = result.data[0]; if (!item) { return { code: 500, error: "Document not found" }; } item.content = { ...item.content, ...updateData }; return this.writeDatabase(item, { copy: true }); } /** * 批量更新文档 * @param target - 更新的数据 * @param source - 查询条件(可选,默认使用 target) */ async updateMany(target, source) { const updateData = { ...target }; const queryCondition = source || { ...target }; if (source) { Object.keys(source).forEach((key) => { if (updateData[key] === source[key]) { delete updateData[key]; } }); } const result = await this._findInternal(queryCondition, {}); if (result.code !== 200) return { code: 500, error: result.error }; if (result.data.length === 0) { return { code: 500, error: "No documents found" }; } const updatedDocs = result.data.map((item) => ({ ...item, content: { ...item.content, ...updateData } })); const batchResult = await this.writeDatabaseBatch(updatedDocs, { copy: true }); return { code: 200, status: batchResult.errorList.length === 0, errlist: batchResult.errorList }; } /** * 删除单个文档(软删除) */ async removeOne(query, options = {}) { const queryCondition = { ...query }; let result; if (queryCondition._$id) { const readResult = await this.readDatabase({ _$id: queryCondition._$id }); result = { code: readResult.code, data: readResult.data || [], count: readResult.data?.length || 0, error: readResult.error }; } else { result = await this._findInternal(queryCondition, options); } if (result.code !== 200) return { code: 500, error: result.error }; const item = result.data[0]; if (!item) { return { code: 500, error: "Document not found" }; } item._$status = 0; return this.writeDatabase(item, { copy: true }); } /** * 批量删除文档(软删除) */ async removeMany(query) { const result = await this._findInternal(query); if (result.code !== 200) return { code: 500, error: result.error }; if (result.data.length === 0) { return { code: 500, error: "No documents found" }; } const deletedDocs = result.data.map((item) => ({ ...item, _$status: 0 })); const batchResult = await this.writeDatabaseBatch(deletedDocs, { copy: true }); return { code: 200, status: batchResult.errorList.length === 0, errlist: batchResult.errorList }; } // ==================== 私有辅助方法 ==================== /** * 获取文档字段值(兼容顶层字段和 content 字段) */ getDocumentFieldValue(doc, key) { if (TOP_LEVEL_FIELDS.includes(key)) { return doc[key]; } return doc.content[key]; } /** * 构建查询条件(过滤 undefined 值) */ buildQuery(params) { return Object.keys(params).reduce((query, key) => { if (params[key] !== void 0) { query[key] = params[key]; } return query; }, {}); } /** * 文档排序 */ sortDocuments(docs, sort) { return [...docs].sort( (a, b) => sort > 0 ? a._$id - b._$id : b._$id - a._$id ); } }; exports.DB = DB; exports.Document = Document; exports.FileHandler = FileHandler; exports.TOP_LEVEL_FIELDS = TOP_LEVEL_FIELDS; //# sourceMappingURL=index.cjs.map //# sourceMappingURL=index.cjs.map