tinycoll
Version:
A minimal reactive document store with Mongo-like querying, reactivity, TTL support, and optional persistence.
110 lines (109 loc) • 3.54 kB
JavaScript
function getValue(obj, path) {
return path.split('.').reduce((o, k) => o?.[k], obj);
}
function setValue(obj, path, value) {
const keys = path.split('.');
const last = keys.pop();
let updated = structuredClone(obj);
let target = updated;
for (const k of keys) {
target[k] = structuredClone(target[k] ?? {});
target = target[k];
}
target[last] = value;
return updated;
}
function unsetValue(obj, path) {
const keys = path.split('.');
const last = keys.pop();
let updated = structuredClone(obj);
let parent = updated;
for (const k of keys) {
parent[k] = structuredClone(parent[k] ?? {});
parent = parent[k];
}
delete parent[last];
return updated;
}
export function applyModifier(doc, mod, ctx = {}) {
let updated = doc;
if (mod.$setOnInsert && ctx.inserting) {
for (const [k, v] of Object.entries(mod.$setOnInsert)) {
if (getValue(updated, k) === undefined) {
updated = setValue(updated, k, v);
}
}
}
if (mod.$set) {
for (const [k, v] of Object.entries(mod.$set)) {
const current = getValue(updated, k);
if (current !== v)
updated = setValue(updated, k, v);
}
}
if (mod.$inc) {
for (const [k, v] of Object.entries(mod.$inc)) {
const current = getValue(updated, k);
updated = setValue(updated, k, (typeof current === 'number' ? current : 0) + v);
}
}
if (mod.$min) {
for (const [k, v] of Object.entries(mod.$min)) {
const current = getValue(updated, k);
if (typeof current === 'number' && typeof v === 'number') {
if (v < current)
updated = setValue(updated, k, v);
}
}
}
if (mod.$max) {
for (const [k, v] of Object.entries(mod.$max)) {
const current = getValue(updated, k);
if (typeof current === 'number' && typeof v === 'number') {
if (v > current)
updated = setValue(updated, k, v);
}
}
}
if (mod.$mul) {
for (const [k, v] of Object.entries(mod.$mul)) {
const current = getValue(updated, k);
updated = setValue(updated, k, (typeof current === 'number' ? current : 0) * v);
}
}
if (mod.$unset) {
for (const k of Object.keys(mod.$unset)) {
if (getValue(updated, k) !== undefined) {
updated = unsetValue(updated, k);
}
}
}
if (mod.$push) {
for (const [k, v] of Object.entries(mod.$push)) {
const arr = getValue(updated, k);
updated = setValue(updated, k, Array.isArray(arr) ? [...arr, v] : [v]);
}
}
if (mod.$addToSet) {
for (const [k, v] of Object.entries(mod.$addToSet)) {
const arr = getValue(updated, k);
if (Array.isArray(arr)) {
if (!arr.includes(v)) {
updated = setValue(updated, k, [...arr, v]);
}
}
else {
updated = setValue(updated, k, [v]);
}
}
}
if (mod.$pull) {
for (const [k, v] of Object.entries(mod.$pull)) {
const arr = getValue(updated, k);
if (Array.isArray(arr)) {
updated = setValue(updated, k, arr.filter((item) => item !== v));
}
}
}
return updated;
}