"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { $: () => Eval3, Database: () => Database, Driver: () => Driver, Eval: () => Eval3, Field: () => Field, Logger: () => import_cordis3.Logger, Model: () => Model, Relation: () => Relation, RuntimeError: () => RuntimeError, Schema: () => import_cordis3.Schema, Selection: () => Selection, Tables: () => Tables, Type: () => Type, Types: () => Types, default: () => src_default, executeEval: () => executeEval, executeQuery: () => executeQuery, executeSort: () => executeSort, executeUpdate: () => executeUpdate, flatten: () => flatten, getCell: () => getCell, hasSubquery: () => hasSubquery, isAggrExpr: () => isAggrExpr, isComparable: () => isComparable, isEmpty: () => isEmpty, isEvalExpr: () => isEvalExpr, isFlat: () => isFlat, isUpdateExpr: () => isUpdateExpr, makeRegExp: () => makeRegExp, randomId: () => randomId, unravel: () => unravel, z: () => import_cordis3.Schema }); module.exports = __toCommonJS(src_exports); // src/database.ts var import_cosmokit6 = require("cosmokit"); var import_cordis = require("cordis"); // src/utils.ts var import_cosmokit4 = require("cosmokit"); // src/eval.ts var import_cosmokit3 = require("cosmokit"); // src/type.ts var import_cosmokit = require("cosmokit"); var Type; ((Type2) => { Type2.kType = Symbol.for("minato.type"); Type2.Any = fromField("expr"); Type2.Boolean = fromField("boolean"); Type2.Number = fromField("double"); Type2.String = fromField("string"); Type2.Object = /* @__PURE__ */ __name((obj) => (0, import_cosmokit.defineProperty)({ type: "json", inner: globalThis.Object.keys(obj ?? {}).length ? (0, import_cosmokit.mapValues)(obj, (value) => isType(value) ? value : fromField(value)) : void 0 }, Type2.kType, true), "Object"); Type2.Array = /* @__PURE__ */ __name((type) => (0, import_cosmokit.defineProperty)({ type: "json", inner: type, array: true }, Type2.kType, true), "Array"); function fromPrimitive(value) { if ((0, import_cosmokit.isNullable)(value)) return fromField("expr"); else if (typeof value === "number") return Type2.Number; else if (typeof value === "string") return Type2.String; else if (typeof value === "boolean") return Type2.Boolean; else if (typeof value === "bigint") return fromField("bigint"); else if (value instanceof Date) return fromField("timestamp"); else if (import_cosmokit.Binary.is(value)) return fromField("binary"); else if (globalThis.Array.isArray(value)) return (0, Type2.Array)(value.length ? fromPrimitive(value[0]) : void 0); else if (typeof value === "object") return fromField("json"); throw new TypeError(`invalid primitive: ${value}`); } Type2.fromPrimitive = fromPrimitive; __name(fromPrimitive, "fromPrimitive"); function fromField(field) { if (isType(field)) return field; else if (field === "array") return (0, Type2.Array)(); else if (field === "object") return (0, Type2.Object)(); else if (typeof field === "string") return (0, import_cosmokit.defineProperty)({ type: field }, Type2.kType, true); else if (field.type) return field.type; else if (field.expr?.[Type2.kType]) return field.expr[Type2.kType]; throw new TypeError(`invalid field: ${field}`); } Type2.fromField = fromField; __name(fromField, "fromField"); function fromTerm(value, initial) { if (isEvalExpr(value)) return value[Type2.kType] ?? initial ?? fromField("expr"); else return fromPrimitive(value); } Type2.fromTerm = fromTerm; __name(fromTerm, "fromTerm"); function fromTerms(values, initial) { return values.map((x) => fromTerm(x)).find((type) => type.type !== "expr") ?? initial ?? fromField("expr"); } Type2.fromTerms = fromTerms; __name(fromTerms, "fromTerms"); function isType(value) { return value?.[Type2.kType] === true; } Type2.isType = isType; __name(isType, "isType"); function isArray(type) { return type?.type === "json" && type?.array; } Type2.isArray = isArray; __name(isArray, "isArray"); function getInner(type, key) { if (!type?.inner) return; if (isArray(type)) return type.inner; if ((0, import_cosmokit.isNullable)(key)) return; if (type.inner[key]) return type.inner[key]; if (key.includes(".")) return key.split(".").reduce((t, k) => getInner(t, k), type); const fields = globalThis.Object.entries(type.inner).filter(([k]) => k.startsWith(`${key}.`)).map(([k, v]) => [k.slice(key.length + 1), v]); return fields.length ? (0, Type2.Object)(globalThis.Object.fromEntries(fields)) : void 0; } Type2.getInner = getInner; __name(getInner, "getInner"); function transform(value, type, callback) { if (!(0, import_cosmokit.isNullable)(value) && type?.inner) { if (Type2.isArray(type)) { return value.map((x) => callback(x, Type2.getInner(type))).filter((x) => !type.ignoreNull || !isEmpty(x)); } else { if (type.ignoreNull && isEmpty(value)) return null; return (0, import_cosmokit.mapValues)(value, (x, k) => callback(x, Type2.getInner(type, k))); } } return value; } Type2.transform = transform; __name(transform, "transform"); })(Type || (Type = {})); // src/model.ts var import_cosmokit2 = require("cosmokit"); var Primary = Symbol("minato.primary"); var Relation; ((Relation4) => { const Marker = Symbol("minato.relation"); Relation4.Type = ["oneToOne", "oneToMany", "manyToOne", "manyToMany"]; function buildAssociationTable(...tables) { return "_" + tables.sort().join("_"); } Relation4.buildAssociationTable = buildAssociationTable; __name(buildAssociationTable, "buildAssociationTable"); function buildAssociationKey(key, table) { return `${table}.${key}`; } Relation4.buildAssociationKey = buildAssociationKey; __name(buildAssociationKey, "buildAssociationKey"); function buildSharedKey(field, reference) { return [field, reference].sort().join("_"); } Relation4.buildSharedKey = buildSharedKey; __name(buildSharedKey, "buildSharedKey"); function parse(def, key, model, relmodel, subprimary) { const shared = !def.shared ? {} : typeof def.shared === "string" ? { [def.shared]: def.shared } : Array.isArray(def.shared) ? Object.fromEntries(def.shared.map((x) => [x, x])) : def.shared; const fields = def.fields ?? (subprimary || def.type === "manyToOne" || def.type === "oneToOne" && (model.name === relmodel.name || !(0, import_cosmokit2.makeArray)(relmodel.primary).every((key2) => !relmodel.fields[key2]?.nullable)) ? (0, import_cosmokit2.makeArray)(relmodel.primary).map((x) => `${key}.${x}`) : model.primary); const relation = { type: def.type, table: def.table ?? relmodel.name, fields: (0, import_cosmokit2.makeArray)(fields), shared, references: (0, import_cosmokit2.makeArray)(def.references ?? relmodel.primary), required: def.type !== "manyToOne" && model.name !== relmodel.name && (0, import_cosmokit2.makeArray)(fields).every((key2) => !model.fields[key2]?.nullable || (0, import_cosmokit2.makeArray)(model.primary).includes(key2)) }; Object.entries(shared).forEach(([k, v]) => { relation.fields = relation.fields.filter((x) => x !== k); relation.references = relation.references.filter((x) => x !== v); }); const inverse = { type: relation.type === "oneToMany" ? "manyToOne" : relation.type === "manyToOne" ? "oneToMany" : relation.type, table: model.name, fields: relation.references, references: relation.fields, shared: Object.fromEntries(Object.entries(shared).map(([k, v]) => [v, k])), required: relation.type !== "oneToMany" && relation.references.every((key2) => !relmodel.fields[key2]?.nullable || (0, import_cosmokit2.makeArray)(relmodel.primary).includes(key2)) }; if (inverse.required) relation.required = false; return [relation, inverse]; } Relation4.parse = parse; __name(parse, "parse"); })(Relation || (Relation = {})); var Field; ((Field2) => { Field2.number = ["integer", "unsigned", "float", "double", "decimal"]; Field2.string = ["char", "string", "text"]; Field2.boolean = ["boolean"]; Field2.date = ["timestamp", "date", "time"]; Field2.object = ["list", "json"]; const NewType = Symbol("minato.newtype"); const regexp = /^(\w+)(?:\((.+)\))?$/; function parse(source) { if (typeof source === "function") throw new TypeError("view field is not supported"); if (typeof source !== "string") { return { initial: null, deftype: source.type, ...source, type: Type.fromField(source.type) }; } const capture = regexp.exec(source); if (!capture) throw new TypeError("invalid field definition"); const type = capture[1]; const args = (capture[2] || "").split(","); const field = { deftype: type, type: Type.fromField(type) }; if (field.initial === void 0) field.initial = getInitial(type); if (type === "decimal") { field.precision = +args[0]; field.scale = +args[1]; } else if (args[0]) { field.length = +args[0]; } return field; } Field2.parse = parse; __name(parse, "parse"); function getInitial(type, initial) { if (initial === void 0) { if (Field2.number.includes(type)) return 0; if (Field2.string.includes(type)) return ""; if (type === "list") return []; if (type === "json") return {}; } return initial; } Field2.getInitial = getInitial; __name(getInitial, "getInitial"); function available(field) { return !!field && !field.deprecated && !field.relation && field.deftype !== "expr"; } Field2.available = available; __name(available, "available"); })(Field || (Field = {})); var Model = class { constructor(name) { this.name = name; this.autoInc = false; this.primary = "id"; this.unique = []; this.indexes = []; this.foreign = {}; } static { __name(this, "Model"); } fields = {}; migrations = /* @__PURE__ */ new Map(); extend(fields = {}, config = {}) { const { primary, autoInc, unique = [], indexes = [], foreign, callback } = config; this.primary = primary || this.primary; this.autoInc = autoInc || this.autoInc; unique.forEach((key) => this.unique.includes(key) || this.unique.push(key)); indexes.map((x) => this.parseIndex(x)).forEach((index) => this.indexes.some((ind) => (0, import_cosmokit2.deepEqual)(ind, index)) || this.indexes.push(index)); Object.assign(this.foreign, foreign); if (callback) this.migrations.set(callback, Object.keys(fields)); for (const key in fields) { this.fields[key] = Field.parse(fields[key]); this.fields[key].deprecated = !!callback; } if (typeof this.primary === "string" && this.fields[this.primary]?.deftype === "primary") { this.autoInc = true; } this.checkIndex(this.primary); this.unique.forEach((index) => this.checkIndex(index)); this.indexes.forEach((index) => this.checkIndex(index)); } parseIndex(index) { if (typeof index === "string" || Array.isArray(index)) { return { name: `index:${this.name}:` + (0, import_cosmokit2.makeArray)(index).join("+"), unique: false, keys: Object.fromEntries((0, import_cosmokit2.makeArray)(index).map((key) => [key, "asc"])) }; } else { return { name: index.name ?? `index:${this.name}:` + Object.keys(index.keys).join("+"), unique: index.unique ?? false, keys: index.keys }; } } checkIndex(index) { for (const key of typeof index === "string" || Array.isArray(index) ? (0, import_cosmokit2.makeArray)(index) : Object.keys(index.keys)) { if (!this.fields[key]) { throw new TypeError(`missing field definition for index key "${key}"`); } } } resolveValue(field, value) { if ((0, import_cosmokit2.isNullable)(value)) return value; if (typeof field === "string") field = this.fields[field]; if (field) field = Type.fromField(field); if (field?.type === "time") { const date = /* @__PURE__ */ new Date(0); date.setHours(value.getHours(), value.getMinutes(), value.getSeconds(), value.getMilliseconds()); return date; } else if (field?.type === "date") { const date = new Date(value); date.setHours(0, 0, 0, 0); return date; } return value; } resolveModel(obj, model) { if (!model) model = this.getType(); if ((0, import_cosmokit2.isNullable)(obj) || !model.inner) return obj; if (Type.isArray(model) && Array.isArray(obj)) { return obj.map((x) => this.resolveModel(x, Type.getInner(model))); } const result = {}; for (const key in obj) { const type = Type.getInner(model, key); if (!type || (0, import_cosmokit2.isNullable)(obj[key])) { result[key] = obj[key]; } else if (type.type !== "json") { result[key] = this.resolveValue(type, obj[key]); } else if (isEvalExpr(obj[key])) { result[key] = obj[key]; } else if (type.inner && Type.isArray(type) && Array.isArray(obj[key])) { result[key] = obj[key].map((x) => this.resolveModel(x, Type.getInner(type))); } else if (type.inner) { result[key] = this.resolveModel(obj[key], type); } else { result[key] = obj[key]; } } return result; } format(source, strict = true, prefix = "", result = {}) { const fields = Object.keys(this.fields).filter((key) => !this.fields[key].relation); Object.entries(source).map(([key, value]) => { key = prefix + key; if (value === void 0) return; if (fields.includes(key)) { result[key] = value; return; } const field = fields.find((field2) => key.startsWith(field2 + ".")); if (field) { result[key] = value; } else if (isFlat(value)) { if (strict && (typeof value !== "object" || Object.keys(value).length)) { throw new TypeError(`unknown field "${key}" in model ${this.name}`); } } else { this.format(value, strict, key + ".", result); } }); return strict && prefix === "" ? this.resolveModel(result) : result; } parse(source, strict = true, prefix = "", result = {}) { const fields = Object.keys(this.fields).filter((key) => !this.fields[key].relation); if (strict && prefix === "") { Object.assign(result, unravel( Object.fromEntries(fields.filter((key) => key.includes(".")).map((key) => [key.slice(0, key.lastIndexOf(".")), {}])) )); } for (const key in source) { let node = result; const segments = key.split(".").reverse(); for (let index = segments.length - 1; index > 0; index--) { const segment = segments[index]; node = node[segment] ??= {}; } if (key in source) { const fullKey = prefix + key, value = source[key]; const field = fields.find((field2) => fullKey === field2 || fullKey.startsWith(field2 + ".")); if (field) { node[segments[0]] = value; } else if (isFlat(value)) { if (strict) { throw new TypeError(`unknown field "${fullKey}" in model ${this.name}`); } else { node[segments[0]] = value; } } else { this.parse(value, strict, fullKey + ".", node[segments[0]] ??= {}); } } } return strict && prefix === "" ? this.resolveModel(result) : result; } create(data) { const result = {}; const keys = (0, import_cosmokit2.makeArray)(this.primary); for (const key in this.fields) { if (!Field.available(this.fields[key])) continue; const { initial } = this.fields[key]; if (!keys.includes(key) && !(0, import_cosmokit2.isNullable)(initial)) { result[key] = (0, import_cosmokit2.clone)(initial); } } return this.parse({ ...result, ...data }); } avaiableFields() { return (0, import_cosmokit2.filterKeys)(this.fields, (_, field) => Field.available(field)); } getType(key) { if (!this.type) (0, import_cosmokit2.defineProperty)(this, "type", Type.Object((0, import_cosmokit2.mapValues)(this.fields, (field) => Type.fromField(field)))); return key ? Type.getInner(this.type, key) : this.type; } }; // src/eval.ts function isEvalExpr(value) { return value && Object.keys(value).some((key) => key.startsWith("$")); } __name(isEvalExpr, "isEvalExpr"); var isUpdateExpr = isEvalExpr; function isAggrExpr(expr) { return expr["$"] || expr["$select"]; } __name(isAggrExpr, "isAggrExpr"); function hasSubquery(value) { if (!isEvalExpr(value)) return false; return Object.entries(value).filter(([k]) => k.startsWith("$")).some(([k, v]) => { if ((0, import_cosmokit3.isNullable)(v) || isComparable(v)) return false; if (k === "$exec") return true; if (isEvalExpr(v)) return hasSubquery(v); if (Array.isArray(v)) return v.some((x) => hasSubquery(x)); if (typeof v === "object") return Object.values(v).some((x) => hasSubquery(x)); return false; }); } __name(hasSubquery, "hasSubquery"); var kExpr = Symbol("expr"); var kType = Symbol("type"); var kAggr = Symbol("aggr"); var Eval3 = /* @__PURE__ */ __name((key, value, type) => (0, import_cosmokit3.defineProperty)((0, import_cosmokit3.defineProperty)({ ["$" + key]: value }, kExpr, true), Type.kType, type), "Eval"); var operators = /* @__PURE__ */ Object.create(null); operators["$"] = getRecursive; function unary(key, callback, type) { operators[`$${key}`] = callback; return (value) => Eval3(key, value, typeof type === "function" ? type(value) : type); } __name(unary, "unary"); function multary(key, callback, type) { operators[`$${key}`] = callback; return (...args) => Eval3(key, args, typeof type === "function" ? type(...args) : type); } __name(multary, "multary"); function comparator(key, callback) { operators[`$${key}`] = (args, data) => { const left = executeEval(data, args[0]); const right = executeEval(data, args[1]); if ((0, import_cosmokit3.isNullable)(left) || (0, import_cosmokit3.isNullable)(right)) return true; return callback(left.valueOf(), right.valueOf()); }; return (...args) => Eval3(key, args, Type.Boolean); } __name(comparator, "comparator"); Eval3.switch = (branches, vDefault) => Eval3("switch", { branches, default: vDefault }, Type.fromTerm(branches[0])); operators.$switch = (args, data) => { for (const branch of args.branches) { if (executeEval(data, branch.case)) return executeEval(data, branch.then); } return executeEval(data, args.default); }; Eval3.ignoreNull = (expr) => (expr["$ignoreNull"] = true, expr[Type.kType].ignoreNull = true, expr); Eval3.select = multary("select", (args, table) => args.map((arg) => executeEval(table, arg)), Type.Array()); Eval3.query = (row, query, expr = true) => ({ $expr: expr, ...query }); Eval3.exec = unary("exec", (expr, data) => expr.driver.executeSelection(expr, data), (expr) => Type.fromTerm(expr.args[0])); Eval3.if = multary("if", ([cond, vThen, vElse], data) => executeEval(data, cond) ? executeEval(data, vThen) : executeEval(data, vElse), (cond, vThen, vElse) => Type.fromTerm(vThen)); Eval3.ifNull = multary("ifNull", ([value, fallback], data) => executeEval(data, value) ?? executeEval(data, fallback), (value) => Type.fromTerm(value)); Eval3.add = multary("add", (args, data) => args.reduce((prev, curr) => prev + executeEval(data, curr), 0), Type.Number); Eval3.mul = Eval3.multiply = multary("multiply", (args, data) => args.reduce((prev, curr) => prev * executeEval(data, curr), 1), Type.Number); Eval3.sub = Eval3.subtract = multary("subtract", ([left, right], data) => executeEval(data, left) - executeEval(data, right), Type.Number); Eval3.div = Eval3.divide = multary("divide", ([left, right], data) => executeEval(data, left) / executeEval(data, right), Type.Number); Eval3.mod = Eval3.modulo = multary("modulo", ([left, right], data) => executeEval(data, left) % executeEval(data, right), Type.Number); Eval3.abs = unary("abs", (arg, data) => Math.abs(executeEval(data, arg)), Type.Number); Eval3.floor = unary("floor", (arg, data) => Math.floor(executeEval(data, arg)), Type.Number); Eval3.ceil = unary("ceil", (arg, data) => Math.ceil(executeEval(data, arg)), Type.Number); Eval3.round = unary("round", (arg, data) => Math.round(executeEval(data, arg)), Type.Number); Eval3.exp = unary("exp", (arg, data) => Math.exp(executeEval(data, arg)), Type.Number); Eval3.log = multary("log", ([left, right], data) => Math.log(executeEval(data, left)) / Math.log(executeEval(data, right ?? Math.E)), Type.Number); Eval3.pow = Eval3.power = multary("power", ([left, right], data) => Math.pow(executeEval(data, left), executeEval(data, right)), Type.Number); Eval3.random = () => Eval3("random", {}, Type.Number); operators.$random = () => Math.random(); Eval3.eq = comparator("eq", (left, right) => left === right); Eval3.ne = comparator("ne", (left, right) => left !== right); Eval3.gt = comparator("gt", (left, right) => left > right); Eval3.ge = Eval3.gte = comparator("gte", (left, right) => left >= right); Eval3.lt = comparator("lt", (left, right) => left < right); Eval3.le = Eval3.lte = comparator("lte", (left, right) => left <= right); Eval3.in = (value, array) => Eval3("in", [Array.isArray(value) ? Eval3.select(...value) : value, array], Type.Boolean); operators.$in = ([value, array], data) => { const val = executeEval(data, value), arr = executeEval(data, array); if (typeof val === "object") return arr.includes(val) || arr.map(JSON.stringify).includes(JSON.stringify(val)); return arr.includes(val); }; Eval3.nin = (value, array) => Eval3("nin", [Array.isArray(value) ? Eval3.select(...value) : value, array], Type.Boolean); operators.$nin = ([value, array], data) => { const val = executeEval(data, value), arr = executeEval(data, array); if (typeof val === "object") return !arr.includes(val) && !arr.map(JSON.stringify).includes(JSON.stringify(val)); return !arr.includes(val); }; Eval3.concat = multary("concat", (args, data) => args.map((arg) => executeEval(data, arg)).join(""), Type.String); Eval3.regex = multary("regex", ([value, regex, flags], data) => makeRegExp(executeEval(data, regex), flags).test(executeEval(data, value)), Type.Boolean); Eval3.and = multary("and", (args, data) => { const type = Type.fromTerms(args, Type.Boolean); if (Field.boolean.includes(type.type)) return args.every((arg) => executeEval(data, arg)); else if (Field.number.includes(type.type)) return args.map((arg) => executeEval(data, arg)).reduce((prev, curr) => prev & curr); else if (type.type === "bigint") return args.map((arg) => BigInt(executeEval(data, arg) ?? 0)).reduce((prev, curr) => prev & curr); }, (...args) => Type.fromTerms(args, Type.Boolean)); Eval3.or = multary("or", (args, data) => { const type = Type.fromTerms(args, Type.Boolean); if (Field.boolean.includes(type.type)) return args.some((arg) => executeEval(data, arg)); else if (Field.number.includes(type.type)) return args.map((arg) => executeEval(data, arg)).reduce((prev, curr) => prev | curr); else if (type.type === "bigint") return args.map((arg) => BigInt(executeEval(data, arg) ?? 0)).reduce((prev, curr) => prev | curr); }, (...args) => Type.fromTerms(args, Type.Boolean)); Eval3.not = unary("not", (value, data) => { const type = Type.fromTerms([value], Type.Boolean); if (Field.boolean.includes(type.type)) return !executeEval(data, value); else if (Field.number.includes(type.type)) return ~executeEval(data, value); else if (type.type === "bigint") return ~BigInt(executeEval(data, value) ?? 0); }, (value) => Type.fromTerms([value], Type.Boolean)); Eval3.xor = multary("xor", (args, data) => { const type = Type.fromTerms(args, Type.Boolean); if (Field.boolean.includes(type.type)) return args.map((arg) => executeEval(data, arg)).reduce((prev, curr) => prev !== curr); else if (Field.number.includes(type.type)) return args.map((arg) => executeEval(data, arg)).reduce((prev, curr) => prev ^ curr); else if (type.type === "bigint") return args.map((arg) => BigInt(executeEval(data, arg) ?? 0)).reduce((prev, curr) => prev ^ curr); }, (...args) => Type.fromTerms(args, Type.Boolean)); Eval3.literal = multary("literal", ([value, type]) => value, (value, type) => type ? Type.fromField(type) : Type.fromTerm(value)); Eval3.number = unary("number", (arg, data) => { const value = executeEval(data, arg); return value instanceof Date ? Math.floor(value.valueOf() / 1e3) : Number(value); }, Type.Number); var unwrapAggr = /* @__PURE__ */ __name((expr, def) => { let type = Type.fromTerm(expr); type = Type.getInner(type) ?? type; return def && type.type === "expr" ? def : type; }, "unwrapAggr"); Eval3.sum = unary("sum", (expr, table) => Array.isArray(table) ? table.reduce((prev, curr) => prev + executeAggr(expr, curr), 0) : Array.from(executeEval(table, expr)).reduce((prev, curr) => prev + curr, 0), Type.Number); Eval3.avg = unary("avg", (expr, table) => { if (Array.isArray(table)) return table.reduce((prev, curr) => prev + executeAggr(expr, curr), 0) / table.length; else { const array = Array.from(executeEval(table, expr)); return array.reduce((prev, curr) => prev + curr, 0) / array.length; } }, Type.Number); Eval3.max = unary("max", (expr, table) => Array.isArray(table) ? table.map((data) => executeAggr(expr, data)).reduce((x, y) => x > y ? x : y, -Infinity) : Array.from(executeEval(table, expr)).reduce((x, y) => x > y ? x : y, -Infinity), (expr) => unwrapAggr(expr, Type.Number)); Eval3.min = unary("min", (expr, table) => Array.isArray(table) ? table.map((data) => executeAggr(expr, data)).reduce((x, y) => x < y ? x : y, Infinity) : Array.from(executeEval(table, expr)).reduce((x, y) => x < y ? x : y, Infinity), (expr) => unwrapAggr(expr, Type.Number)); Eval3.count = unary("count", (expr, table) => new Set(table.map((data) => executeAggr(expr, data))).size, Type.Number); (0, import_cosmokit3.defineProperty)(Eval3, "length", unary("length", (expr, table) => Array.isArray(table) ? table.map((data) => executeAggr(expr, data)).length : Array.from(executeEval(table, expr)).length, Type.Number)); operators.$object = (field, table) => (0, import_cosmokit3.mapValues)(field, (value) => executeAggr(value, table)); Eval3.object = (fields) => { if (fields.$model) { const modelFields = Object.entries(fields.$model.fields); const prefix = fields.$prefix; fields = Object.fromEntries(modelFields.filter(([, field]) => Field.available(field)).filter(([path]) => path.startsWith(prefix)).map(([k]) => [k.slice(prefix.length), fields[k.slice(prefix.length)]])); return Eval3("object", fields, Type.Object((0, import_cosmokit3.mapValues)(fields, (value) => Type.fromTerm(value)))); } return Eval3("object", fields, Type.Object((0, import_cosmokit3.mapValues)(fields, (value) => Type.fromTerm(value)))); }; Eval3.array = unary("array", (expr, table) => Array.isArray(table) ? table.map((data) => executeAggr(expr, data)).filter((x) => !expr[Type.kType]?.ignoreNull || !isEmpty(x)) : Array.from(executeEval(table, expr)).filter((x) => !expr[Type.kType]?.ignoreNull || !isEmpty(x)), (expr) => Type.Array(Type.fromTerm(expr))); Eval3.get = multary("get", ([x, key], data) => executeEval(data, x)?.[executeEval(data, key)], (x, key) => Type.getInner(Type.fromTerm(x), key) ?? Type.Any); function getRecursive(args, data) { if (typeof args === "string") { return getRecursive(["_", args], data); } const [ref, path] = args; let value = data[ref]; if (!value) return value; if (path in value) return value[path]; const prefix = Object.keys(value).find((s) => path.startsWith(s + ".")) || path.split(".", 1)[0]; const rest = path.slice(prefix.length + 1).split(".").filter(Boolean); rest.unshift(prefix); for (const key of rest) { value = value[key]; if (!value) return value; } return value; } __name(getRecursive, "getRecursive"); function executeEvalExpr(expr, data) { for (const key in expr) { if (key in operators) { return operators[key](expr[key], data); } } return expr; } __name(executeEvalExpr, "executeEvalExpr"); function executeAggr(expr, data) { if (typeof expr === "string") { return getRecursive(expr, data); } return executeEvalExpr(expr, data); } __name(executeAggr, "executeAggr"); function executeEval(data, expr) { if (isComparable(expr) || (0, import_cosmokit3.isNullable)(expr)) { return expr; } if (Array.isArray(expr)) { return expr.map((item) => executeEval(data, item)); } return executeEvalExpr(expr, data); } __name(executeEval, "executeEval"); function executeUpdate(data, update, ref) { for (const key in update) { let root = data; const path = key.split("."); const last = path.pop(); for (const key2 of path) { root = root[key2] ||= {}; } root[last] = executeEval({ [ref]: data, _: data }, update[key]); } return data; } __name(executeUpdate, "executeUpdate"); // src/utils.ts function isComparable(value) { return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || value instanceof Date; } __name(isComparable, "isComparable"); function isFlat(value) { return !value || typeof value !== "object" || isEvalExpr(value) || Object.keys(value).length === 0 || Array.isArray(value) || value instanceof Date || value instanceof RegExp || import_cosmokit4.Binary.isSource(value); } __name(isFlat, "isFlat"); var letters = "abcdefghijklmnopqrstuvwxyz"; function randomId() { return Array(8).fill(0).map(() => letters[Math.floor(Math.random() * letters.length)]).join(""); } __name(randomId, "randomId"); function makeRegExp(source, flags) { return source instanceof RegExp && !flags ? source : new RegExp(source.source ?? source, flags ?? source.flags); } __name(makeRegExp, "makeRegExp"); function unravel(source, init) { const result = {}; for (const key in source) { let node = result; const segments = key.split(".").reverse(); for (let index = segments.length - 1; index > 0; index--) { const segment = segments[index]; node = node[segment] ??= {}; if (init) node = init(node); } node[segments[0]] = source[key]; } return result; } __name(unravel, "unravel"); function flatten(source, prefix = "", ignore = isFlat) { const result = {}; for (const key in source) { const value = source[key]; if (ignore(value)) { result[`${prefix}${key}`] = value; } else { Object.assign(result, flatten(value, `${prefix}${key}.`, ignore)); } } return result; } __name(flatten, "flatten"); function getCell(row, path) { if (path in row) return row[path]; if (path.includes(".")) { const index = path.indexOf("."); return getCell(row[path.slice(0, index)] ?? {}, path.slice(index + 1)); } else { return row[path]; } } __name(getCell, "getCell"); function isEmpty(value) { if ((0, import_cosmokit4.isNullable)(value)) return true; if (typeof value !== "object") return false; for (const key in value) { if (!isEmpty(value[key])) return false; } return true; } __name(isEmpty, "isEmpty"); // src/selection.ts var import_cosmokit5 = require("cosmokit"); var createRow = /* @__PURE__ */ __name((ref, expr = {}, prefix = "", model, intermediate) => new Proxy(expr, { get(target, key) { if (key === "$prefix") return prefix; if (key === "$model") return model; if (typeof key === "symbol" || key in target || key.startsWith("$")) return Reflect.get(target, key); if (intermediate) { if (Type.isArray(expr?.[Type.kType]) && Number.isInteger(+key)) { return createRow(ref, Eval3.get(expr, +key), "", model, Eval3.get(expr, +key)); } else { return createRow(ref, Eval3.get(intermediate, `${prefix}${key}`), `${prefix}${key}.`, model, intermediate); } } let type; const field = model?.fields[prefix + key]; if (Type.isArray(expr?.[Type.kType]) && Number.isInteger(+key)) { type = Type.getInner(expr?.[Type.kType]) ?? Type.fromField("expr"); return createRow(ref, Eval3.get(expr, +key), "", model, Eval3.get(expr, +key)); } else if (Type.getInner(expr?.[Type.kType], key)) { type = Type.getInner(expr?.[Type.kType], key); } else if (field) { type = Type.fromField(field); } else if (Object.keys(model?.fields).some((k) => k.startsWith(`${prefix}${key}.`))) { type = Type.Object(Object.fromEntries(Object.entries(model?.fields).filter(([k]) => k.startsWith(`${prefix}${key}`)).map(([k, field2]) => [k.slice(prefix.length + key.length + 1), Type.fromField(field2)]))); } else { type = model?.getType(`${prefix}${key}`) ?? Type.fromField("expr"); } const row = createRow(ref, Eval3("", [ref, `${prefix}${key}`], type), `${prefix}${key}.`, model); if (!field && Object.keys(model?.fields).some((k) => k.startsWith(`${prefix}${key}.`))) { return createRow(ref, Eval3.object(row), `${prefix}${key}.`, model); } else { return row; } } }), "createRow"); var Executable = class { static { __name(this, "Executable"); } row; model; driver; constructor(driver, payload) { Object.assign(this, payload); (0, import_cosmokit5.defineProperty)(this, "driver", driver); (0, import_cosmokit5.defineProperty)(this, "model", driver.model(this.table)); (0, import_cosmokit5.defineProperty)(this, "row", createRow(this.ref, {}, "", this.model)); } resolveQuery(query = {}) { if (typeof query === "function") { const expr = query(this.row); return expr["$expr"] ? expr : isEvalExpr(expr) ? { $expr: expr } : expr; } if (Array.isArray(query) || query instanceof RegExp || ["string", "number", "bigint"].includes(typeof query)) { const { primary } = this.model; if (Array.isArray(primary)) { throw new TypeError("invalid shorthand for composite primary key"); } return { [primary]: query }; } return query; } resolveField(field) { if (typeof field === "string") { return this.row[field]; } else if (typeof field === "function") { return field(this.row); } else { throw new TypeError("invalid field definition"); } } resolveFields(fields) { if (typeof fields === "string") fields = [fields]; if (Array.isArray(fields)) { const modelFields = Object.keys(this.model.fields); const entries = fields.flatMap((key) => { if (this.model.fields[key]) return [[key, this.row[key]]]; else if (modelFields.some((path) => path.startsWith(key + "."))) { return modelFields.filter((path) => path.startsWith(key + ".")).map((path) => [path, this.row[path]]); } return [[key, key.split(".").reduce((row, k) => row[k], this.row)]]; }); return Object.fromEntries(entries); } else { const entries = Object.entries(fields).flatMap(([key, field]) => { const expr = this.resolveField(field); if (expr["$object"] && !Type.fromTerm(expr).ignoreNull) { return Object.entries(expr["$object"]).map(([key2, expr2]) => [`${key}.${key2}`, expr2]); } return [[key, expr]]; }); return Object.fromEntries(entries); } } async execute() { await this.driver.database.prepared(); await this.driver._ensureSession(); return this.driver[this.type](this, ...this.args); } }; var Selection = class _Selection extends Executable { static { __name(this, "Selection"); } tables = {}; constructor(driver, table, query) { super(driver, { type: "get", ref: randomId(), table, query: null, args: [{ sort: [], limit: Infinity, offset: 0, group: void 0, having: Eval3.and(), optional: {} }] }); this.tables[this.ref] = this.model; this.query = this.resolveQuery(query); if (typeof table !== "string") { Object.assign(this.tables, table.tables); } } where(query) { this.query.$and ||= []; this.query.$and.push(this.resolveQuery(query)); return this; } limit(...args) { if (args.length > 1) this.offset(args.shift()); this.args[0].limit = args[0]; return this; } offset(offset) { this.args[0].offset = offset; return this; } orderBy(field, direction = "asc") { this.args[0].sort.push([this.resolveField(field), direction]); return this; } groupBy(fields, ...args) { this.args[0].fields = this.resolveFields(fields); this.args[0].group = Object.keys(this.args[0].fields); const extra = typeof args[0] === "function" ? void 0 : args.shift(); Object.assign(this.args[0].fields, this.resolveFields(extra || {})); if (args[0]) this.having(args[0]); return new _Selection(this.driver, this); } having(query) { this.args[0].having["$and"].push(this.resolveField(query)); return this; } project(fields) { this.args[0].fields = this.resolveFields(fields); return new _Selection(this.driver, this); } join(name, selection, callback = () => Eval3.and(), optional = false) { const fields = Object.fromEntries(Object.entries(this.model.fields).filter(([key, field]) => Field.available(field) && !key.startsWith(name + ".")).map(([key]) => [key, (row) => getCell(row[this.ref], key)])); const joinFields = Object.fromEntries(Object.entries(selection.model.fields).filter(([key, field]) => Field.available(field) || Field.available(this.model.fields[`${name}.${key}`])).map(([key]) => [ key, (row) => Field.available(this.model.fields[`${name}.${key}`]) ? getCell(row[this.ref], `${name}.${key}`) : getCell(row[name], key) ])); if (optional) { return this.driver.database.join({ [this.ref]: this, [name]: selection }, (t) => callback(t[this.ref], t[name]), { [this.ref]: false, [name]: true }).project({ ...fields, [name]: (row) => Eval3.ignoreNull(Eval3.object((0, import_cosmokit5.mapValues)(joinFields, (x) => x(row)))) }); } else { return this.driver.database.join({ [this.ref]: this, [name]: selection }, (t) => callback(t[this.ref], t[name])).project({ ...fields, [name]: (row) => Eval3.ignoreNull(Eval3.object((0, import_cosmokit5.mapValues)(joinFields, (x) => x(row)))) }); } } _action(type, ...args) { return new Executable(this.driver, { ...this, type, args }); } evaluate(callback) { const selection = new _Selection(this.driver, this); if (!callback) callback = /* @__PURE__ */ __name((row) => Eval3.array(Eval3.object(row)), "callback"); const expr = Array.isArray(callback) ? Eval3.select(...callback.map((x) => this.resolveField(x))) : this.resolveField(callback); if (isAggrExpr(expr)) (0, import_cosmokit5.defineProperty)(expr, Type.kType, Type.Array(Type.fromTerm(expr))); return Eval3.exec(selection._action("eval", expr)); } async execute(cursor) { if (typeof cursor === "function") { const selection = new _Selection(this.driver, this); return selection._action("eval", this.resolveField(cursor)).execute(); } if (Array.isArray(cursor)) { cursor = { fields: cursor }; } else if (!cursor) { cursor = {}; } if (cursor.fields) this.project(cursor.fields); if (cursor.limit !== void 0) this.limit(cursor.limit); if (cursor.offset !== void 0) this.offset(cursor.offset); if (cursor.sort) { for (const field in cursor.sort) { this.orderBy(field, cursor.sort[field]); } } const rows = await super.execute(); if (!cursor.fields) return rows; return rows.map((row) => { return (0, import_cosmokit5.filterKeys)(row, (key) => { return cursor.fields.some((k) => k === key || k.startsWith(`${key}.`)); }); }); } }; ((Selection2) => { function is(sel) { return sel && !!sel.tables; } Selection2.is = is; __name(is, "is"); })(Selection || (Selection = {})); function executeSort(data, modifier, name) { const { limit, offset, sort } = modifier; data.sort((a, b) => { for (const [field, direction] of sort) { const sign = direction === "asc" ? 1 : -1; const x = executeEval({ [name]: a, _: a }, field); const y = executeEval({ [name]: b, _: b }, field); if (x < y) return -sign; if (x > y) return sign; } return 0; }); return data.slice(offset, offset + limit); } __name(executeSort, "executeSort"); // src/database.ts function mergeQuery(base, query) { if (typeof query === "function") { return (row) => { const q = query(row); return { $expr: true, ...base, ...q.$expr ? q : { $expr: q } }; }; } else { return (_) => ({ $expr: true, ...base, ...query }); } } __name(mergeQuery, "mergeQuery"); var Database = class _Database extends import_cordis.Service { static { __name(this, "Database"); } static transact = Symbol("minato.transact"); static migrate = Symbol("minato.migrate"); tables = /* @__PURE__ */ Object.create(null); drivers = []; types = /* @__PURE__ */ Object.create(null); _driver; stashed = /* @__PURE__ */ new Set(); prepareTasks = /* @__PURE__ */ Object.create(null); migrateTasks = /* @__PURE__ */ Object.create(null); constructor(ctx) { ctx ||= new import_cordis.Context(); super(ctx, "model", true); } async connect(driver, ...args) { this.ctx.plugin(driver, args[0]); await this.ctx.start(); } refresh() { for (const name in this.tables) { this.prepareTasks[name] = this.prepare(name); } } async prepared() { if (this[_Database.migrate]) return; await Promise.all(Object.values(this.prepareTasks)); } getDriver(table) { if (Selection.is(table)) return table.driver; const model = this.tables[table]; if (!model) throw new Error(`cannot resolve table "${table}"`); return model.ctx?.get("database")?._driver; } async prepare(name) { this.stashed.add(name); await this.prepareTasks[name]; await Promise.resolve(); if (!this.stashed.delete(name)) return; const driver = this.getDriver(name); if (!driver) return; const { fields } = driver.model(name); Object.values(fields).forEach((field) => field?.transformers?.forEach((x) => driver.define(x))); await driver.prepare(name); await driver.prepareIndexes(name); } extend(name, fields, config = {}) { let model = this.tables[name]; if (!model) { model = this.tables[name] = new Model(name); } Object.entries(fields).forEach(([key, field]) => { const transformer = []; this.parseField(field, transformer, void 0, (value) => field = fields[key] = value); if (typeof field === "object") field.transformers = transformer; }); model.extend(fields, config); if ((0, import_cosmokit6.makeArray)(model.primary).every((key) => key in fields)) { (0, import_cosmokit6.defineProperty)(model, "ctx", this.ctx); } Object.entries(fields).forEach(([key, def]) => { if (!Relation.Type.includes(def.type)) return; const subprimary = !def.fields && (0, import_cosmokit6.makeArray)(model.primary).includes(key); const [relation, inverse] = Relation.parse(def, key, model, this.tables[def.table ?? key], subprimary); const relmodel = this.tables[relation.table]; if (!relmodel) throw new Error(`relation table ${relation.table} does not exist`); (model.fields[key] = Field.parse("expr")).relation = relation; if (def.target) { (relmodel.fields[def.target] ??= Field.parse("expr")).relation = inverse; } if (relation.type === "oneToOne" || relation.type === "manyToOne") { relation.fields.forEach((x, i) => { model.fields[x] ??= { ...relmodel.fields[relation.references[i]] }; if (!relation.required) { model.fields[x].nullable = true; model.fields[x].initial = null; } }); } else if (relation.type === "manyToMany") { const assocTable = Relation.buildAssociationTable(relation.table, name); if (this.tables[assocTable]) return; const shared = Object.entries(relation.shared).map(([x, y]) => [Relation.buildSharedKey(x, y), model.fields[x].deftype]); const fields2 = relation.fields.map((x) => [Relation.buildAssociationKey(x, name), model.fields[x].deftype]); const references = relation.references.map((x) => [Relation.buildAssociationKey(x, relation.table), relmodel.fields[x]?.deftype]); this.extend(assocTable, { ...Object.fromEntries([...shared, ...fields2, ...references]), [name]: { type: "manyToOne", table: name, fields: [...shared, ...fields2].map((x) => x[0]), references: [...Object.keys(relation.shared), ...relation.fields] }, [relation.table]: { type: "manyToOne", table: relation.table, fields: [...shared, ...references].map((x) => x[0]), references: [...Object.values(relation.shared), ...relation.references] } }, { primary: [...shared, ...fields2, ...references].map((x) => x[0]) }); } }); if (Array.isArray(model.primary) || model.fields[model.primary].relation) { model.primary = (0, import_cosmokit6.deduplicate)((0, import_cosmokit6.makeArray)(model.primary).map((key) => model.fields[key].relation?.fields || key).flat()); } model.unique = model.unique.map((keys) => typeof keys === "string" ? model.fields[keys].relation?.fields || keys : keys.map((key) => model.fields[key].relation?.fields || key).flat()); this.prepareTasks[name] = this.prepare(name); this.ctx.emit("model", name); } _parseField(field, transformers = [], setInitial, setField) { if (field === "object") { setInitial?.({}); setField?.({ initial: {}, deftype: "json", type: Type.Object() }); return Type.Object(); } else if (field === "array") { setInitial?.([]); setField?.({ initial: [], deftype: "json", type: Type.Array() }); return Type.Array(); } else if (typeof field === "string" && this.types[field]) { transformers.push({ types: [field], load: this.types[field].load, dump: this.types[field].dump }, ...this.types[field].transformers ?? []); setInitial?.(this.types[field].initial); setField?.({ ...this.types[field], type: field }); return Type.fromField(field); } else if (typeof field === "string") { setInitial?.(Field.getInitial(field.split("(")[0])); setField?.(field); return Type.fromField(field.split("(")[0]); } else if (typeof field === "object" && field.type === "object") { const inner = field.inner ? unravel(field.inner, (value) => (value.type = "object", value.inner ??= {})) : /* @__PURE__ */ Object.create(null); const initial = /* @__PURE__ */ Object.create(null); const res = Type.Object((0, import_cosmokit6.mapValues)(inner, (x, k) => this.parseField(x, transformers, (value) => initial[k] = value))); setInitial?.(Field.getInitial("json", initial)); setField?.({ initial: Field.getInitial("json", initial), ...field, deftype: "json", type: res }); return res; } else if (typeof field === "object" && field.type === "array") { const res = field.inner ? Type.Array(this.parseField(field.inner, transformers)) : Type.Array(); setInitial?.([]); setField?.({ initial: [], ...field, deftype: "json", type: res }); return res; } else if (typeof field === "object" && this.types[field.type]) { transformers.push({ types: [field.type], load: this.types[field.type].load, dump: this.types[field.type].dump }, ...this.types[field.type].transformers ?? []); setInitial?.(field.initial === void 0 ? this.types[field.type].initial : field.initial); setField?.({ initial: this.types[field.type].initial, ...field }); return Type.fromField(field.type); } else { setInitial?.(Field.getInitial(field.type, field.initial)); setField?.(field); return Type.fromField(field.type); } } parseField(field, transformers = [], setInitial, setField) { let midfield; let type = this._parseField(field, transformers, setInitial, (value) => (midfield = value, setField?.(value))); if (typeof field === "object" && field.load && field.dump) { if (type.inner) type = Type.fromField(this.define({ ...(0, import_cosmokit6.omit)(midfield, ["load", "dump"]), type })); const name = this.define({ ...field, deftype: midfield.deftype, type: type.type }); transformers.push({ types: [name], load: field.load, dump: field.dump }); setInitial?.(field.initial); setField?.({ ...field, deftype: midfield.deftype ?? this.types[type.type]?.deftype ?? type.type, initial: midfield.initial, type: name }); return Type.fromField(name); } if (typeof midfield === "object") setField?.({ ...midfield, deftype: midfield.deftype ?? this.types[type.type]?.deftype ?? type?.type }); return type; } define(name, field) { if (typeof name === "object") { field = name; name = void 0; } if (name && this.types[name]) throw new Error(`type "${name}" already defined`); if (!name) while (this.types[name = "_define_" + randomId()]) ; const transformers = []; const type = this._parseField(field, transformers, void 0, (value) => field = value); field.transformers = transformers; this.ctx.effect(() => { this.types[name] = { ...field }; this.types[name].deftype ??= this.types[field.type]?.deftype ?? type.type; return () => delete this.types[name]; }); return name; } migrate(name, fields, callback) { this.extend(name, fields, { callback }); } select(table, query, include) { let sel = new Selection(this.getDriver(table), table, query); if (typeof table !== "string") return sel; const whereOnly = include === null, isAssoc = !!include?.$assoc; const rawquery = typeof query === "function" ? query : () => query; const modelFields = this.tables[table].fields; if (include) include = (0, import_cosmokit6.filterKeys)(include, (key) => !!modelFields[key]?.relation); for (const key in { ...sel.query, ...sel.query.$not }) { if (modelFields[key]?.relation) { if (sel.query[key] === null && !modelFields[key].relation.required) { sel.query[key] = Object.fromEntries(modelFields[key].relation.references.map((k) => [k, null])); } if (sel.query[key] && typeof sel.query[key] !== "function" && typeof sel.query[key] === "object" && Object.keys(sel.query[key]).every((x) => modelFields[key].relation.fields.includes(`${key}.${x}`))) { Object.entries(sel.query[key]).forEach(([k, v]) => sel.query[`${key}.${k}`] = v); delete sel.query[key]; } if (sel.query.$not?.[key] === null && !modelFields[key].relation.required) { sel.query.$not[key] = Object.fromEntries(modelFields[key].relation.references.map((k) => [k, null])); } if (sel.query.$not?.[key] && typeof sel.query.$not[key] !== "function" && typeof sel.query.$not[key] === "object" && Object.keys(sel.query.$not[key]).every((x) => modelFields[key].relation.fields.includes(`${key}.${x}`))) { Object.entries(sel.query.$not[key]).forEach(([k, v]) => sel.query.$not[`${key}.${k}`] = v); delete sel.query.$not[key]; } if (!include || !Object.getOwnPropertyNames(include).includes(key)) { (include ??= {})[key] = true; } } } sel.query = (0, import_cosmokit6.omit)(sel.query, Object.keys(include ?? {})); if (Object.keys(sel.query.$not ?? {}).length) { sel.query.$not = (0, import_cosmokit6.omit)(sel.query.$not, Object.keys(include ?? {})); if (Object.keys(sel.query.$not).length === 0) Reflect.deleteProperty(sel.query, "$not"); } if (include && typeof include === "object") { if (typeof table !== "string") throw new Error("cannot include relations on derived selection"); const extraFields = []; const applyQuery = /* @__PURE__ */ __name((sel2, key) => { const query2 = rawquery(sel2.row); const relquery = query2[key] !== void 0 ? query2[key] : query2.$not?.[key] !== void 0 ? { $not: query2.$not?.[key] } : void 0; return relquery === void 0 ? sel2 : sel2.where(this.transformRelationQuery(table, sel2.row, key, relquery)); }, "applyQuery"); for (const key in include) { if (!include[key] || !modelFields[key]?.relation) continue; const relation = modelFields[key].relation; const relmodel = this.tables[relation.table]; if (relation.type === "oneToOne" || relation.type === "manyToOne") { sel = whereOnly ? sel : sel.join(key, this.select( relation.table, typeof include[key] === "object" ? (0, import_cosmokit6.filterKeys)(include[key], (k) => !relmodel.fields[k]?.relation) : {}, typeof include[key] === "object" ? (0, import_cosmokit6.filterKeys)(include[key], (k) => !!relmodel.fields[k]?.relation) : include[key] ), (self, other) => Eval3.and( ...relation.fields.map((k, i) => Eval3.eq(self[k], other[relation.references[i]])) ), !isAssoc); sel = applyQuery(sel, key); } else if (relation.type === "oneToMany") { sel = whereOnly ? sel : sel.join(key, this.select( relation.table, typeof include[key] === "object" ? (0, import_cosmokit6.filterKeys)(include[key], (k) => !relmodel.fields[k]?.relation) : {}, typeof include[key] === "object" ? (0, import_cosmokit6.filterKeys)(include[key], (k) => !!relmodel.fields[k]?.relation) : include[key] ), (self, other) => Eval3.and( ...relation.fields.map((k, i) => Eval3.eq(self[k], other[relation.references[i]])) ), true); sel = applyQuery(sel, key); sel = whereOnly ? sel : sel.groupBy([ ...Object.entries(modelFields).filter(([k, field]) => !extraFields.some((x) => k.startsWith(`${x}.`)) && Field.available(field)).map(([k]) => k), ...extraFields ], { [key]: (row) => Eval3.ignoreNull(Eval3.array(row[key])) }); } else if (relation.type === "manyToMany") { const assocTable = Relation.buildAssociationTable(relation.table, table); const references = relation.fields.map((x) => Relation.buildAssociationKey(x, table)); const shared = Object.entries(relation.shared).map(([x, y]) => [Relation.buildSharedKey(x, y), { field: x, reference: y }]); sel = whereOnly ? sel : sel.join( key, this.select(assocTable, {}, { $assoc: true, [relation.table]: include[key] }), (self, other) => Eval3.and( ...shared.map(([k, v]) => Eval3.eq(self[v.field], other[k])), ...relation.fields.map((k, i) => Eval3.eq(self[k], other[references[i]])) ), true ); sel = applyQuery(sel, key); sel = whereOnly ? sel : sel.groupBy([ ...Object.entries(modelFields).filter(([k, field]) => !extraFields.some((x) => k.startsWith(`${x}.`)) && Field.available(field)).map(([k]) => k), ...extraFields ], { [key]: (row) => Eval3.ignoreNull(Eval3.array(row[key][relation.table])) }); } extraFields.push(key); } } return sel; } join(tables, query = (...args) => Eval3.and(), optional) { const oldTables = tables; if (Array.isArray(oldTables)) { tables = Object.fromEntries(oldTables.map((name) => [name, this.select(name)])); } let sels = (0, import_cosmokit6.mapValues)(tables, (t) => { return typeof t === "string" ? this.select(t) : t; }); if (Object.keys(sels).length === 0) throw new Error("no tables to join"); const drivers = new Set(Object.values(sels).map((sel2) => sel2.driver[_Database.transact] ?? sel2.driver)); if (drivers.size !== 1) throw new Error("cannot join tables from different drivers"); if (Object.keys(sels).length === 2 && (optional?.[0] || optional?.[Object.keys(sels)[0]])) { if (optional[1] || optional[Object.keys(sels)[1]]) throw new Error("full join is not supported"); sels = Object.fromEntries(Object.entries(sels).reverse()); } const sel = new Selection([...drivers][0], sels); if (Array.isArray(oldTables)) { sel.args[0].having = Eval3.and(query(...oldTables.map((name) => sel.row[name]))); sel.args[0].optional = Object.fromEntries(oldTables.map((name, index) => [name, optional?.[index]])); } else { sel.args[0].having = Eval3.and(query(sel.row)); sel.args[0].optional = optional; } return this.select(sel); } async get(table, query, cursor) { let fields = Array.isArray(cursor) ? cursor : cursor?.fields; fields = fields ? Object.fromEntries(fields.map((x) => [x, true])) : cursor?.include; return this.select(table, query, fields).execute(cursor); } async eval(table, expr, query) { return this.select(table, query).execute(typeof expr === "function" ? expr : () => expr); } async set(table, query, update) { const rawupdate = typeof update === "function" ? update : () => update; let sel = this.select(table, query, null); if (typeof update === "function") update = update(sel.row); const primary = (0, import_cosmokit6.makeArray)(sel.model.primary); if (primary.some((key) => key in update)) { throw new TypeError(`cannot modify primary key`); } const relations = Object.entries(sel.model.fields).filter(([key, field]) => key in update && field.relation).map(([key, field]) => [key, field.relation]); if (relations.length) { return await this.ensureTransaction(async (database) => { const rows = await database.get(table, query); sel = database.select(table, query, null); let baseUpdate = (0, import_cosmokit6.omit)(rawupdate(sel.row), relations.map(([key]) => key)); baseUpdate = sel.model.format(baseUpdate); for (const [key] of relations) { await Promise.all(rows.map((row) => database.processRelationUpdate(table, row, key, rawupdate(row)[key]))); } return Object.keys(baseUpdate).length === 0 ? {} : await sel._action("set", baseUpdate).execute(); }); } update = sel.model.format(update); if (Object.keys(update).length === 0) return {}; return sel._action("set", update).execute(); } async remove(table, query) { const sel = this.select(table, query, null); return sel._action("remove").execute(); } async create(table, data) { const sel = this.select(table); if (!this.hasRelation(table, data)) { const { primary, autoInc } = sel.model; if (!autoInc) { const keys = (0, import_cosmokit6.makeArray)(primary); if (keys.some((key) => getCell(data, key) === void 0)) { throw new Error("missing primary key"); } } return sel._action("create", sel.model.create(data)).execute(); } else { return this.ensureTransaction((database) => database.createOrUpdate(table, data, false)); } } async upsert(table, upsert, keys) { const sel = this.select(table); if (typeof upsert === "function") upsert = upsert(sel.row); upsert = upsert.map((item) => sel.model.format(item)); keys = (0, import_cosmokit6.makeArray)(keys || sel.model.primary); return sel._action("upsert", upsert, keys).execute(); } makeProxy(marker, getDriver) { const drivers = /* @__PURE__ */ new Map(); const database = new Proxy(this, { get: /* @__PURE__ */ __name((target, p, receiver) => { if (p === marker) return true; if (p !== "getDriver") return Reflect.get(target, p, receiver); return (name) => { const original = this.getDriver(name); let driver = drivers.get(original); if (!driver) { driver = getDriver?.(original, database) ?? new Proxy(original, { get: /* @__PURE__ */ __name((target2, p2, receiver2) => { if (p2 === "database") return database; return Reflect.get(target2, p2, receiver2); }, "get") }); drivers.set(original, driver); } return driver; }; }, "get") }); return database; } withTransaction(callback) { return this.transact(callback); } async transact(callback) { if (this[_Database.transact]) throw new Error("nested transactions are not supported"); const finalTasks = []; const database = this.makeProxy(_Database.transact, (driver) => { let initialized = false, session; let _resolve; const sessionTask = new Promise((resolve) => _resolve = resolve); driver = new Proxy(driver, { get: /* @__PURE__ */ __name((target, p, receiver) => { if (p === _Database.transact) return target; if (p === "database") return database; if (p === "session") return session; if (p === "_ensureSession") return () => sessionTask; return Reflect.get(target, p, receiver); }, "get") }); finalTasks.push(driver.withTransaction((_session) => { if (initialized) initialTask = initialTaskFactory(); initialized = true; _resolve(session = _session); return initialTask; })); return driver; }); const initialTaskFactory = /* @__PURE__ */ __name(() => Promise.resolve().then(() => callback(database)), "initialTaskFactory"); let initialTask = initialTaskFactory(); return initialTask.catch(import_cosmokit6.noop).finally(() => Promise.all(finalTasks)); } async stopAll() { await Promise.all(this.drivers.splice(0, Infinity).map((driver) => driver.stop())); } async drop(table) { if (this[_Database.transact]) throw new Error("cannot drop table in transaction"); await this.getDriver(table).drop(table); } async dropAll() { if (this[_Database.transact]) throw new Error("cannot drop table in transaction"); await Promise.all(Object.values(this.drivers).map((driver) => driver.dropAll())); } async stats() { await this.prepared(); const stats = { size: 0, tables: {} }; await Promise.all(Object.values(this.drivers).map(async (driver) => { const { size = 0, tables } = await driver.stats(); stats.size += size; Object.assign(stats.tables, tables); })); return stats; } ensureTransaction(callback) { if (this[_Database.transact]) { return callback(this); } else { return this.transact(callback); } } transformRelationQuery(table, row, key, query) { const relation = this.tables[table].fields[key].relation; const results = []; if (relation.type === "oneToOne" || relation.type === "manyToOne") { if (query === null) { results.push(Eval3.nin( relation.fields.map((x) => row[x]), this.select(relation.table).evaluate(relation.references) )); } else { results.push(Eval3.in( relation.fields.map((x) => row[x]), this.select(relation.table, query).evaluate(relation.references) )); } } else if (relation.type === "oneToMany") { if (query.$or) results.push(Eval3.or(...query.$or.map((q) => this.transformRelationQuery(table, row, key, q).$expr))); if (query.$and) results.push(...query.$and.map((q) => this.transformRelationQuery(table, row, key, q).$expr)); if (query.$not) results.push(Eval3.not(this.transformRelationQuery(table, row, key, query.$not).$expr)); if (query.$some) { results.push(Eval3.in( relation.fields.map((x) => row[x]), this.select(relation.table, query.$some).evaluate(relation.references) )); } if (query.$none) { results.push(Eval3.nin( relation.fields.map((x) => row[x]), this.select(relation.table, query.$none).evaluate(relation.references) )); } if (query.$every) { results.push(Eval3.nin( relation.fields.map((x) => row[x]), this.select(relation.table, Eval3.not(query.$every)).evaluate(relation.references) )); } } else if (relation.type === "manyToMany") { const assocTable = Relation.buildAssociationTable(table, relation.table); const fields = relation.fields.map((x) => Relation.buildAssociationKey(x, table)); const references = relation.references.map((x) => Relation.buildAssociationKey(x, relation.table)); if (query.$or) results.push(Eval3.or(...query.$or.map((q) => this.transformRelationQuery(table, row, key, q).$expr))); if (query.$and) results.push(...query.$and.map((q) => this.transformRelationQuery(table, row, key, q).$expr)); if (query.$not) results.push(Eval3.not(this.transformRelationQuery(table, row, key, query.$not).$expr)); if (query.$some) { const innerTable = this.select(relation.table, query.$some).evaluate(relation.references); const relTable = this.select(assocTable, (r) => Eval3.in(references.map((x) => r[x]), innerTable)).evaluate(fields); results.push(Eval3.in(relation.fields.map((x) => row[x]), relTable)); } if (query.$none) { const innerTable = this.select(relation.table, query.$none).evaluate(relation.references); const relTable = this.select(assocTable, (r) => Eval3.in(references.map((x) => r[x]), innerTable)).evaluate(fields); results.push(Eval3.nin(relation.fields.map((x) => row[x]), relTable)); } if (query.$every) { const innerTable = this.select(relation.table, Eval3.not(query.$every)).evaluate(relation.references); const relTable = this.select(assocTable, (r) => Eval3.in(references.map((x) => r[x]), innerTable)).evaluate(fields); results.push(Eval3.nin(relation.fields.map((x) => row[x]), relTable)); } } return { $expr: Eval3.and(...results) }; } async createOrUpdate(table, data, upsert = true) { const sel = this.select(table); data = { ...data }; const tasks = [""]; for (const key in data) { if (data[key] !== void 0 && this.tables[table].fields[key]?.relation) { const relation = this.tables[table].fields[key].relation; if (relation.type === "oneToOne" && relation.required) tasks.push(key); else if (relation.type === "oneToOne") tasks.unshift(key); else if (relation.type === "oneToMany") tasks.push(key); else if (relation.type === "manyToOne") tasks.unshift(key); else if (relation.type === "manyToMany") tasks.push(key); } } for (const key of [...tasks]) { if (!key) { const { primary, autoInc } = sel.model; const keys = (0, import_cosmokit6.makeArray)(primary); if (keys.some((key2) => (0, import_cosmokit6.isNullable)(getCell(data, key2)))) { if (!autoInc) { throw new Error("missing primary key"); } else { delete data[primary]; upsert = false; } } if (upsert) { await sel._action("upsert", [sel.model.format((0, import_cosmokit6.omit)(data, tasks))], keys).execute(); } else { Object.assign(data, await sel._action("create", sel.model.create((0, import_cosmokit6.omit)(data, tasks))).execute()); } continue; } const value = data[key]; const relation = this.tables[table].fields[key].relation; if (relation.type === "oneToOne") { if (value.$literal) { data[key] = value.$literal; (0, import_cosmokit6.remove)(tasks, key); } else if (value.$create || !isUpdateExpr(value)) { const result = await this.createOrUpdate(relation.table, { ...Object.fromEntries(relation.references.map((k, i) => [k, getCell(data, relation.fields[i])])), ...value.$create ?? value }); if (!relation.required) { relation.references.forEach((k, i) => data[relation.fields[i]] = getCell(result, k)); } } else if (value.$upsert) { await this.upsert(relation.table, [{ ...Object.fromEntries(relation.references.map((k, i) => [k, getCell(data, relation.fields[i])])), ...value.$upsert }]); if (!relation.required) { relation.references.forEach((k, i) => data[relation.fields[i]] = getCell(value.$upsert, k)); } } else if (value.$connect) { if (relation.required) { await this.set( relation.table, value.$connect, Object.fromEntries(relation.references.map((k, i) => [k, getCell(data, relation.fields[i])])) ); } else { const result = relation.references.every((k) => value.$connect[k] !== void 0) ? [value.$connect] : await this.get(relation.table, value.$connect); if (result.length !== 1) throw new Error("related row not found or not unique"); relation.references.forEach((k, i) => data[relation.fields[i]] = getCell(result[0], k)); } } } else if (relation.type === "manyToOne") { if (value.$literal) { data[key] = value.$literal; (0, import_cosmokit6.remove)(tasks, key); } else if (value.$create || !isUpdateExpr(value)) { const result = await this.createOrUpdate(relation.table, value.$create ?? value); relation.references.forEach((k, i) => data[relation.fields[i]] = getCell(result, k)); } else if (value.$upsert) { await this.upsert(relation.table, [value.$upsert]); relation.references.forEach((k, i) => data[relation.fields[i]] = getCell(value.$upsert, k)); } else if (value.$connect) { const result = relation.references.every((k) => value.$connect[k] !== void 0) ? [value.$connect] : await this.get(relation.table, value.$connect); if (result.length !== 1) throw new Error("related row not found or not unique"); relation.references.forEach((k, i) => data[relation.fields[i]] = getCell(result[0], k)); } } else if (relation.type === "oneToMany") { if (value.$create || Array.isArray(value)) { for (const item of (0, import_cosmokit6.makeArray)(value.$create ?? value)) { await this.createOrUpdate(relation.table, { ...Object.fromEntries(relation.references.map((k, i) => [k, getCell(data, relation.fields[i])])), ...item }); } } if (value.$upsert) { await this.upsert(relation.table, (0, import_cosmokit6.makeArray)(value.$upsert).map((r) => ({ ...Object.fromEntries(relation.references.map((k, i) => [k, getCell(data, relation.fields[i])])), ...r }))); } if (value.$connect) { await this.set( relation.table, value.$connect, Object.fromEntries(relation.references.map((k, i) => [k, getCell(data, relation.fields[i])])) ); } } else if (relation.type === "manyToMany") { const assocTable = Relation.buildAssociationTable(relation.table, table); const fields = relation.fields.map((x) => Relation.buildAssociationKey(x, table)); const references = relation.references.map((x) => Relation.buildAssociationKey(x, relation.table)); const shared = Object.entries(relation.shared).map(([x, y]) => [Relation.buildSharedKey(x, y), { field: x, reference: y }]); const result = []; if (value.$create || Array.isArray(value)) { for (const item of (0, import_cosmokit6.makeArray)(value.$create ?? value)) { result.push(await this.createOrUpdate(relation.table, { ...Object.fromEntries(shared.map(([, v]) => [v.reference, getCell(item, v.reference) ?? getCell(data, v.field)])), ...item })); } } if (value.$upsert) { const upsert2 = (0, import_cosmokit6.makeArray)(value.$upsert).map((r) => ({ ...Object.fromEntries(shared.map(([, v]) => [v.reference, getCell(r, v.reference) ?? getCell(data, v.field)])), ...r })); await this.upsert(relation.table, upsert2); result.push(...upsert2); } if (value.$connect) { for (const item of (0, import_cosmokit6.makeArray)(value.$connect)) { if (references.every((k) => item[k] !== void 0)) result.push(item); else result.push(...await this.get(relation.table, item)); } } await this.upsert(assocTable, result.map((r) => ({ ...Object.fromEntries(shared.map(([k, v]) => [k, getCell(r, v.reference) ?? getCell(data, v.field)])), ...Object.fromEntries(fields.map((k, i) => [k, getCell(data, relation.fields[i])])), ...Object.fromEntries(references.map((k, i) => [k, getCell(r, relation.references[i])])) }))); } } return data; } async processRelationUpdate(table, row, key, value) { const model = this.tables[table], update = /* @__PURE__ */ Object.create(null); const relation = this.tables[table].fields[key].relation; if (relation.type === "oneToOne") { if (value === null) { value = relation.required ? { $remove: {} } : { $disconnect: {} }; } if (typeof value === "object" && !isUpdateExpr(value)) { value = { $create: value }; } if (value.$remove) { await this.remove(relation.table, Object.fromEntries(relation.references.map((k, i) => [k, getCell(row, relation.fields[i])]))); } if (value.$disconnect) { if (relation.required) { await this.set( relation.table, mergeQuery(Object.fromEntries(relation.references.map((k, i) => [k, getCell(row, relation.fields[i])])), value.$disconnect), Object.fromEntries(relation.references.map((k, i) => [k, null])) ); } else { Object.assign(update, Object.fromEntries(relation.fields.map((k, i) => [k, null]))); } } if (value.$set || typeof value === "function") { await this.set( relation.table, Object.fromEntries(relation.references.map((k, i) => [k, getCell(row, relation.fields[i])])), value.$set ?? value ); } if (value.$create) { const result = await this.createOrUpdate(relation.table, { ...Object.fromEntries(relation.references.map((k, i) => [k, getCell(row, relation.fields[i])])), ...value.$create }); if (!relation.required) { Object.assign(update, Object.fromEntries(relation.fields.map((k, i) => [k, getCell(result, relation.references[i])]))); } } if (value.$upsert) { await this.upsert(relation.table, (0, import_cosmokit6.makeArray)(value.$upsert).map((r) => ({ ...Object.fromEntries(relation.references.map((k, i) => [k, getCell(row, relation.fields[i])])), ...r }))); if (!relation.required) { Object.assign(update, Object.fromEntries(relation.fields.map((k, i) => [k, getCell(value.$upsert, relation.references[i])]))); } } if (value.$connect) { if (relation.required) { await this.set( relation.table, value.$connect, Object.fromEntries(relation.references.map((k, i) => [k, getCell(row, relation.fields[i])])) ); } else { const result = await this.get(relation.table, value.$connect); if (result.length !== 1) throw new Error("related row not found or not unique"); Object.assign(update, Object.fromEntries(relation.fields.map((k, i) => [k, getCell(result[0], relation.references[i])]))); } } } else if (relation.type === "manyToOne") { if (value === null) { value = { $disconnect: {} }; } if (typeof value === "object" && !isUpdateExpr(value)) { value = { $create: value }; } if (value.$remove) { await this.remove(relation.table, Object.fromEntries(relation.references.map((k, i) => [k, getCell(row, relation.fields[i])]))); } if (value.$disconnect) { Object.assign(update, Object.fromEntries(relation.fields.map((k, i) => [k, null]))); } if (value.$set || typeof value === "function") { await this.set( relation.table, Object.fromEntries(relation.references.map((k, i) => [k, getCell(row, relation.fields[i])])), value.$set ?? value ); } if (value.$create) { const result = await this.createOrUpdate(relation.table, { ...Object.fromEntries(relation.references.map((k, i) => [k, getCell(row, relation.fields[i])])), ...value.$create }); Object.assign(update, Object.fromEntries(relation.fields.map((k, i) => [k, getCell(result, relation.references[i])]))); } if (value.$upsert) { await this.upsert(relation.table, (0, import_cosmokit6.makeArray)(value.$upsert).map((r) => ({ ...Object.fromEntries(relation.references.map((k, i) => [k, getCell(row, relation.fields[i])])), ...r }))); Object.assign(update, Object.fromEntries(relation.fields.map((k, i) => [k, getCell(value.$upsert, relation.references[i])]))); } if (value.$connect) { const result = await this.get(relation.table, value.$connect); if (result.length !== 1) throw new Error("related row not found or not unique"); Object.assign(update, Object.fromEntries(relation.fields.map((k, i) => [k, getCell(result[0], relation.references[i])]))); } } else if (relation.type === "oneToMany") { if (Array.isArray(value)) { const $create = [], $upsert = []; value.forEach((item) => this.hasRelation(relation.table, item) ? $create.push(item) : $upsert.push(item)); value = { $remove: {}, $create, $upsert }; } if (value.$remove) { await this.remove(relation.table, mergeQuery(Object.fromEntries(relation.references.map((k, i) => [k, row[relation.fields[i]]])), value.$remove)); } if (value.$disconnect) { await this.set( relation.table, mergeQuery(Object.fromEntries(relation.references.map((k, i) => [k, getCell(row, relation.fields[i])])), value.$disconnect), Object.fromEntries(relation.references.map((k, i) => [k, null])) ); } if (value.$set || typeof value === "function") { for (const setexpr of (0, import_cosmokit6.makeArray)(value.$set ?? value)) { const [query, update2] = setexpr.update ? [setexpr.where, setexpr.update] : [{}, setexpr]; await this.set( relation.table, mergeQuery(Object.fromEntries(relation.references.map((k, i) => [k, row[relation.fields[i]]])), query), update2 ); } } if (value.$create) { for (const item of (0, import_cosmokit6.makeArray)(value.$create)) { await this.createOrUpdate(relation.table, { ...Object.fromEntries(relation.references.map((k, i) => [k, getCell(row, relation.fields[i])])), ...item }); } } if (value.$upsert) { await this.upsert(relation.table, (0, import_cosmokit6.makeArray)(value.$upsert).map((r) => ({ ...Object.fromEntries(relation.references.map((k, i) => [k, getCell(row, relation.fields[i])])), ...r }))); } if (value.$connect) { await this.set( relation.table, value.$connect, Object.fromEntries(relation.references.map((k, i) => [k, row[relation.fields[i]]])) ); } } else if (relation.type === "manyToMany") { const assocTable = Relation.buildAssociationTable(table, relation.table); const fields = relation.fields.map((x) => Relation.buildAssociationKey(x, table)); const references = relation.references.map((x) => Relation.buildAssociationKey(x, relation.table)); const shared = Object.entries(relation.shared).map(([x, y]) => [Relation.buildSharedKey(x, y), { field: x, reference: y }]); if (Array.isArray(value)) { const $create = [], $upsert = []; value.forEach((item) => this.hasRelation(relation.table, item) ? $create.push(item) : $upsert.push(item)); value = { $disconnect: {}, $create, $upsert }; } if (value.$remove) { const rows = await this.select(assocTable, { ...Object.fromEntries(shared.map(([k, v]) => [k, getCell(row, v.field)])), ...Object.fromEntries(fields.map((k, i) => [k, getCell(row, relation.fields[i])])), [relation.table]: value.$remove }, null).execute(); await this.remove(assocTable, (r) => Eval3.in( [...shared.map(([k, v]) => r[k]), ...fields.map((x) => r[x]), ...references.map((x) => r[x])], rows.map((r2) => [...shared.map(([k, v]) => getCell(r2, k)), ...fields.map((x) => getCell(r2, x)), ...references.map((x) => getCell(r2, x))]) )); await this.remove(relation.table, (r) => Eval3.in( [...shared.map(([k, v]) => r[v.reference]), ...relation.references.map((x) => r[x])], rows.map((r2) => [...shared.map(([k, v]) => getCell(r2, k)), ...references.map((x) => getCell(r2, x))]) )); } if (value.$disconnect) { const rows = await this.select(assocTable, { ...Object.fromEntries(shared.map(([k, v]) => [k, getCell(row, v.field)])), ...Object.fromEntries(fields.map((k, i) => [k, getCell(row, relation.fields[i])])), [relation.table]: value.$disconnect }, null).execute(); await this.remove(assocTable, (r) => Eval3.in( [...shared.map(([k, v]) => r[k]), ...fields.map((x) => r[x]), ...references.map((x) => r[x])], rows.map((r2) => [...shared.map(([k, v]) => getCell(r2, k)), ...fields.map((x) => getCell(r2, x)), ...references.map((x) => getCell(r2, x))]) )); } if (value.$set) { for (const setexpr of (0, import_cosmokit6.makeArray)(value.$set)) { const [query, update2] = setexpr.update ? [setexpr.where, setexpr.update] : [{}, setexpr]; const rows = await this.select(assocTable, (r) => ({ ...Object.fromEntries(shared.map(([k, v]) => [k, getCell(row, v.field)])), ...Object.fromEntries(fields.map((k, i) => [k, getCell(row, relation.fields[i])])), [relation.table]: query }), null).execute(); await this.set( relation.table, (r) => Eval3.in( [...shared.map(([k, v]) => r[v.reference]), ...relation.references.map((x) => r[x])], rows.map((r2) => [...shared.map(([k, v]) => getCell(r2, k)), ...references.map((x) => getCell(r2, x))]) ), update2 ); } } if (value.$create) { const result = []; for (const item of (0, import_cosmokit6.makeArray)(value.$create)) { result.push(await this.createOrUpdate(relation.table, { ...Object.fromEntries(relation.references.map((k, i) => [k, getCell(row, relation.fields[i])])), ...item })); } await this.upsert(assocTable, result.map((r) => ({ ...Object.fromEntries(shared.map(([k, v]) => [k, getCell(row, v.field)])), ...Object.fromEntries(fields.map((k, i) => [k, row[relation.fields[i]]])), ...Object.fromEntries(references.map((k, i) => [k, r[relation.references[i]]])) }))); } if (value.$upsert) { await this.upsert(relation.table, (0, import_cosmokit6.makeArray)(value.$upsert).map((r) => ({ ...Object.fromEntries(relation.references.map((k, i) => [k, getCell(row, relation.fields[i])])), ...r }))); await this.upsert(assocTable, (0, import_cosmokit6.makeArray)(value.$upsert).map((r) => ({ ...Object.fromEntries(shared.map(([k, v]) => [k, getCell(row, v.field)])), ...Object.fromEntries(fields.map((k, i) => [k, row[relation.fields[i]]])), ...Object.fromEntries(references.map((k, i) => [k, r[relation.references[i]]])) }))); } if (value.$connect) { const rows = await this.get( relation.table, mergeQuery(Object.fromEntries(shared.map(([k, v]) => [v.reference, getCell(row, v.field)])), value.$connect) ); await this.upsert(assocTable, rows.map((r) => ({ ...Object.fromEntries(shared.map(([k, v]) => [k, getCell(row, v.field)])), ...Object.fromEntries(fields.map((k, i) => [k, row[relation.fields[i]]])), ...Object.fromEntries(references.map((k, i) => [k, r[relation.references[i]]])) }))); } } if (Object.keys(update).length) { await this.set(table, (0, import_cosmokit6.pick)(model.format(row), (0, import_cosmokit6.makeArray)(model.primary)), update); } } hasRelation(table, data) { for (const key in data) { if (data[key] !== void 0 && this.tables[table].fields[key]?.relation) return true; } return false; } }; // src/driver.ts var import_cosmokit7 = require("cosmokit"); var import_cordis2 = require("cordis"); var Driver = class { constructor(ctx, config) { this.ctx = ctx; this.config = config; this.database = ctx.model; this.logger = ctx.logger(this.constructor.name); ctx.on("ready", async () => { await Promise.resolve(); await this.start(); ctx.model.drivers.push(this); ctx.model.refresh(); const database = Object.create(ctx.model); (0, import_cosmokit7.defineProperty)(database, "ctx", ctx); database._driver = this; database[import_cordis2.Service.tracker] = { associate: "database", property: "ctx" }; ctx.set("database", import_cordis2.Context.associate(database, "database")); }); ctx.on("dispose", async () => { (0, import_cosmokit7.remove)(ctx.model.drivers, this); await this.stop(); }); } static { __name(this, "Driver"); } static inject = ["model"]; database; logger; types = /* @__PURE__ */ Object.create(null); model(table) { if (typeof table === "string") { const model2 = this.database.tables[table]; if (model2) return model2; throw new TypeError(`unknown table name "${table}"`); } if (Selection.is(table)) { if (!table.args[0].fields && (typeof table.table === "string" || Selection.is(table.table))) { return table.model; } const model2 = new Model("temp"); if (table.args[0].fields) { model2.fields = (0, import_cosmokit7.mapValues)(table.args[0].fields, (expr) => ({ type: Type.fromTerm(expr) })); } else { model2.fields = (0, import_cosmokit7.mapValues)(table.model.fields, (field) => ({ type: Type.fromField(field) })); } return model2; } const model = new Model("temp"); for (const key in table) { const submodel = this.model(table[key]); for (const field in submodel.fields) { if (!Field.available(submodel.fields[field])) continue; model.fields[`${key}.${field}`] = { expr: Eval3("", [table[key].ref, field], Type.fromField(submodel.fields[field])), type: Type.fromField(submodel.fields[field]) }; } } return model; } async migrate(name, hooks) { const database = this.database.makeProxy(Database.migrate); const model = this.model(name); await (database.migrateTasks[name] = Promise.resolve(database.migrateTasks[name]).then(() => { return Promise.all([...model.migrations].map(async ([migrate, keys]) => { try { if (!hooks.before(keys)) return; await migrate(database); hooks.after(keys); } catch (reason) { hooks.error(reason); } })); }).then(hooks.finalize).catch(hooks.error)); } define(converter) { converter.types.forEach((type) => this.types[type] = converter); } async _ensureSession() { } async prepareIndexes(table) { const oldIndexes = await this.getIndexes(table); const { indexes } = this.model(table); for (const index of indexes) { const oldIndex = oldIndexes.find((info) => info.name === index.name); if (!oldIndex) { await this.createIndex(table, index); } else if (!(0, import_cosmokit7.deepEqual)(oldIndex, index)) { await this.dropIndex(table, index.name); await this.createIndex(table, index); } } } }; // src/error.ts var RuntimeError = class _RuntimeError extends Error { constructor(code, message) { super(message || code.replace("-", " ")); this.code = code; } static { __name(this, "RuntimeError"); } name = "RuntimeError"; static check(error, code) { if (!(error instanceof _RuntimeError)) return false; return !code || error.message === code; } }; // src/query.ts var import_cosmokit8 = require("cosmokit"); var queryOperators = { // logical $or: /* @__PURE__ */ __name((query, data) => query.reduce((prev, query2) => prev || executeFieldQuery(query2, data), false), "$or"), $and: /* @__PURE__ */ __name((query, data) => query.reduce((prev, query2) => prev && executeFieldQuery(query2, data), true), "$and"), $not: /* @__PURE__ */ __name((query, data) => !executeFieldQuery(query, data), "$not"), // existence $exists: /* @__PURE__ */ __name((query, data) => query !== (0, import_cosmokit8.isNullable)(data), "$exists"), // comparison $eq: /* @__PURE__ */ __name((query, data) => data.valueOf() === query.valueOf(), "$eq"), $ne: /* @__PURE__ */ __name((query, data) => data.valueOf() !== query.valueOf(), "$ne"), $gt: /* @__PURE__ */ __name((query, data) => data.valueOf() > query.valueOf(), "$gt"), $gte: /* @__PURE__ */ __name((query, data) => data.valueOf() >= query.valueOf(), "$gte"), $lt: /* @__PURE__ */ __name((query, data) => data.valueOf() < query.valueOf(), "$lt"), $lte: /* @__PURE__ */ __name((query, data) => data.valueOf() <= query.valueOf(), "$lte"), // membership $in: /* @__PURE__ */ __name((query, data) => query.includes(data), "$in"), $nin: /* @__PURE__ */ __name((query, data) => !query.includes(data), "$nin"), // regexp $regex: /* @__PURE__ */ __name((query, data) => makeRegExp(query).test(data), "$regex"), $regexFor: /* @__PURE__ */ __name((query, data) => typeof query === "string" ? makeRegExp(data).test(query) : makeRegExp(data, query.flags).test(query.input), "$regexFor"), // bitwise $bitsAllSet: /* @__PURE__ */ __name((query, data) => (query & data) === query, "$bitsAllSet"), $bitsAllClear: /* @__PURE__ */ __name((query, data) => (query & data) === 0, "$bitsAllClear"), $bitsAnySet: /* @__PURE__ */ __name((query, data) => (query & data) !== 0, "$bitsAnySet"), $bitsAnyClear: /* @__PURE__ */ __name((query, data) => (query & data) !== query, "$bitsAnyClear"), // list $el: /* @__PURE__ */ __name((query, data) => data.some((item) => executeFieldQuery(query, item)), "$el"), $size: /* @__PURE__ */ __name((query, data) => data.length === query, "$size") }; function executeFieldQuery(query, data) { if (Array.isArray(query)) { return query.includes(data); } else if (query instanceof RegExp) { return query.test(data); } else if (isComparable(query)) { return data.valueOf() === query.valueOf(); } else if ((0, import_cosmokit8.isNullable)(query)) { return (0, import_cosmokit8.isNullable)(data); } for (const key in query) { if (key in queryOperators) { if (!queryOperators[key](query[key], data)) return false; } } return true; } __name(executeFieldQuery, "executeFieldQuery"); function executeQuery(data, query, ref, env = {}) { const entries = Object.entries(query); return entries.every(([key, value]) => { if (key === "$and") { return value.reduce((prev, query2) => prev && executeQuery(data, query2, ref, env), true); } else if (key === "$or") { return value.reduce((prev, query2) => prev || executeQuery(data, query2, ref, env), false); } else if (key === "$not") { return !executeQuery(data, value, ref, env); } else if (key === "$expr") { return executeEval({ ...env, [ref]: data, _: data }, value); } try { const flattenQuery = isFlat(query[key]) ? { [key]: query[key] } : flatten(query[key], `${key}.`); return Object.entries(flattenQuery).every(([key2, value2]) => executeFieldQuery(value2, getCell(data, key2))); } catch { return false; } }); } __name(executeQuery, "executeQuery"); // src/index.ts var import_cordis3 = require("cordis"); var Types = Symbol("minato.types"); var Tables = Symbol("minato.tables"); var src_default = Database; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { $, Database, Driver, Eval, Field, Logger, Model, Relation, RuntimeError, Schema, Selection, Tables, Type, Types, executeEval, executeQuery, executeSort, executeUpdate, flatten, getCell, hasSubquery, isAggrExpr, isComparable, isEmpty, isEvalExpr, isFlat, isUpdateExpr, makeRegExp, randomId, unravel, z }); //# sourceMappingURL=index.cjs.map