import { DataResultKeyed, FiltersMap, syncWithSharedQuery, TaskState, } from '@wix/bex-core'; import { NestedCollectionFetchAllState } from './NestedCollectionFetchAllState'; import { action, makeObservable } from 'mobx'; import { NestedCollectionLevelDescriptor } from './NestedCollectionLevelDescriptor'; import { createNestedOptimisticActions } from './createNestedOptimisticActions'; export interface NestedCollectionFetchAllCollectionStateParas< T, F extends FiltersMap, > { readonly fetchAllState: NestedCollectionFetchAllState; readonly levelDescriptor: NestedCollectionLevelDescriptor; } export class NestedCollectionFetchAllCollectionState { readonly collection; readonly levelDescriptor; readonly parentFilter; readonly fetchAllState; readonly initTask = new TaskState(); readonly originalLimit; constructor(params: NestedCollectionFetchAllCollectionStateParas) { const { levelDescriptor } = params; this.levelDescriptor = levelDescriptor; this.fetchAllState = params.fetchAllState; const { collection, parentFilter } = levelDescriptor.createCollection(); createNestedOptimisticActions( params.fetchAllState.nestedCollectionState.container, collection, { levelDescriptor: this.levelDescriptor, parentFilter, }, ); this.collection = collection; this.parentFilter = parentFilter; const { fetchAllState: { query: sharedQuery, options: { fetchAllThreshold }, }, } = this; const { query } = collection; syncWithSharedQuery(query, sharedQuery); this.originalLimit = query.pagination.limit; query.pagination.limit = levelDescriptor.fetchAllThreshold ?? fetchAllThreshold; makeObservable(this, { init: action, _populateCache: action, _emitFetchAllAbortSignalIfExceedsThreshold: action, }); } _setWithParentFilterQueryData(itemsByParentId: Map) { const { collection, parentFilter } = this; const { queryClient, _queryKey, query } = collection; const [queryName, computedQuery] = _queryKey(query.asComputed); for (const [parentId, subItems] of Array.from(itemsByParentId.entries())) { queryClient.setQueryData>( [ queryName, { ...computedQuery, filtersKey: { ...computedQuery.filtersKey, ...(parentFilter && { [parentFilter.name]: [parentId], }), }, limit: this.originalLimit, }, ], { items: subItems, total: subItems.length, cursor: null, available: null, }, ); } } _setSectionQueryData() { const { collection } = this; const { queryClient, _queryKey, query, result: { items }, } = collection; const [queryName, computedQuery] = _queryKey(query.asComputed); queryClient.setQueryData>( [ queryName, { ...computedQuery, limit: this.originalLimit, }, ], { items, total: items.length, cursor: null, available: null, }, ); } _populateCacheWithEmptyResult(parents: { id: string }[]) { this._setWithParentFilterQueryData( new Map(parents.map(({ id }) => [id, []])), ); } _populateCache( next: NestedCollectionFetchAllCollectionState | null, ) { const { fetchAllState, collection, levelDescriptor: { parentKey }, } = this; const { result: { items, keyedItems }, } = collection; if (fetchAllState._otherLevelSameCollectionExists(this)) { this._populateCacheWithEmptyResult(keyedItems); } const itemsByParentId = items.reduce((groups, item) => { const parentId = String(parentKey(item) ?? null); const group = groups.get(parentId) ?? []; if (!groups.has(parentId)) { groups.set(parentId, group); } group.push(item); return groups; }, new Map([])); this._setWithParentFilterQueryData(itemsByParentId); this._setSectionQueryData(); // also include current collection items treated as parents, // so if the next level doesn't have any items that their parent id is one of the current collection items, // there's still a cache entry for them next?._populateCacheWithEmptyResult(keyedItems); } _exceedsTotalThreshold() { const { fetchAllState: { options: { fetchAllThreshold }, total, }, } = this; return total > fetchAllThreshold; } _exceedsLevelThreshold() { const { levelDescriptor: { fetchAllThreshold }, collection, } = this; return ( fetchAllThreshold != null && collection.result.total > fetchAllThreshold ); } _exceedsThreshold() { return this._exceedsTotalThreshold() || this._exceedsLevelThreshold(); } _emitFetchAllAbortSignalIfExceedsThreshold() { const { fetchAllState } = this; if (fetchAllState.isFetchAllAborted) { return; } if (this._exceedsThreshold()) { fetchAllState.events.emit('fetchAllAbortSignal'); fetchAllState.isFetchAllAborted = true; } } init() { const { initTask, collection, fetchAllState } = this; const disposers = [collection.init({ skipFilterListeners: true })]; initTask.runOnce(async () => { collection.initTask.runOnce(); const collectionPromise = new Promise(async (resolve, reject) => { try { await collection.initTask.status.promise; this._emitFetchAllAbortSignalIfExceedsThreshold(); resolve(); } catch (e) { reject(e); } }); await Promise.race([fetchAllState.fetchedAllPromise, collectionPromise]); }); return () => { disposers.forEach((d) => d()); }; } }