UNPKG

probejs-core

Version:

A powerful tool for traversing and investigating nested objects

203 lines 7.18 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Probe = void 0; const validation_1 = require("./validation"); const errors_1 = require("./errors"); const timing_1 = require("./utils/timing"); class Probe { constructor(data, options = {}) { var _a, _b, _c, _d; this.cache = new Map(); this.statistics = { searches: 0, cacheHits: 0, cacheMisses: 0, averageSearchTime: 0, }; if (!data || (typeof data !== "object" && !Array.isArray(data))) { throw new errors_1.ProbeError("Data must be a non-null object or array"); } this.data = data; this.options = { caseSensitive: (_a = options.caseSensitive) !== null && _a !== void 0 ? _a : true, maxDepth: (_b = options.maxDepth) !== null && _b !== void 0 ? _b : Infinity, pathDelimiter: (_c = options.pathDelimiter) !== null && _c !== void 0 ? _c : ".", caching: (_d = options.caching) !== null && _d !== void 0 ? _d : true, }; (0, validation_1.validateOptions)(this.options); (0, validation_1.detectCircular)(this.data); } find(key, occurrence = "first") { (0, validation_1.validateKey)(key); const startTime = (0, timing_1.getTime)(); let results = this.getCachedResults(key); if (!results) { results = this.findAll(key); this.cacheResults(key, results); } this.trackSearch(startTime); if (results.length === 0) return undefined; switch (occurrence) { case "first": return results[0]; case "last": return results[results.length - 1]; case "all": return results || []; default: throw new errors_1.ProbeError(`Invalid occurrence type: ${occurrence}`); } } isProtectedKey(key) { return key === "__proto__" || key === "constructor" || key === "prototype"; } handleArrayAccess(current, key) { const index = parseInt(key, 10); if (isNaN(index) || index < 0 || index >= current.length) { return undefined; } return current[index]; } findByPath(path) { (0, validation_1.validatePath)(path); const startTime = (0, timing_1.getTime)(); try { const keys = path.split(this.options.pathDelimiter); let current = this.data; for (let i = 0; i < keys.length; i++) { const key = keys[i]; // Protect against prototype pollution if (this.isProtectedKey(key)) { return undefined; } if (current === null || typeof current !== "object") { return undefined; } if (Array.isArray(current)) { const result = this.handleArrayAccess(current, key); if (result === undefined) { return undefined; } current = result; } else { if (!Object.prototype.hasOwnProperty.call(current, key)) { // If the key doesn't exist, check if the remaining path forms a valid key const remainingPath = keys.slice(i).join(this.options.pathDelimiter); if (Object.prototype.hasOwnProperty.call(current, remainingPath)) { current = current[remainingPath]; break; // Exit the loop since we've processed the full key } return undefined; } current = current[key]; } } return { value: current, path, key: keys[keys.length - 1], }; } finally { this.trackSearch(startTime); } } findWhere(predicate) { if (typeof predicate !== "function") { throw new errors_1.ProbeError("Predicate must be a function"); } const startTime = (0, timing_1.getTime)(); const results = []; try { this.traverse(this.data, [], (value, key, path) => { if (predicate(value, key, path)) { results.push({ value, path: path.join(this.options.pathDelimiter), key, }); } }); return results; } finally { this.trackSearch(startTime); } } getStatistics() { return { ...this.statistics }; } clearCache() { this.cache.clear(); } setCaching(enabled) { if (!enabled) { this.clearCache(); } this.options.caching = enabled; } findAll(key) { const results = []; this.traverse(this.data, [], (value, currentKey, path) => { const normalizedKey = this.options.caseSensitive ? currentKey : currentKey.toLowerCase(); const searchKey = this.options.caseSensitive ? key : key.toLowerCase(); if (normalizedKey === searchKey) { results.push({ value, path: path.join(this.options.pathDelimiter), key: currentKey, }); } }); return results; } traverse(obj, path, callback) { if (!obj || typeof obj !== "object" || path.length >= this.options.maxDepth) { return; } if (Array.isArray(obj)) { obj.forEach((value, index) => { callback(value, index.toString(), [...path, index.toString()]); this.traverse(value, [...path, index.toString()], callback); }); } else { for (const [key, value] of Object.entries(obj)) { callback(value, key, [...path, key]); this.traverse(value, [...path, key], callback); } } } getCachedResults(key) { if (!this.options.caching) return null; const cached = this.cache.get(key); if (cached) { this.statistics.cacheHits++; return cached; } this.statistics.cacheMisses++; return null; } cacheResults(key, results) { if (this.options.caching) { this.cache.set(key, results); } } trackSearch(startTime) { const searchTime = (0, timing_1.getTime)() - startTime; this.statistics.searches++; this.statistics.averageSearchTime = (this.statistics.averageSearchTime * (this.statistics.searches - 1) + searchTime) / this.statistics.searches; } } exports.Probe = Probe; //# sourceMappingURL=Probe.js.map