UNPKG

@2toad/reflex

Version:

A simple approach to state management

268 lines 9.33 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.map = map; exports.filter = filter; exports.merge = merge; exports.combine = combine; exports.scan = scan; exports.debounce = debounce; exports.switchMap = switchMap; exports.mergeMap = mergeMap; exports.concatMap = concatMap; exports.catchError = catchError; const reflex_1 = require("./reflex"); /** * Maps values from a source reflex using a transform function * @param source The source reflex to map from * @param fn The transform function to apply to each value */ function map(source, fn) { const errorState = (0, reflex_1.reflex)({ initialValue: null }); const mapped = (0, reflex_1.reflex)({ initialValue: fn(source.value) }); mapped._errorState = errorState; source.subscribe((value) => { try { mapped.setValue(fn(value)); // Clear error state on success errorState.setValue(null); } catch (error) { errorState.setValue(error); } }); return mapped; } /** * Filters values from a source reflex using a predicate function * @param source The source reflex to filter * @param predicate The function to test each value */ function filter(source, predicate) { const filtered = (0, reflex_1.reflex)({ initialValue: predicate(source.value) ? source.value : undefined, }); source.subscribe((value) => predicate(value) && filtered.setValue(value)); return filtered; } /** * Merges multiple reflex sources into a single reflex that emits whenever any source emits * @param sources Array of reflex sources to merge */ function merge(sources) { if (!sources.length) { throw new Error("merge requires at least one source"); } const [firstSource, ...restSources] = sources; const merged = (0, reflex_1.reflex)({ initialValue: firstSource.value }); const subscriber = (value) => merged.setValue(value); restSources.forEach((source) => source.subscribe(subscriber)); firstSource.subscribe(subscriber); return merged; } /** * Combines multiple reflex sources into a single reflex that emits arrays of their latest values * @param sources Array of reflex sources to combine */ function combine(sources) { if (!sources.length) { throw new Error("combine requires at least one source"); } const combined = (0, reflex_1.reflex)({ initialValue: sources.map((source) => source.value), }); sources.forEach((source, index) => { source.subscribe((value) => { combined.setValue([...combined.value.slice(0, index), value, ...combined.value.slice(index + 1)]); }); }); return combined; } /** * Creates a reflex that accumulates values over time using a reducer function * @param source The source reflex * @param reducer The reducer function to accumulate values * @param seed The initial accumulator value */ function scan(source, reducer, seed) { const scanned = (0, reflex_1.reflex)({ initialValue: seed }); let accumulator = seed; source.subscribe((value) => { accumulator = reducer(accumulator, value); scanned.setValue(accumulator); }); return scanned; } /** * Debounces a reflex source, only emitting after a specified delay has passed without any new emissions * @param source The source reflex to debounce * @param delayMs The delay in milliseconds */ function debounce(source, delayMs) { const debounced = (0, reflex_1.reflex)({ initialValue: source.value }); let timeoutId = null; source.subscribe((value) => { if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(() => debounced.setValue(value), delayMs); }); return debounced; } /** * Projects each value from the source to an inner reflex, cancelling previous projections * when a new value arrives. Like map, but for async/reflex operations where only the latest matters. * @param source The source reflex * @param project Function that returns a reflex or promise for each source value */ function switchMap(source, project) { const result = (0, reflex_1.reflex)({ initialValue: undefined }); let currentProjection = Symbol(); const handleProjection = async (value, projectionId) => { try { const projected = project(value); if (projected instanceof Promise) { const resolvedValue = await projected; if (currentProjection === projectionId) { result.setValue(resolvedValue); } } else { if (currentProjection === projectionId) { result.setValue(projected.value); projected.subscribe((newValue) => { if (currentProjection === projectionId) { result.setValue(newValue); } }); } } } catch (error) { console.error("[Reflex SwitchMap Error]", error); throw error; } }; handleProjection(source.value, currentProjection); source.subscribe((value) => { currentProjection = Symbol(); handleProjection(value, currentProjection); }); return result; } /** * Projects each value from the source to an inner reflex, maintaining all active projections * concurrently. Like map, but for async/reflex operations that should run in parallel. * @param source The source reflex * @param project Function that returns a reflex or promise for each source value */ function mergeMap(source, project) { const result = (0, reflex_1.reflex)({ initialValue: undefined }); const handleProjection = async (value) => { try { const projected = project(value); if (projected instanceof Promise) { result.setValue(await projected); } else { result.setValue(projected.value); projected.subscribe((newValue) => result.setValue(newValue)); } } catch (error) { console.error("[Reflex MergeMap Error]", error); throw error; } }; handleProjection(source.value); source.subscribe(handleProjection); return result; } /** * Projects each value from the source to an inner reflex, processing projections in sequence. * Like map, but for async/reflex operations that must complete in order. * @param source The source reflex * @param project Function that returns a reflex or promise for each source value */ function concatMap(source, project) { const result = (0, reflex_1.reflex)({ initialValue: undefined }); const queue = [source.value]; let isProcessing = false; const processQueue = async () => { if (isProcessing || !queue.length) return; isProcessing = true; const value = queue[0]; try { const projected = project(value); if (projected instanceof Promise) { result.setValue(await projected); } else { result.setValue(projected.value); const unsubscribe = projected.subscribe((newValue) => result.setValue(newValue)); await new Promise((resolve) => setTimeout(resolve, 0)); unsubscribe(); } queue.shift(); } catch (error) { console.error("[Reflex ConcatMap Error]", error); queue.shift(); } finally { isProcessing = false; processQueue(); } }; processQueue(); source.subscribe((value) => { queue.push(value); processQueue(); }); return result; } /** * Catches errors in a reflex stream and allows for recovery or fallback values * @param source The source reflex * @param errorHandler Function that returns a reflex or value to recover from the error */ function catchError(source, errorHandler) { const result = (0, reflex_1.reflex)({ initialValue: undefined }); let currentFallback = null; const handleError = (error) => { currentFallback?.unsubscribe(); currentFallback = null; const handled = errorHandler(error); if (handled instanceof Object && "subscribe" in handled) { const fallbackReflex = handled; const unsubscribe = fallbackReflex.subscribe((value) => result.setValue(value)); currentFallback = { reflex: fallbackReflex, unsubscribe }; result.setValue(fallbackReflex.value); } else { result.setValue(handled); } }; try { result.setValue(source.value); } catch (error) { handleError(error); } source.subscribe((value) => { if (currentFallback) { currentFallback.unsubscribe(); currentFallback = null; } try { result.setValue(value); } catch (error) { handleError(error); } }); const errorState = source._errorState; errorState?.subscribe((error) => error && handleError(error)); return result; } //# sourceMappingURL=operators.js.map