{"version":3,"file":"createSelectors.mjs","sources":["../../../../src/lib/state/core/createSelectors.ts"],"sourcesContent":["/**\n * @file Selector Factory\n * @description Create memoized selectors with automatic dependency tracking\n *\n * This module provides utilities for creating performant, memoized selectors\n * that prevent unnecessary re-renders by caching results and using shallow\n * equality comparisons.\n */\n\nimport { shallow } from 'zustand/shallow';\nimport type { Selector, EqualityFn } from './types';\n\n// ============================================================================\n// Memoization Cache Types\n// ============================================================================\n\n/**\n * Memoization cache entry\n */\ninterface CacheEntry<T> {\n  deps: unknown[];\n  result: T;\n}\n\n// ============================================================================\n// Core Selector Factories\n// ============================================================================\n\n/**\n * Create a memoized selector that only recomputes when dependencies change\n *\n * Similar to Reselect's createSelector, but optimized for Zustand.\n * Uses reference equality for dependencies and optional custom equality for results.\n *\n * @example\n * ```typescript\n * // Combine multiple primitive selectors into a computed value\n * const selectFullName = createSelector(\n *   [selectFirstName, selectLastName],\n *   (first, last) => `${first} ${last}`\n * );\n *\n * // With custom equality\n * const selectFilteredItems = createSelector(\n *   [selectItems, selectFilter],\n *   (items, filter) => items.filter(item => item.includes(filter)),\n *   shallow // Use shallow equality for array comparison\n * );\n * ```\n */\nexport function createSelector<TState, TDeps extends unknown[], TResult>(\n  dependencySelectors: { [K in keyof TDeps]: Selector<TState, TDeps[K]> },\n  combiner: (...deps: TDeps) => TResult,\n  equalityFn: EqualityFn<TResult> = Object.is\n): Selector<TState, TResult> {\n  let cache: CacheEntry<TResult> | null = null;\n\n  return (state: TState): TResult => {\n    // Extract dependencies from state\n    const deps = dependencySelectors.map((selector) => selector(state)) as TDeps;\n\n    // Check if dependencies changed (reference equality)\n    if (cache !== null && deps.every((dep, i) => Object.is(dep, cache?.deps[i]))) {\n      return cache.result;\n    }\n\n    // Recompute result\n    const result = combiner(...deps);\n\n    // Check if result changed (for referential stability)\n    if (cache !== null && equalityFn(cache.result, result)) {\n      // Update deps but keep old result reference\n      cache.deps = deps;\n      return cache.result;\n    }\n\n    // Update cache with new result\n    cache = { deps, result };\n    return result;\n  };\n}\n\n/**\n * Create a selector that returns a stable object reference\n *\n * Use this for selectors that return objects to prevent unnecessary re-renders.\n * Uses shallow equality to compare object properties.\n *\n * @example\n * ```typescript\n * // This selector will return the same reference if properties are equal\n * const selectDisplaySettings = createObjectSelector((state) => ({\n *   locale: state.locale,\n *   timezone: state.timezone,\n *   dateFormat: state.dateFormat,\n * }));\n * ```\n */\nexport function createObjectSelector<TState, TResult extends object>(\n  selector: Selector<TState, TResult>\n): Selector<TState, TResult> {\n  let cache: TResult | null = null;\n\n  return (state: TState): TResult => {\n    const result = selector(state);\n\n    // Use shallow equality for object comparison\n    if (cache && shallow(cache, result)) {\n      return cache;\n    }\n\n    cache = result;\n    return result;\n  };\n}\n\n/**\n * Create a selector that returns a stable array reference\n *\n * Use this for selectors that return arrays to prevent unnecessary re-renders.\n * Uses element-wise reference equality comparison.\n *\n * @example\n * ```typescript\n * const selectActiveItems = createArraySelector((state) =>\n *   state.items.filter(item => item.active)\n * );\n * ```\n */\nexport function createArraySelector<TState, TItem>(\n  selector: Selector<TState, TItem[]>\n): Selector<TState, TItem[]> {\n  let cache: TItem[] | null = null;\n\n  return (state: TState): TItem[] => {\n    const result = selector(state);\n\n    // Compare array length and element references\n    if (\n      cache &&\n      cache.length === result.length &&\n      cache.every((item, i) => Object.is(item, result[i]))\n    ) {\n      return cache;\n    }\n\n    cache = result;\n    return result;\n  };\n}\n\n/** Default maximum cache size for parameterized selectors */\nconst DEFAULT_MAX_CACHE_SIZE = 100;\n\n/**\n * Create a parameterized selector (selector factory)\n *\n * Use this when you need selectors that depend on external parameters.\n * The returned function is memoized per parameter value with LRU eviction.\n *\n * @param factory - Factory function that creates a selector for each parameter\n * @param maxSize - Maximum cache size (default: 100). When exceeded, least recently used entries are evicted.\n *\n * @example\n * ```typescript\n * const selectItemById = createParameterizedSelector(\n *   (id: string) => (state) => state.items.find(item => item.id === id)\n * );\n *\n * // In component:\n * const item = useStore(selectItemById('123'));\n * ```\n */\nexport function createParameterizedSelector<TState, TParam, TResult>(\n  factory: (param: TParam) => Selector<TState, TResult>,\n  maxSize: number = DEFAULT_MAX_CACHE_SIZE\n): (param: TParam) => Selector<TState, TResult> {\n  const selectorCache = new Map<TParam, Selector<TState, TResult>>();\n\n  return (param: TParam) => {\n    if (selectorCache.has(param)) {\n      // Move to end (most recently used) for LRU behavior\n      const selector = selectorCache.get(param)!;\n      selectorCache.delete(param);\n      selectorCache.set(param, selector);\n      return selector;\n    }\n\n    // Evict oldest entries if at capacity\n    if (selectorCache.size >= maxSize) {\n      const firstKey = selectorCache.keys().next().value;\n      if (firstKey !== undefined) {\n        selectorCache.delete(firstKey);\n      }\n    }\n\n    const selector = factory(param);\n    selectorCache.set(param, selector);\n    return selector;\n  };\n}\n\n/**\n * Create a parameterized selector with cache limit\n *\n * Same as createParameterizedSelector but with LRU cache eviction.\n * Use this when parameters can have many unique values.\n *\n * @example\n * ```typescript\n * const selectUserById = createBoundedParameterizedSelector(\n *   (id: string) => (state) => state.users[id],\n *   100 // Cache up to 100 selectors\n * );\n * ```\n */\nexport function createBoundedParameterizedSelector<TState, TParam, TResult>(\n  factory: (param: TParam) => Selector<TState, TResult>,\n  maxSize: number = 50\n): (param: TParam) => Selector<TState, TResult> {\n  const selectorCache = new Map<TParam, Selector<TState, TResult>>();\n\n  return (param: TParam) => {\n    if (selectorCache.has(param)) {\n      // Move to end (most recently used)\n      const selector = selectorCache.get(param)!;\n      selectorCache.delete(param);\n      selectorCache.set(param, selector);\n      return selector;\n    }\n\n    // Evict oldest if at capacity\n    if (selectorCache.size >= maxSize) {\n      const firstKey = selectorCache.keys().next().value;\n      if (firstKey !== undefined) {\n        selectorCache.delete(firstKey);\n      }\n    }\n\n    const selector = factory(param);\n    selectorCache.set(param, selector);\n    return selector;\n  };\n}\n\n// ============================================================================\n// Selector Combinators\n// ============================================================================\n\n/**\n * Combine multiple selectors into one with shallow equality\n *\n * Use this to select multiple values from state in a single hook call\n * while maintaining referential stability.\n *\n * @example\n * ```typescript\n * const selectUserProfile = combineSelectors({\n *   name: selectUserName,\n *   email: selectUserEmail,\n *   avatar: selectUserAvatar,\n * });\n *\n * // In component:\n * const { name, email, avatar } = useStore(selectUserProfile, shallow);\n * ```\n */\nexport function combineSelectors<TState, TResult extends Record<string, unknown>>(selectors: {\n  [K in keyof TResult]: Selector<TState, TResult[K]>;\n}): Selector<TState, TResult> {\n  const keys = Object.keys(selectors) as (keyof TResult)[];\n  let cache: TResult | null = null;\n\n  return (state: TState): TResult => {\n    const result = {} as TResult;\n    let hasChanged = cache === null;\n\n    for (const key of keys) {\n      result[key] = selectors[key](state);\n      if (!hasChanged && cache !== null && !Object.is(result[key], cache[key])) {\n        hasChanged = true;\n      }\n    }\n\n    if (!hasChanged && cache !== null) {\n      return cache;\n    }\n\n    cache = result;\n    return result;\n  };\n}\n\n/**\n * Create a selector that picks specific keys from state\n *\n * @example\n * ```typescript\n * const selectUIState = pickSelector<StoreState>()('sidebarOpen', 'activeModal');\n * ```\n */\nexport function pickSelector<TState>() {\n  return <K extends keyof TState>(...keys: K[]): Selector<TState, Pick<TState, K>> => {\n    return createObjectSelector((state) => {\n      const result = {} as Pick<TState, K>;\n      for (const key of keys) {\n        result[key] = state[key];\n      }\n      return result;\n    });\n  };\n}\n\n/**\n * Create a selector that omits specific keys from state\n *\n * @example\n * ```typescript\n * const selectPublicState = omitSelector<StoreState>()('_internal', 'password');\n * ```\n */\nexport function omitSelector<TState extends object>() {\n  return <K extends keyof TState>(...keys: K[]): Selector<TState, Omit<TState, K>> => {\n    return createObjectSelector((state) => {\n      const result = { ...state };\n      for (const key of keys) {\n        delete result[key];\n      }\n      return result as Omit<TState, K>;\n    });\n  };\n}\n\n// ============================================================================\n// Utility Selectors\n// ============================================================================\n\n/**\n * Selector utility functions\n */\nexport const selectorUtils = {\n  /**\n   * Identity selector (passthrough)\n   */\n  identity: <T>(value: T): T => value,\n\n  /**\n   * Create a selector with default value\n   *\n   * @example\n   * ```typescript\n   * const selectUserOrDefault = selectorUtils.withDefault(\n   *   (state) => state.user,\n   *   { name: 'Guest', email: '' }\n   * );\n   * ```\n   */\n  withDefault: <TState, T>(\n    selector: Selector<TState, T | undefined | null>,\n    defaultValue: T\n  ): Selector<TState, T> => {\n    return (state) => selector(state) ?? defaultValue;\n  },\n\n  /**\n   * Create a boolean selector from a predicate\n   *\n   * @example\n   * ```typescript\n   * const selectHasItems = selectorUtils.predicate(\n   *   (state) => state.items,\n   *   (items) => items.length > 0\n   * );\n   * ```\n   */\n  predicate: <TState, T>(\n    selector: Selector<TState, T>,\n    predicate: (value: T) => boolean\n  ): Selector<TState, boolean> => {\n    return (state) => predicate(selector(state));\n  },\n\n  /**\n   * Create a mapped selector\n   *\n   * @example\n   * ```typescript\n   * const selectItemIds = selectorUtils.map(\n   *   (state) => state.items,\n   *   (items) => items.map(item => item.id)\n   * );\n   * ```\n   */\n  map: <TState, TInput, TOutput>(\n    selector: Selector<TState, TInput>,\n    transform: (value: TInput) => TOutput\n  ): Selector<TState, TOutput> => {\n    let lastInput: TInput | undefined;\n    let lastOutput: TOutput | undefined;\n\n    return (state) => {\n      const input = selector(state);\n      if (Object.is(input, lastInput) && lastOutput !== undefined) {\n        return lastOutput;\n      }\n      lastInput = input;\n      lastOutput = transform(input);\n      return lastOutput;\n    };\n  },\n\n  /**\n   * Create a filtered selector\n   *\n   * @example\n   * ```typescript\n   * const selectActiveItems = selectorUtils.filter(\n   *   (state) => state.items,\n   *   (item) => item.active\n   * );\n   * ```\n   */\n  filter: <TState, T>(\n    selector: Selector<TState, T[]>,\n    predicate: (item: T) => boolean\n  ): Selector<TState, T[]> => {\n    return createArraySelector((state) => selector(state).filter(predicate));\n  },\n\n  /**\n   * Create a find selector\n   *\n   * @example\n   * ```typescript\n   * const selectFirstActiveItem = selectorUtils.find(\n   *   (state) => state.items,\n   *   (item) => item.active\n   * );\n   * ```\n   */\n  find: <TState, T>(\n    selector: Selector<TState, T[]>,\n    predicate: (item: T) => boolean\n  ): Selector<TState, T | undefined> => {\n    let lastArray: T[] | undefined;\n    let lastResult: T | undefined;\n\n    return (state) => {\n      const array = selector(state);\n      if (array === lastArray) {\n        return lastResult;\n      }\n      lastArray = array;\n      lastResult = array.find(predicate);\n      return lastResult;\n    };\n  },\n};\n"],"names":["createSelector","dependencySelectors","combiner","equalityFn","cache","state","deps","selector","dep","result","createObjectSelector","shallow","createArraySelector","item","i","DEFAULT_MAX_CACHE_SIZE","createParameterizedSelector","factory","maxSize","selectorCache","param","firstKey","createBoundedParameterizedSelector","combineSelectors","selectors","keys","hasChanged","key","pickSelector","omitSelector","selectorUtils","value","defaultValue","predicate","transform","lastInput","lastOutput","input","lastArray","lastResult","array"],"mappings":"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"}