@ostore/db
Version:
基于文件系统的轻量级 NoSQL 数据库【Lightweight file-based NoSQL database】
392 lines (387 loc) • 11.4 kB
JavaScript
import * as fs from 'fs';
import * as path from 'path';
import { promisify } from 'util';
// 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 = promisify(fs.readFile);
var fsWriteFile = promisify(fs.writeFile);
var fsCopyFile = promisify(fs.copyFile);
var fsUnlink = promisify(fs.unlink);
var fsReaddir = promisify(fs.readdir);
var FileHandler = class {
model;
databasePath;
constructor(config) {
const rootPath = config.base || process.cwd();
this.model = config.model;
this.databasePath = path.join(rootPath, "data", this.model);
if (!this.model) {
throw new Error("Model name is required");
}
this.initDatabase();
}
/**
* 初始化数据库目录
*/
initDatabase() {
if (!fs.existsSync(this.databasePath)) {
fs.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.join(this.databasePath, `${options._$id}.json`);
if (!fs.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.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.join(this.databasePath, `${doc._$id}.json`);
const filenameCopy = path.join(this.databasePath, `${doc._$id}_copy.json`);
if (options.copy && fs.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.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.join(this.databasePath, `${doc._$id}.json`);
const filenameCopy = path.join(this.databasePath, `${doc._$id}_copy.json`);
if (fs.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
);
}
};
export { DB, Document, FileHandler, TOP_LEVEL_FIELDS };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map