import { ActionName, calculateActionName, runAction, QueryParams, QueryMatcher, DeterministicSortComparator, StateResolveFunctionInput, ChangeEvent } from 'event-reduce-js'; import type { RxQuery, MangoQuery, RxChangeEvent, StringKeys, RxDocumentData } from 'nxdb-old/src/types'; import { rxChangeEventToEventReduceChangeEvent } from 'nxdb-old/src/rx-change-event'; import { arrayFilterNotEmpty, clone, ensureNotFalsy, getFromMapOrCreate } from 'nxdb-old/src/plugins/utils'; import { getQueryMatcher, getSortComparator, normalizeMangoQuery } from 'nxdb-old/src/rx-query-helper'; export type EventReduceResultNeg = { runFullQueryAgain: true; }; export type EventReduceResultPos = { runFullQueryAgain: false; changed: boolean; newResults: RxDocumentType[]; }; export type EventReduceResult = EventReduceResultNeg | EventReduceResultPos; export function getSortFieldsOfQuery( primaryKey: StringKeys>, query: MangoQuery ): (string | StringKeys)[] { if (!query.sort || query.sort.length === 0) { return [primaryKey]; } else { return query.sort.map(part => Object.keys(part)[0]); } } export const RXQUERY_QUERY_PARAMS_CACHE: WeakMap> = new WeakMap(); export function getQueryParams( rxQuery: RxQuery ): QueryParams { return getFromMapOrCreate( RXQUERY_QUERY_PARAMS_CACHE, rxQuery, () => { const collection = rxQuery.collection; const normalizedMangoQuery = normalizeMangoQuery( collection.storageInstance.schema, clone(rxQuery.mangoQuery) ); const primaryKey = collection.schema.primaryPath; /** * Create a custom sort comparator * that uses the hooks to ensure * we send for example compressed documents to be sorted by compressed queries. */ const sortComparator = getSortComparator( collection.schema.jsonSchema, normalizedMangoQuery ); const useSortComparator: DeterministicSortComparator = (docA: RxDocType, docB: RxDocType) => { const sortComparatorData = { docA, docB, rxQuery }; return sortComparator(sortComparatorData.docA, sortComparatorData.docB); }; /** * Create a custom query matcher * that uses the hooks to ensure * we send for example compressed documents to match compressed queries. */ const queryMatcher = getQueryMatcher( collection.schema.jsonSchema, normalizedMangoQuery ); const useQueryMatcher: QueryMatcher> = (doc: RxDocumentData) => { const queryMatcherData = { doc, rxQuery }; return queryMatcher(queryMatcherData.doc); }; const ret: QueryParams = { primaryKey: rxQuery.collection.schema.primaryPath as any, skip: normalizedMangoQuery.skip, limit: normalizedMangoQuery.limit, sortFields: getSortFieldsOfQuery(primaryKey, normalizedMangoQuery) as string[], sortComparator: useSortComparator, queryMatcher: useQueryMatcher }; return ret; } ); } export function calculateNewResults( rxQuery: RxQuery, rxChangeEvents: RxChangeEvent[] ): EventReduceResult { if (!rxQuery.collection.database.eventReduce) { return { runFullQueryAgain: true }; } const queryParams = getQueryParams(rxQuery); const previousResults: RxDocumentType[] = ensureNotFalsy(rxQuery._result).docsData.slice(0); const previousResultsMap: Map = ensureNotFalsy(rxQuery._result).docsDataMap; let changed: boolean = false; const eventReduceEvents: ChangeEvent[] = rxChangeEvents .map(cE => rxChangeEventToEventReduceChangeEvent(cE)) .filter(arrayFilterNotEmpty); const foundNonOptimizeable = eventReduceEvents.find(eventReduceEvent => { const stateResolveFunctionInput: StateResolveFunctionInput = { queryParams, changeEvent: eventReduceEvent, previousResults, keyDocumentMap: previousResultsMap }; const actionName: ActionName = calculateActionName(stateResolveFunctionInput); if (actionName === 'runFullQueryAgain') { return true; } else if (actionName !== 'doNothing') { changed = true; runAction( actionName, queryParams, eventReduceEvent, previousResults, previousResultsMap ); return false; } }); if (foundNonOptimizeable) { return { runFullQueryAgain: true, }; } else { return { runFullQueryAgain: false, changed, newResults: previousResults }; } }