UNPKG

jsonion

Version:

A lightweight JSON file-based database with nested data access and manipulation capabilities.

310 lines 9.62 kB
import { db } from "./db.js"; import { dbError } from "./types.js"; /** * Parse dot notation path into array of keys * Example: 'user.profile.address[0].city' => ['user', 'profile', 'address', '0', 'city'] */ function parsePath(path) { return path .replace(/\[(\w+)\]/g, '.$1') .replace(/^\./, '') .split('.') .filter(key => key.length > 0); } /** * Get value at deep path */ db.prototype.getPath = function (path) { const keys = parsePath(path); let current = this.all(); for (const key of keys) { if (current === undefined || current === null) { return undefined; } current = current[key]; } return current; }; /** * Set value at deep path (creates path if it doesn't exist) */ db.prototype.setPath = function (path, value) { const keys = parsePath(path); if (keys.length === 0) { throw new dbError('set', 'Invalid path'); } const data = this.all(); const lastKey = keys[keys.length - 1]; let current = data; // Navigate to parent object for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; const nextKey = keys[i + 1]; if (!(key in current) || typeof current[key] !== 'object' || current[key] === null) { // Create object or array based on next key current[key] = /^\d+$/.test(nextKey) ? [] : {}; } current = current[key]; } current[lastKey] = value; // Save all data const rootKey = keys[0]; this.set(rootKey, data[rootKey]); }; /** * Check if path exists */ db.prototype.hasPath = function (path) { const keys = parsePath(path); let current = this.all(); for (const key of keys) { if (current === undefined || current === null || !(key in current)) { return false; } current = current[key]; } return true; }; /** * Delete value at path */ db.prototype.deletePath = function (path) { const keys = parsePath(path); if (keys.length === 0) return false; const data = this.all(); const lastKey = keys[keys.length - 1]; let current = data; // Navigate to parent for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; if (!(key in current)) { return false; } current = current[key]; } if (!(lastKey in current)) { return false; } delete current[lastKey]; // Save root object const rootKey = keys[0]; this.set(rootKey, data[rootKey]); return true; }; /** * Update value at path (only if it exists) */ db.prototype.updatePath = function (path, value) { if (!this.hasPath(path)) { return false; } const oldValue = this.getPath(path); const newValue = typeof value === 'function' ? value(oldValue) : value; this.setPath(path, newValue); return true; }; /** * Find all occurrences of a key-value pair in nested data */ db.prototype.findDeep = function (key, value) { const results = []; function search(obj, currentPath = '') { if (obj === null || typeof obj !== 'object') return; if (Array.isArray(obj)) { obj.forEach((item, index) => { search(item, `${currentPath}[${index}]`); }); } else { for (const [k, v] of Object.entries(obj)) { const newPath = currentPath ? `${currentPath}.${k}` : k; if (k === key && v === value) { results.push({ path: newPath, value: v }); } if (typeof v === 'object' && v !== null) { search(v, newPath); } } } } search(this.all()); return results; }; /** * Get all values matching a wildcard path * Example: pluckPath('users[*].name') returns all user names */ db.prototype.pluckPath = function (path) { const results = []; const parts = path.split('[*]'); if (parts.length === 1) { // No wildcard, just get the value const value = this.getPath(path); return value !== undefined ? [value] : []; } // Get base path const basePath = parts[0]; const baseData = basePath ? this.getPath(basePath) : this.all(); if (!Array.isArray(baseData)) { return []; } // Get remaining path after [*] const remainingPath = parts.slice(1).join('[*]').replace(/^\./, ''); for (let i = 0; i < baseData.length; i++) { if (remainingPath) { const fullPath = basePath ? `${basePath}[${i}].${remainingPath}` : `[${i}].${remainingPath}`; const subResults = this.pluckPath(fullPath); results.push(...subResults); } else { results.push(baseData[i]); } } return results; }; /** * Find items in array at path matching predicate */ db.prototype.findInPath = function (path, predicate) { const data = this.getPath(path); if (!Array.isArray(data)) { return []; } if (typeof predicate === 'function') { return data.filter(predicate); } // Object predicate return data.filter(item => { if (typeof item !== 'object' || item === null) return false; return Object.entries(predicate).every(([key, value]) => { if (typeof value === 'object' && value !== null && !Array.isArray(value)) { // Handle operators like { $lt: 100 } const operators = value; const itemValue = item[key]; for (const [op, opValue] of Object.entries(operators)) { switch (op) { case '$lt': if (!(itemValue < opValue)) return false; break; case '$lte': if (!(itemValue <= opValue)) return false; break; case '$gt': if (!(itemValue > opValue)) return false; break; case '$gte': if (!(itemValue >= opValue)) return false; break; case '$ne': if (!(itemValue !== opValue)) return false; break; case '$in': if (!Array.isArray(opValue) || !opValue.includes(itemValue)) return false; break; case '$nin': if (!Array.isArray(opValue) || opValue.includes(itemValue)) return false; break; } } return true; } return item[key] === value; }); }); }; /** * Push value to array at path */ db.prototype.pushToPath = function (path, value) { const data = this.getPath(path); if (!Array.isArray(data)) { // Create array if doesn't exist this.setPath(path, [value]); return; } data.push(value); this.setPath(path, data); }; /** * Remove items from array at path matching predicate */ db.prototype.pullFromPath = function (path, predicate) { const data = this.getPath(path); if (!Array.isArray(data)) { return 0; } const originalLength = data.length; const filtered = data.filter(item => !predicate(item)); this.setPath(path, filtered); return originalLength - filtered.length; }; /** * Update item in array at path */ db.prototype.updateInPath = function (arrayPath, findCondition, updateData) { const data = this.getPath(arrayPath); if (!Array.isArray(data)) { return false; } let found = false; const predicate = typeof findCondition === 'function' ? findCondition : (item) => { if (typeof item !== 'object' || item === null) return false; return Object.entries(findCondition).every(([key, value]) => item[key] === value); }; const updated = data.map(item => { if (predicate(item)) { found = true; return { ...item, ...updateData }; } return item; }); if (found) { this.setPath(arrayPath, updated); } return found; }; /** * Merge data at path without removing existing properties */ db.prototype.mergePath = function (path, data) { const existing = this.getPath(path); if (typeof existing === 'object' && existing !== null && !Array.isArray(existing)) { this.setPath(path, { ...existing, ...data }); } else { this.setPath(path, data); } }; /** * Copy data from one path to another */ db.prototype.copyPath = function (sourcePath, destPath) { const data = this.getPath(sourcePath); if (data === undefined) { throw new dbError('read', `Source path does not exist: ${sourcePath}`); } // Deep clone to avoid reference issues const cloned = JSON.parse(JSON.stringify(data)); this.setPath(destPath, cloned); }; /** * Move data from one path to another */ db.prototype.movePath = function (sourcePath, destPath) { this.copyPath(sourcePath, destPath); this.deletePath(sourcePath); }; //# sourceMappingURL=DeepAccess.js.map