/** * TanStack Store Factory * * Main entry point for creating TanStack-style stores. */ import type { BaseRecord, TanStackStore, Collection, QueryCollectionOptions, SyncCollectionOptions, PGLiteCollectionOptions, } from './types' import { createQueryCollection, QueryCollection } from './query/index' import { createSyncCollection, SyncCollection } from './sync/index' import { createPGLiteCollection } from './pglite/index' /** * Options for creating a TanStack store */ export interface TanStackStoreOptions { /** Default stale time for query collections */ defaultStaleTime?: number /** Default sync URL for sync collections */ defaultSyncUrl?: string /** Maximum number of collections allowed */ maxCollections?: number } /** * Summary returned from dispose() */ export interface DisposeSummary { collectionsCleared: number timersCleared: number subscribersCleared: number } /** * Memory statistics */ export interface MemoryStats { collectionCount: number totalItems: number } /** * Determine if options are for a QueryCollection */ function isQueryOptions( options: QueryCollectionOptions | SyncCollectionOptions | PGLiteCollectionOptions ): options is QueryCollectionOptions { return 'queryFn' in options } /** * Determine if options are for a SyncCollection */ function isSyncOptions( options: QueryCollectionOptions | SyncCollectionOptions | PGLiteCollectionOptions ): options is SyncCollectionOptions { return 'syncUrl' in options } /** * Determine if options are for a PGLiteCollection */ function isPGLiteOptions( options: QueryCollectionOptions | SyncCollectionOptions | PGLiteCollectionOptions ): options is PGLiteCollectionOptions { return 'pglite' in options } /** * Main TanStack Store implementation */ class TanStackStoreImpl implements TanStackStore { private collections: Map> = new Map() private _options: TanStackStoreOptions constructor(options: TanStackStoreOptions = {}) { this._options = options } /** * Get memory usage statistics */ getMemoryStats(): MemoryStats { let totalItems = 0 for (const collection of this.collections.values()) { totalItems += collection.getAll().length } return { collectionCount: this.collections.size, totalItems, } } /** * Get memory growth statistics */ getMemoryGrowth(): { startBytes: number; currentBytes: number; growthRate: number } { const stats = this.getMemoryStats() const estimatedBytes = stats.totalItems * 200 return { startBytes: 0, currentBytes: estimatedBytes, growthRate: 0, } } /** * Weak register a collection (allows GC when not in use) */ weakRegisterCollection( options: QueryCollectionOptions | SyncCollectionOptions | PGLiteCollectionOptions ): Collection { return this.registerCollection(options) } /** * Get a collection by ID */ getCollection(id: string): Collection | undefined { return this.collections.get(id) as Collection | undefined } /** * Register a new collection */ registerCollection( options: QueryCollectionOptions | SyncCollectionOptions | PGLiteCollectionOptions ): Collection { if (this._options.maxCollections !== undefined && this.collections.size >= this._options.maxCollections) { throw new Error(`Cannot register collection: max collections limit (${this._options.maxCollections}) reached`) } let collection: Collection if (isQueryOptions(options)) { const queryOptions: QueryCollectionOptions = { ...options, } if (options.staleTime !== undefined) { queryOptions.staleTime = options.staleTime } else if (this._options.defaultStaleTime !== undefined) { queryOptions.staleTime = this._options.defaultStaleTime } collection = createQueryCollection(queryOptions) } else if (isSyncOptions(options)) { collection = createSyncCollection(options) } else if (isPGLiteOptions(options)) { collection = createPGLiteCollection(options) } else { throw new Error('Invalid collection options') } this.collections.set(options.id, collection as unknown as Collection) return collection } /** * Get all registered collection IDs */ getCollectionIds(): string[] { return Array.from(this.collections.keys()) } /** * Dispose of all resources */ async dispose(): Promise { for (const collection of this.collections.values()) { if (collection instanceof QueryCollection) { collection.stopAutoRefetch() collection.clear() } else if (collection instanceof SyncCollection) { collection.disconnect() collection.clear() } } this.collections.clear() } /** * Dispose of all resources with detailed summary * * @returns Summary of what was cleaned up (useful for debugging) */ async disposeWithSummary(): Promise { let timersCleared = 0 let subscribersCleared = 0 const collectionsCleared = this.collections.size for (const collection of this.collections.values()) { if (collection instanceof QueryCollection) { timersCleared += collection.getActiveTimerCount() subscribersCleared += collection.getSubscriberCount() collection.stopAutoRefetch() collection.clear() } else if (collection instanceof SyncCollection) { subscribersCleared += collection.getSubscriberCount() collection.disconnect() collection.clear() } } this.collections.clear() return { collectionsCleared, timersCleared, subscribersCleared, } } } /** * Create a new TanStack store */ export function createTanStackStore(options: TanStackStoreOptions = {}): TanStackStore { return new TanStackStoreImpl(options) }