/**
* ChainFlow.js - The Ultimate JavaScript Utility Library
* Powerful, complex operations made simple with chainable API
*
* @version 1.0.0
* @author ChainFlow Labs
* @license MIT
*/
(function(global) {
'use strict';
class ChainFlow {
constructor(data) {
this.data = data;
this.operations = [];
this.cached = new Map();
this.middleware = [];
}
// ==================== ARRAY UTILITIES ====================
/**
* Smart filter with multiple conditions and AI-like matching
*/
filter(condition) {
if (typeof condition === 'string') {
// Smart search with fuzzy matching
return this._chain(data => this._smartFilter(data, condition));
} else if (typeof condition === 'object') {
// Multi-field object matching
return this._chain(data => this._objectFilter(data, condition));
} else if (typeof condition === 'function') {
return this._chain(data => data.filter(condition));
}
return this;
}
/**
* Advanced grouping with nested support
*/
groupBy(key, options = {}) {
return this._chain(data => {
const groups = {};
const nested = options.nested || false;
const transform = options.transform || (x => x);
data.forEach(item => {
const groupKey = this._getNestedValue(item, key);
const transformedKey = transform(groupKey);
if (!groups[transformedKey]) {
groups[transformedKey] = [];
}
groups[transformedKey].push(item);
});
if (nested) {
return this._createNestedGroups(groups, options.levels || 1);
}
return groups;
});
}
/**
* Smart sorting with multiple criteria
*/
sort(criteria) {
return this._chain(data => {
if (typeof criteria === 'string') {
return [...data].sort((a, b) => this._smartCompare(a, b,
criteria));
} else if (Array.isArray(criteria)) {
return [...data].sort((a, b) => this._multiSort(a, b,
criteria));
} else if (typeof criteria === 'function') {
return [...data].sort(criteria);
}
return data;
});
}
/**
* Advanced map with async support and error handling
*/
map(transformer, options = {}) {
return this._chain(async data => {
const concurrent = options.concurrent || false;
const batchSize = options.batchSize || 10;
const errorHandler = options.onError || (err =>
console.error(err));
if (concurrent) {
return this._concurrentMap(data, transformer, batchSize,
errorHandler);
} else {
return this._sequentialMap(data, transformer, errorHandler);
}
});
}
/**
* Intelligent reduce with accumulator patterns
*/
reduce(reducer, initialValue, options = {}) {
return this._chain(data => {
const parallel = options.parallel || false;
const chunks = options.chunks || 4;
if (parallel && data.length > 100) {
return this._parallelReduce(data, reducer, initialValue,
chunks);
} else {
return data.reduce(reducer, initialValue);
}
});
}
/**
* Find with fuzzy matching and scoring
*/
find(query, options = {}) {
return this._chain(data => {
const fuzzy = options.fuzzy || false;
const threshold = options.threshold || 0.6;
const key = options.key;
if (fuzzy) {
const results = this._fuzzySearch(data, query, key, threshold);
return options.all ? results : results[0];
} else {
return data.find(item => this._matches(item, query, key));
}
});
}
// ==================== OBJECT UTILITIES ====================
/**
* Deep merge with conflict resolution
*/
merge(...objects) {
return this._chain(data => {
const strategy = objects[objects.length - 1];
const isStrategy = typeof strategy === 'object' &&
strategy.strategy;
const mergeStrategy = isStrategy ? strategy.strategy : 'overwrite';
const objsToMerge = isStrategy ? objects.slice(0, -1) : objects;
return this._deepMerge([data, ...objsToMerge], mergeStrategy);
});
}
/**
* Transform object structure with mapping
*/
transform(mapping) {
return this._chain(data => {
if (Array.isArray(data)) {
return data.map(item => this._transformObject(item, mapping));
} else {
return this._transformObject(data, mapping);
}
});
}
/**
* Extract nested values with default fallbacks
*/
extract(paths, options = {}) {
const defaultValue = options.default;
const flatten = options.flatten || false;
return this._chain(data => {
const extracted = {};
paths.forEach(path => {
const value = this._getNestedValue(data, path, defaultValue);
if (flatten) {
const key = path.split('.').pop();
extracted[key] = value;
} else {
this._setNestedValue(extracted, path, value);
}
});
return extracted;
});
}
// ==================== ASYNC UTILITIES ====================
/**
* Smart retry with exponential backoff
*/
retry(fn, options = {}) {
const maxAttempts = options.attempts || 3;
const delay = options.delay || 1000;
const backoff = options.backoff || 'exponential';
const condition = options.retryIf || (() => true);
return this._chain(async data => {
let attempt = 0;
let lastError;
while (attempt < maxAttempts) {
try {
return await fn(data);
} catch (error) {
lastError = error;
attempt++;
if (attempt >= maxAttempts || !condition(error)) {
throw error;
}
const waitTime = this._calculateDelay(delay, attempt,
backoff);
await this._sleep(waitTime);
}
}
});
}
/**
* Parallel execution with concurrency control
*/
parallel(tasks, options = {}) {
const concurrency = options.concurrency || 5;
const failFast = options.failFast || false;
return this._chain(async data => {
const results = [];
const errors = [];
const executing = [];
for (let i = 0; i < tasks.length; i++) {
const task = tasks[i];
const promise = this._executeTask(task, data, i)
.then(result => {
results[i] = result;
})
.catch(error => {
errors[i] = error;
if (failFast) throw error;
});
executing.push(promise);
if (executing.length >= concurrency) {
await Promise.race(executing);
this._cleanupCompleted(executing);
}
}
await Promise.all(executing);
return { results, errors };
});
}
/**
* Debounce with advanced options
*/
debounce(fn, delay = 300, options = {}) {
const leading = options.leading || false;
const trailing = options.trailing !== false;
const maxWait = options.maxWait;
return this._chain(data => {
return new Promise((resolve) => {
const debounced = this._createDebounce(
() => resolve(fn(data)),
delay,
{ leading, trailing, maxWait }
);
debounced();
});
});
}
/**
* Throttle with burst handling
*/
throttle(fn, limit = 100, options = {}) {
const burst = options.burst || false;
const queue = options.queue || false;
return this._chain(data => {
return new Promise((resolve) => {
const throttled = this._createThrottle(
() => resolve(fn(data)),
limit,
{ burst, queue }
);
throttled();
});
});
}
// ==================== VALIDATION UTILITIES ====================
/**
* Schema validation with custom rules
*/
validate(schema, options = {}) {
const strict = options.strict || false;
const transform = options.transform || false;
return this._chain(data => {
const result = this._validateSchema(data, schema, strict);
if (result.isValid) {
return transform ? result.transformed : data;
} else {
const error = new Error('Validation failed');
error.details = result.errors;
throw error;
}
});
}
/**
* Type checking with coercion
*/
ensureType(type, options = {}) {
const coerce = options.coerce || false;
const strict = options.strict || false;
return this._chain(data => {
const currentType = this._getType(data);
if (currentType === type) return data;
if (coerce) {
return this._coerceType(data, type, strict);
} else if (strict) {
throw new Error(`Expected ${type}, got ${currentType}`);
}
return data;
});
}
// ==================== PERFORMANCE UTILITIES ====================
/**
* Memoization with TTL and size limits
*/
memo(options = {}) {
const ttl = options.ttl || 300000; // 5 minutes
const maxSize = options.maxSize || 100;
const keyGenerator = options.keyGen || JSON.stringify;
return this._chain(data => {
const key = keyGenerator(data);
const cached = this.cached.get(key);
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.value;
}
// Clean cache if too large
if (this.cached.size >= maxSize) {
this._cleanCache();
}
this.cached.set(key, {
value: data,
timestamp: Date.now()
});
return data;
});
}
/**
* Batch processing with progress tracking
*/
batch(processor, options = {}) {
const size = options.size || 10;
const delay = options.delay || 0;
const onProgress = options.onProgress || (() => {});
return this._chain(async data => {
const results = [];
const batches = this._createBatches(data, size);
for (let i = 0; i < batches.length; i++) {
const batch = batches[i];
const batchResult = await processor(batch, i);
results.push(...batchResult);
onProgress({
completed: i + 1,
total: batches.length,
progress: ((i + 1) / batches.length) * 100
});
if (delay > 0 && i < batches.length - 1) {
await this._sleep(delay);
}
}
return results;
});
}
// ==================== CHAIN OPERATIONS ====================
/**
* Conditional execution
*/
when(condition, trueFn, falseFn = null) {
return this._chain(data => {
const shouldExecute = typeof condition === 'function'
? condition(data)
: condition;
if (shouldExecute) {
return trueFn ? trueFn(data) : data;
} else {
return falseFn ? falseFn(data) : data;
}
});
}
/**
* Try-catch with fallback
*/
attempt(fn, fallback = null) {
return this._chain(async data => {
try {
return await fn(data);
} catch (error) {
if (fallback) {
return typeof fallback === 'function' ? fallback(error,
data) : fallback;
}
throw error;
}
});
}
/**
* Tap for side effects without changing data
*/
tap(fn) {
return this._chain(async data => {
await fn(data);
return data;
});
}
/**
* Execute and return final result
*/
async execute() {
let result = this.data;
for (const operation of this.operations) {
result = await operation(result);
}
return result;
}
/**
* Get result synchronously (for non-async operations)
*/
value() {
let result = this.data;
for (const operation of this.operations) {
result = operation(result);
}
return result;
}
// ==================== PRIVATE METHODS ====================
_chain(operation) {
const newChain = new ChainFlow(this.data);
newChain.operations = [...this.operations, operation];
newChain.cached = this.cached;
return newChain;
}
_smartFilter(data, query) {
if (!Array.isArray(data)) return data;
return data.filter(item => {
if (typeof item === 'string') {
return this._fuzzyMatch(item, query) > 0.3;
} else if (typeof item === 'object') {
return Object.values(item).some(value =>
String(value).toLowerCase().includes(query.toLowerCase())
);
}
return false;
});
}
_objectFilter(data, conditions) {
if (!Array.isArray(data)) return data;
return data.filter(item => {
return Object.entries(conditions).every(([key, value]) => {
const itemValue = this._getNestedValue(item, key);
if (typeof value === 'object' && value.operator) {
return this._applyOperator(itemValue, value.operator,
value.value);
}
return itemValue === value;
});
});
}
_smartCompare(a, b, key) {
const aVal = this._getNestedValue(a, key);
const bVal = this._getNestedValue(b, key);
if (typeof aVal === 'number' && typeof bVal === 'number') {
return aVal - bVal;
}
return String(aVal).localeCompare(String(bVal));
}
_multiSort(a, b, criteria) {
for (const criterion of criteria) {
let comparison = 0;
const { key, order = 'asc' } = typeof criterion === 'string'
? { key: criterion, order: 'asc' }
: criterion;
const aVal = this._getNestedValue(a, key);
const bVal = this._getNestedValue(b, key);
if (typeof aVal === 'number' && typeof bVal === 'number') {
comparison = aVal - bVal;
} else {
comparison = String(aVal).localeCompare(String(bVal));
}
if (comparison !== 0) {
return order === 'desc' ? -comparison : comparison;
}
}
return 0;
}
async _concurrentMap(data, transformer, batchSize, errorHandler) {
const results = [];
const batches = this._createBatches(data, batchSize);
for (const batch of batches) {
const promises = batch.map(async (item, index) => {
try {
return await transformer(item, index);
} catch (error) {
errorHandler(error, item, index);
return null;
}
});
const batchResults = await Promise.all(promises);
results.push(...batchResults);
}
return results;
}
async _sequentialMap(data, transformer, errorHandler) {
const results = [];
for (let i = 0; i < data.length; i++) {
try {
const result = await transformer(data[i], i);
results.push(result);
} catch (error) {
errorHandler(error, data[i], i);
results.push(null);
}
}
return results;
}
_parallelReduce(data, reducer, initialValue, chunks) {
const chunkSize = Math.ceil(data.length / chunks);
const promises = [];
for (let i = 0; i < chunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, data.length);
const chunk = data.slice(start, end);
promises.push(
Promise.resolve(chunk.reduce(reducer, initialValue))
);
}
return Promise.all(promises).then(results =>
results.reduce(reducer, initialValue)
);
}
_fuzzySearch(data, query, key, threshold) {
const results = data
.map(item => {
const searchText = key ? this._getNestedValue(item, key) :
item;
const score = this._fuzzyMatch(String(searchText), query);
return { item, score };
})
.filter(result => result.score >= threshold)
.sort((a, b) => b.score - a.score)
.map(result => result.item);
return results;
}
_fuzzyMatch(text, pattern) {
const textLower = text.toLowerCase();
const patternLower = pattern.toLowerCase();
let score = 0;
let textIndex = 0;
for (let i = 0; i < patternLower.length; i++) {
const char = patternLower[i];
const foundIndex = textLower.indexOf(char, textIndex);
if (foundIndex === -1) {
score -= 1;
} else {
score += 1;
if (foundIndex === textIndex) {
score += 1; // Bonus for consecutive matches
}
textIndex = foundIndex + 1;
}
}
return Math.max(0, score / Math.max(text.length, pattern.length));
}
_deepMerge(objects, strategy) {
const result = {};
objects.forEach(obj => {
Object.keys(obj).forEach(key => {
if (result[key] === undefined) {
result[key] = obj[key];
} else if (this._isObject(result[key]) &&
this._isObject(obj[key])) {
result[key] = this._deepMerge([result[key], obj[key]],
strategy);
} else {
switch (strategy) {
case 'concat':
if (Array.isArray(result[key]) &&
Array.isArray(obj[key])) {
result[key] = [...result[key], ...obj[key]];
} else {
result[key] = obj[key];
}
break;
case 'merge':
result[key] = [result[key], obj[key]];
break;
default: // overwrite
result[key] = obj[key];
}
}
});
});
return result;
}
_getNestedValue(obj, path, defaultValue = undefined) {
const keys = path.split('.');
let current = obj;
for (const key of keys) {
if (current == null || typeof current !== 'object') {
return defaultValue;
}
current = current[key];
}
return current !== undefined ? current : defaultValue;
}
_setNestedValue(obj, path, value) {
const keys = path.split('.');
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!(key in current) || typeof current[key] !== 'object') {
current[key] = {};
}
current = current[key];
}
current[keys[keys.length - 1]] = value;
}
_calculateDelay(baseDelay, attempt, backoff) {
switch (backoff) {
case 'exponential':
return baseDelay * Math.pow(2, attempt - 1);
case 'linear':
return baseDelay * attempt;
case 'fixed':
default:
return baseDelay;
}
}
_sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
_createBatches(array, size) {
const batches = [];
for (let i = 0; i < array.length; i += size) {
batches.push(array.slice(i, i + size));
}
return batches;
}
_isObject(value) {
return value !== null && typeof value === 'object' && !
Array.isArray(value);
}
_getType(value) {
if (value === null) return 'null';
if (Array.isArray(value)) return 'array';
return typeof value;
}
_cleanCache() {
const entries = Array.from(this.cached.entries());
entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
// Remove oldest 25%
const toRemove = Math.ceil(entries.length * 0.25);
for (let i = 0; i < toRemove; i++) {
this.cached.delete(entries[i][0]);
}
}
}
// Static factory methods
ChainFlow.from = (data) => new ChainFlow(data);
ChainFlow.range = (start, end, step = 1) => {
const array = [];
for (let i = start; i < end; i += step) {
array.push(i);
}
return new ChainFlow(array);
};
ChainFlow.empty = () => new ChainFlow([]);
ChainFlow.of = (...items) => new ChainFlow(items);
// Utility shortcuts
const $ = ChainFlow.from;
$.range = ChainFlow.range;
$.empty = ChainFlow.empty;
$.of = ChainFlow.of;
// Export
if (typeof module !== 'undefined' && module.exports) {
module.exports = { ChainFlow, $ };
} else if (typeof define === 'function' && define.amd) {
define([], () => ({ ChainFlow, $ }));
} else {
global.ChainFlow = ChainFlow;
global.$ = ChainFlow.from;
global.CF = ChainFlow.from; // alias tambahan
})(typeof window !== 'undefined' ? window : this);