{"version":3,"file":"stream-to-query-cache.mjs","sources":["../../../src/lib/realtime/stream-to-query-cache.ts"],"sourcesContent":["/**\n * @file Stream to Query Cache\n * @description Maps incoming events into React Query cache updates\n */\n\nimport { type QueryClient, type QueryKey } from '@tanstack/react-query';\n\n/**\n * Stream event types\n */\nexport type StreamEventType = 'create' | 'update' | 'delete' | 'invalidate' | 'patch';\n\n/**\n * Stream event payload\n */\nexport interface StreamEvent<T = unknown> {\n  type: StreamEventType;\n  entity: string;\n  id?: string;\n  data?: T;\n  timestamp: string;\n  queryKeys?: QueryKey[];\n}\n\n/**\n * Cache update strategy\n */\nexport interface CacheUpdateStrategy<T = unknown> {\n  entity: string;\n  getQueryKey: (id?: string) => QueryKey;\n  getListQueryKey: () => QueryKey;\n  merge?: (existing: T | undefined, incoming: Partial<T>) => T;\n  shouldInvalidate?: (event: StreamEvent) => boolean;\n}\n\n/**\n * Cache updater configuration\n */\nexport interface StreamCacheConfig {\n  queryClient: QueryClient;\n  strategies: CacheUpdateStrategy[];\n  onEvent?: (event: StreamEvent) => void;\n  onError?: (error: Error, event: StreamEvent) => void;\n}\n\n/**\n * Stream to query cache updater\n */\nexport class StreamQueryCacheUpdater {\n  private queryClient: QueryClient;\n  private strategies: Map<string, CacheUpdateStrategy>;\n  private readonly onEvent?: (event: StreamEvent) => void;\n  private readonly onError?: (error: Error, event: StreamEvent) => void;\n  \n  constructor(config: StreamCacheConfig) {\n    this.queryClient = config.queryClient;\n    this.strategies = new Map(\n      config.strategies.map((s) => [s.entity, s])\n    );\n    this.onEvent = config.onEvent;\n    this.onError = config.onError;\n  }\n  \n  /**\n   * Process incoming stream event\n   */\n  processEvent(event: StreamEvent): void {\n    try {\n      this.onEvent?.(event);\n      \n      const strategy = this.strategies.get(event.entity);\n      \n      if (!strategy) {\n        // No strategy found, invalidate by provided keys\n        if (event.queryKeys !== undefined && Array.isArray(event.queryKeys) && event.queryKeys.length > 0) {\n          event.queryKeys.forEach((key) => {\n            void this.queryClient.invalidateQueries({ queryKey: key });\n          });\n        }\n        return;\n      }\n      \n      switch (event.type) {\n        case 'create':\n          this.handleCreate(event, strategy);\n          break;\n        case 'update':\n          this.handleUpdate(event, strategy);\n          break;\n        case 'delete':\n          this.handleDelete(event, strategy);\n          break;\n        case 'patch':\n          this.handlePatch(event as StreamEvent<Partial<unknown>>, strategy);\n          break;\n        case 'invalidate':\n          this.handleInvalidate(event, strategy);\n          break;\n      }\n    } catch (error) {\n      console.error('[StreamCache] Error processing event:', error);\n      this.onError?.(error as Error, event);\n    }\n  }\n  \n/**\n   * Add or update a strategy\n   */\n  addStrategy<T>(strategy: CacheUpdateStrategy<T>): void {\n    this.strategies.set(strategy.entity, strategy as CacheUpdateStrategy);\n  }\n  \n  /**\n   * Remove a strategy\n   */\n  removeStrategy(entity: string): void {\n    this.strategies.delete(entity);\n  }\n\n    /**\n   * Handle create event\n   */\n  private handleCreate<T>(\n    event: StreamEvent<T>,\n    strategy: CacheUpdateStrategy<T>\n  ): void {\n    // Invalidate list queries to refetch\n    void this.queryClient.invalidateQueries({\n      queryKey: strategy.getListQueryKey(),\n    });\n\n\n    // Optionally pre-populate detail query\n    if (event.id != null && event.id.length > 0 && event.data != null) {\n      this.queryClient.setQueryData(\n        strategy.getQueryKey(event.id),\n        event.data\n      );\n    }\n  }\n  /**\n   * Handle update event\n   */\n  private handleUpdate<T>(\n    event: StreamEvent<T>,\n    strategy: CacheUpdateStrategy<T>\n  ): void {\n    if (event.id === undefined || event.id === null) return;\n\n\n    // Update detail query with new data\n    if (event.data != null) {\n      this.queryClient.setQueryData(\n        strategy.getQueryKey(event.id),\n        event.data\n      );\n    }\n\n    // Invalidate list queries\n    void this.queryClient.invalidateQueries({\n      queryKey: strategy.getListQueryKey(),\n    });\n  }\n/**\n   * Handle delete event\n   */\n  private handleDelete<T>(\n    event: StreamEvent<T>,\n    strategy: CacheUpdateStrategy<T>\n  ): void {\n    if (event.id === undefined || event.id === null) return;    // Remove from cache\n    void this.queryClient.removeQueries({\n      queryKey: strategy.getQueryKey(event.id),\n    });\n\n    // Invalidate list queries\n    void this.queryClient.invalidateQueries({\n      queryKey: strategy.getListQueryKey(),\n    });\n  }\n/**\n   * Handle patch event (partial update)\n   */\n  private handlePatch<T>(\n    event: StreamEvent<Partial<T>>,\n    strategy: CacheUpdateStrategy<T>\n  ): void {\n    if (event.id === undefined || event.id === null || event.data === undefined) return;\n\n    const eventData = event.data;\n    // Merge with existing data\n    this.queryClient.setQueryData<T>(\n      strategy.getQueryKey(event.id),\n      (old) => {\n        if (old === undefined || old === null) return undefined as T;\n\n        if (strategy.merge) {\n          return strategy.merge(old, eventData);\n        }\n\n        return { ...old, ...eventData } as T;\n      }\n    );\n  }\n  \n/**\n   * Handle invalidate event\n   */\n  private handleInvalidate<T>(\n    event: StreamEvent<T>,\n    strategy: CacheUpdateStrategy<T>\n  ): void {\n    if (strategy.shouldInvalidate !== undefined && !strategy.shouldInvalidate(event)) {\n      return;\n    }\n\n    if (event.id !== undefined && event.id !== null) {\n      void this.queryClient.invalidateQueries({\n        queryKey: strategy.getQueryKey(event.id),\n      });\n    }\n\n    void this.queryClient.invalidateQueries({\n      queryKey: strategy.getListQueryKey(),\n    });\n  }\n}\n\n/**\n * Create stream cache updater helper\n */\nexport function createStreamCacheUpdater(\n  queryClient: QueryClient,\n  strategies: CacheUpdateStrategy[]\n): StreamQueryCacheUpdater {\n  return new StreamQueryCacheUpdater({\n    queryClient,\n    strategies,\n  });\n}\n\n/**\n * Create a simple cache update strategy\n */\nexport function createCacheStrategy<T>(\n  entity: string,\n  options: {\n    detailKeyPrefix?: string;\n    listKeyPrefix?: string;\n  } = {}\n): CacheUpdateStrategy<T> {\n  const detailPrefix = options.detailKeyPrefix ?? entity;\n  const listPrefix = options.listKeyPrefix ?? `${entity}s`;\n  \n  return {\n    entity,\n    getQueryKey: (id) => [detailPrefix, id],\n    getListQueryKey: () => [listPrefix],\n  };\n}\n"],"names":["StreamQueryCacheUpdater","config","s","event","strategy","key","error","entity","eventData","old","createStreamCacheUpdater","queryClient","strategies","createCacheStrategy","options","detailPrefix","listPrefix","id"],"mappings":"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"}