leaf-db
Version:
Small file-based database for node.js
138 lines (134 loc) • 4.01 kB
JavaScript
// src/lib/fn.ts
var equals = (a) => (b) => {
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false;
return a.every((v, i) => equals(b[i])(v));
}
if (a && b && typeof a === "object" && typeof b === "object") {
const keys = Object.keys(a);
if (keys.length !== Object.keys(b).length) return false;
return keys.every((k) => equals(b[k])(a[k]));
}
return a === b;
};
// src/lib/parse.ts
var isObject = (x) => x !== null && !Array.isArray(x) && typeof x === "object";
var hasModifier = (x) => Object.entries(x).some(([k, v]) => {
if (k.startsWith("$")) return true;
return isObject(v) ? hasModifier(v) : false;
});
var parse_default = (x) => {
const raw = JSON.parse(x);
if (!isObject(raw)) throw new Error("Not an object");
if (!("_id" in raw)) throw new Error("Missing _id");
if (hasModifier(raw)) throw new Error("Has modifier");
return raw;
};
// src/lib/query.ts
var match = (doc) => (query) => {
if ("$not" in query) return !match(doc)(query.$not);
if ("$or" in query) return query.$or.some(match(doc));
if ("$and" in query) return query.$and.every(match(doc));
return Object.entries(query).every(([key, rule]) => {
if (isObject(rule)) {
const keys = Object.keys(rule);
const operator = keys[0];
const a = doc[key];
const b = rule[operator];
if (operator === "$gt") return a > b;
if (operator === "$gte") return a >= b;
if (operator === "$lt") return a < b;
if (operator === "$lte") return a <= b;
if (operator === "$regexp") return b.test(a);
if (operator === "$length") return a.length === b;
if (operator === "$includes") return a.includes(b);
if (isObject(a)) return match(a)(rule);
}
return equals(doc[key])(rule);
});
};
var query_default = match;
// src/leafdb.ts
var LeafDB = class {
#memory;
#storage;
async #set(doc) {
this.#memory.set(doc._id, doc);
await this.#storage?.append(JSON.stringify(doc));
}
constructor() {
this.#memory = /* @__PURE__ */ new Map();
}
/** Get all documents */
get docs() {
return Array.from(this.#memory.values());
}
/** Read existing file and store to internal memory */
async open(storage) {
this.#storage = storage;
let data = "";
const corrupt = [];
for (const raw of await this.#storage.open()) {
if (raw.length === 0) continue;
try {
const doc = parse_default(raw);
if ("__deleted" in doc) {
this.#memory.delete(doc._id);
} else {
this.#memory.set(doc._id, doc);
data += `${raw}
`;
}
} catch (err) {
corrupt.push({ raw, error: err });
}
}
await this.#storage.write(data);
return corrupt;
}
/** Close file */
async close() {
return this.#storage?.close();
}
/** Get document by `id` */
get(id) {
return this.#memory.get(id) ?? null;
}
/** Create new document, throws if document already exists */
async insert(draft) {
if (typeof draft._id !== "string") {
draft._id = crypto.randomUUID();
} else if (this.#memory.has(draft._id)) {
throw new Error("Invalid draft, _id already exists");
}
await this.#set(draft);
return draft;
}
/** Find document by query */
query(query) {
const docs = [];
for (const doc of this.#memory.values()) {
if (query_default(doc)(query)) docs.push(doc);
}
return docs;
}
/** Update document, throws if document does not exist */
async update(doc) {
if (!this.#memory.has(doc._id)) throw new Error("Tried to update new document");
return this.#set(doc);
}
/** Delete document by `id` */
async delete(id) {
if (!this.#memory.has(id)) return;
this.#memory.delete(id);
await this.#storage?.append(JSON.stringify({ _id: id, __deleted: true }));
}
/** Delete all documents */
async drop() {
this.#memory.clear();
await this.#storage?.flush();
}
};
export {
LeafDB as default
};