@minatojs/driver-memory
Version:
In-memory Driver for Minato
197 lines (195 loc) • 6.98 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
// src/index.ts
import { clone, deepEqual, makeArray, mapValues, noop, omit, pick } from "cosmokit";
import { Driver, executeEval, executeQuery, executeSort, executeUpdate, Field, isAggrExpr, RuntimeError, Selection } from "minato";
import z from "schemastery";
var MemoryDriver = class extends Driver {
static name = "memory";
_store = {
_fields: []
};
_indexes = {};
async prepare(name) {
}
async start() {
}
async $save(name) {
}
async stop() {
}
table(sel, env = {}) {
if (typeof sel === "string") {
return this._store[sel] ||= [];
}
if (!Selection.is(sel)) {
throw new Error("Should not reach here");
}
const { ref, query, table, args, model } = sel;
const { fields, group, having, optional = {} } = sel.args[0];
let data;
if (typeof table === "object" && !Selection.is(table)) {
const entries = Object.entries(table).map(([name, sel2]) => [name, this.table(sel2, env)]);
const cartesian = /* @__PURE__ */ __name((entries2) => {
if (!entries2.length) return [];
const [[name, rows], ...tail] = entries2;
if (!tail.length) return rows.map((row) => ({ [name]: row }));
return rows.flatMap((row) => {
let res = cartesian(tail).map((tail2) => ({ ...tail2, [name]: row }));
if (Object.keys(table).length === tail.length + 1) {
res = res.map((row2) => ({ ...env, [ref]: row2 })).filter((data2) => executeEval(data2, having)).map((x) => x[ref]);
}
return !optional[tail[0]?.[0]] || res.length ? res : [{ [name]: row }];
});
}, "cartesian");
data = cartesian(entries);
} else {
data = this.table(table, env).filter((row) => executeQuery(row, query, ref, env));
}
env[ref] = data;
const branches = [];
const groupFields = group ? pick(fields, group) : fields;
for (let row of executeSort(data, args[0], ref)) {
row = model.format(row, false);
for (const key in model.fields) {
if (!Field.available(model.fields[key])) continue;
row[key] ??= null;
}
let index = row;
if (fields) {
index = mapValues(groupFields, (expr) => executeEval({ ...env, [ref]: row }, expr));
}
let branch = branches.find((branch2) => {
if (!group || !groupFields) return false;
for (const key in groupFields) {
if (!deepEqual(branch2.index[key], index[key])) return false;
}
return true;
});
if (!branch) {
branch = { index, table: [] };
branches.push(branch);
}
branch.table.push(row);
}
return branches.map(({ index, table: table2 }) => {
if (group) {
if (having) {
const value = executeEval(table2.map((row) => ({ ...env, [ref]: row, _: row })), having);
if (!value) return;
}
for (const key in omit(fields, group)) {
index[key] = executeEval(table2.map((row) => ({ ...env, [ref]: row, _: row })), fields[key]);
}
}
return model.parse(index, false);
}).filter(Boolean);
}
async drop(table) {
delete this._store[table];
}
async dropAll() {
this._store = { _fields: [] };
}
async stats() {
return { tables: mapValues(this._store, (rows, name) => ({ name, count: rows.length, size: 0 })), size: 0 };
}
async get(sel) {
return this.table(sel);
}
async eval(sel, expr) {
const { query, table } = sel;
const ref = typeof table === "string" ? sel.ref : table.ref;
const data = this.table(table).filter((row) => executeQuery(row, query, ref));
return executeEval(data.map((row) => ({ [ref]: row, _: row })), expr);
}
async set(sel, data) {
const { table, ref, query } = sel;
const matched = this.table(table).filter((row) => executeQuery(row, query, ref)).map((row) => executeUpdate(row, data, ref)).length;
this.$save(table);
return { matched };
}
async remove(sel) {
const { ref, query, table } = sel;
const data = this.table(table);
this._store[table] = data.filter((row) => !executeQuery(row, query, ref));
this.$save(table);
const count = data.length - this._store[table].length;
return { removed: count, matched: count };
}
async create(sel, data) {
const { table, model } = sel;
const { primary, autoInc } = model;
const store = this.table(table);
if (!Array.isArray(primary) && autoInc && !(primary in data)) {
let meta = this._store._fields.find((row) => row.table === table && row.field === primary);
if (!meta) {
meta = { table, field: primary, autoInc: 0 };
this._store._fields.push(meta);
}
meta.autoInc += 1;
data[primary] = meta.autoInc;
} else {
const duplicated = await this.database.get(table, pick(model.format(data), makeArray(primary)));
if (duplicated.length) {
throw new RuntimeError("duplicate-entry");
}
}
store.push(clone(data));
this.$save(table);
return clone(clone(data));
}
async upsert(sel, data, keys) {
const { table, model, ref } = sel;
const result = { inserted: 0, matched: 0 };
for (const update of data) {
const row = this.table(table).find((row2) => {
return keys.every((key) => row2[key] === update[key]);
});
if (row) {
executeUpdate(row, update, ref);
result.matched++;
} else {
const data2 = executeUpdate(model.create(), update, ref);
await this.create(sel, data2).catch(noop);
result.inserted++;
}
}
this.$save(table);
return result;
}
executeSelection(sel, env = {}) {
const expr = sel.args[0], table = sel.table;
if (Array.isArray(env)) env = { [sel.ref]: env };
const data = this.table(sel.table, env);
const res = isAggrExpr(expr) ? data.map((row) => executeEval({ ...env, [table.ref]: row, _: row }, expr)) : executeEval(Object.assign(data.map((row) => ({ [table.ref]: row, _: row })), env), expr);
return res;
}
async withTransaction(callback) {
const data = clone(this._store);
await callback().catch((e) => {
this._store = data;
throw e;
});
}
async getIndexes(table) {
return Object.values(this._indexes[table] ?? {});
}
async createIndex(table, index) {
const name = index.name ?? "index:" + Object.entries(index.keys).map(([key, direction]) => `${key}_${direction}`).join("+");
this._indexes[table] ??= {};
this._indexes[table][name] = { name, unique: false, ...index };
}
async dropIndex(table, name) {
this._indexes[table] ??= {};
delete this._indexes[table][name];
}
};
((MemoryDriver2) => {
MemoryDriver2.Config = z.object({});
})(MemoryDriver || (MemoryDriver = {}));
var index_default = MemoryDriver;
export {
MemoryDriver,
index_default as default
};