UNPKG

@oimdb/redux-adapter

Version:

Redux adapter for OIMDB - Create Redux reducers from OIMDB collections and indexes

336 lines (322 loc) 15.4 kB
import { TOIMPk, OIMReactiveCollection, OIMReactiveIndexSetBased, OIMIndexSetBased, OIMReactiveIndexArrayBased, OIMIndexArrayBased, OIMEventQueue } from '@oimdb/core'; import { Reducer, Action, Store, Middleware } from 'redux'; /** * Action type for OIMDB Redux reducer updates */ declare enum EOIMDBReduxReducerActionType { UPDATE = "OIMDB_UPDATE" } /** * Mapper function for converting collection state to Redux state * @param collection - The reactive collection * @param updatedKeys - Set of primary keys that were updated * @param currentState - Current Redux state (undefined on first call) * @returns New Redux state */ type TOIMDBReduxCollectionMapper<TEntity extends object, TPk extends TOIMPk, TState> = (collection: OIMReactiveCollection<TEntity, TPk>, updatedKeys: Set<TPk>, currentState: TState | undefined) => TState; /** * Mapper function for converting index state to Redux state * @param index - The reactive index * @param updatedKeys - Set of index keys that were updated * @param currentState - Current Redux state (undefined on first call) * @returns New Redux state */ type TOIMDBReduxIndexMapper<TIndexKey extends TOIMPk, TPk extends TOIMPk, TState> = (index: OIMReactiveIndexSetBased<TIndexKey, TPk, OIMIndexSetBased<TIndexKey, TPk>> | OIMReactiveIndexArrayBased<TIndexKey, TPk, OIMIndexArrayBased<TIndexKey, TPk>>, updatedKeys: Set<TIndexKey>, currentState: TState | undefined) => TState; /** * Options for OIMDBReduxAdapter */ type TOIMDBReduxAdapterOptions = { /** * Default mapper for collections (used when no mapper is provided) */ defaultCollectionMapper?: TOIMDBReduxCollectionMapper<any, any, any>; /** * Default mapper for indexes (used when no mapper is provided) */ defaultIndexMapper?: TOIMDBReduxIndexMapper<any, any, any>; }; /** * Linked index configuration for automatic index updates */ type TOIMDBReduxLinkedIndex<TEntity extends object, TPk extends TOIMPk, TIndexKey extends TOIMPk> = { /** * Reactive index to update automatically */ index: OIMReactiveIndexSetBased<TIndexKey, TPk, OIMIndexSetBased<TIndexKey, TPk>> | OIMReactiveIndexArrayBased<TIndexKey, TPk, OIMIndexArrayBased<TIndexKey, TPk>>; /** * Field name in entity that contains an array of PKs * When this field changes (by reference), the index will be updated * The entity's PK will be used as the index key, and the array values as index values * Example: deck.cardIds -> index[deck.id] = cardIds */ fieldName: keyof TEntity; }; /** * Options for child reducer that can handle custom actions * and sync changes back to OIMDB */ type TOIMDBReduxCollectionReducerChildOptions<TEntity extends object, TPk extends TOIMPk, TState> = { /** * Child reducer that handles custom actions * Should return new state when action is handled, or return current state unchanged * Can be either Reducer<TState, Action> (RTK slice style) or Reducer<TState | undefined, Action> * If reducer doesn't accept undefined, factory will handle undefined state automatically */ reducer: Reducer<TState, Action> | Reducer<TState | undefined, Action>; /** * Function to extract entities from Redux state and sync them to OIMDB collection * Should directly update the collection using upsertMany/removeMany * If not provided, default implementation will be used for TOIMDBReduxDefaultCollectionState * @param prevState - Previous Redux state (undefined on first call) * @param nextState - New Redux state after child reducer processed action * @param collection - The OIMDB collection to sync with * @param getPk - Function to extract primary key from entity */ extractEntities?: (prevState: TState | undefined, nextState: TState, collection: OIMReactiveCollection<TEntity, TPk>, getPk: (entity: TEntity) => TPk) => void; /** * Optional function to extract primary key from entity * If not provided, will try to use 'id' property */ getPk?: (entity: TEntity) => TPk; /** * Linked indexes that will be automatically updated when entities change * When an entity's field (specified by fieldName) changes by reference, * the index will be updated: old key will be removed, new key will be added * * Note: The index type accepts any TIndexKey that extends TOIMPk. * TypeScript will infer the specific key type (e.g., string) from the actual index instance, * allowing indexes with specific key types to be used even when TPk is a union type. */ linkedIndexes?: Array<{ index: OIMReactiveIndexSetBased<TOIMPk, TPk, OIMIndexSetBased<TOIMPk, TPk>> | OIMReactiveIndexArrayBased<TOIMPk, TPk, OIMIndexArrayBased<TOIMPk, TPk>> | OIMReactiveIndexSetBased<string, TPk, OIMIndexSetBased<string, TPk>> | OIMReactiveIndexArrayBased<string, TPk, OIMIndexArrayBased<string, TPk>> | OIMReactiveIndexSetBased<number, TPk, OIMIndexSetBased<number, TPk>> | OIMReactiveIndexArrayBased<number, TPk, OIMIndexArrayBased<number, TPk>>; fieldName: keyof TEntity; }>; }; /** * Options for child reducer that can handle custom actions * and sync changes back to OIMDB index */ type TOIMDBReduxIndexReducerChildOptions<TIndexKey extends TOIMPk, TPk extends TOIMPk, TState> = { /** * Child reducer that handles custom actions * Should return new state when action is handled, or return current state unchanged * Can be either Reducer<TState, Action> (RTK slice style) or Reducer<TState | undefined, Action> * If reducer doesn't accept undefined, factory will handle undefined state automatically */ reducer: Reducer<TState, Action> | Reducer<TState | undefined, Action>; /** * Function to extract index state from Redux state and sync it to OIMDB index * Should directly update the index using addPks/removePks (for SetBased) or setPks (for ArrayBased) * If not provided, default implementation will be used for TOIMDBReduxDefaultIndexState * @param prevState - Previous Redux state (undefined on first call) * @param nextState - New Redux state after child reducer processed action * @param index - The OIMDB index to sync with */ extractIndexState?: (prevState: TState | undefined, nextState: TState, index: OIMReactiveIndexSetBased<TIndexKey, TPk, OIMIndexSetBased<TIndexKey, TPk>> | OIMReactiveIndexArrayBased<TIndexKey, TPk, OIMIndexArrayBased<TIndexKey, TPk>>) => void; }; /** * OIMDB Redux Update Action */ type OIMDBReduxUpdateAction = { type: EOIMDBReduxReducerActionType.UPDATE; }; /** * Adapter for integrating OIMDB with Redux. * Creates Redux reducers from OIMDB collections and indexes, and provides middleware * for automatic event queue flushing. */ declare class OIMDBReduxAdapter { private store?; private readonly queue; private readonly options; private queueFlushHandler?; private isFlushingSilently; private collectionReducers; private indexReducers; private readonly reducerFactory; constructor(queue: OIMEventQueue, options?: TOIMDBReduxAdapterOptions); /** * Set Redux store (can be called later when store is created) */ setStore(store: Store): void; /** * Flush the event queue without triggering OIMDB_UPDATE dispatch. * Useful for processing events that were triggered by Redux actions * (e.g., through child reducers) without causing unnecessary Redux updates. */ flushSilently(): void; /** * Create Redux middleware that automatically flushes the event queue * after every action. This ensures that when Redux updates OIMDB collections * through child reducers, all events are processed synchronously. * * @returns Redux middleware * * @example * ```typescript * import { createStore, applyMiddleware } from 'redux'; * import { OIMDBReduxAdapter } from '@oimdb/redux-adapter'; * * const adapter = new OIMDBReduxAdapter(queue); * const middleware = adapter.createMiddleware(); * * const store = createStore( * rootReducer, * applyMiddleware(middleware) * ); * * adapter.setStore(store); * ``` */ createMiddleware(): Middleware; /** * Create Redux reducer for a collection * @param collection - The reactive collection * @param child - Optional child reducer that handles custom actions and syncs changes back to OIMDB * @param mapper - Optional mapper for converting OIMDB state to Redux state */ createCollectionReducer<TEntity extends object, TPk extends TOIMPk, TState>(collection: OIMReactiveCollection<TEntity, TPk>, child?: TOIMDBReduxCollectionReducerChildOptions<TEntity, TPk, TState>, mapper?: TOIMDBReduxCollectionMapper<TEntity, TPk, TState>): Reducer<TState | undefined, Action>; /** * Create Redux reducer for an index * @param index - The reactive index * @param child - Optional child reducer that handles custom actions and syncs changes back to OIMDB * @param mapper - Optional mapper for converting OIMDB state to Redux state */ createIndexReducer<TIndexKey extends TOIMPk, TPk extends TOIMPk, TState>(index: OIMReactiveIndexSetBased<TIndexKey, TPk, OIMIndexSetBased<TIndexKey, TPk>> | OIMReactiveIndexArrayBased<TIndexKey, TPk, OIMIndexArrayBased<TIndexKey, TPk>>, child?: TOIMDBReduxIndexReducerChildOptions<TIndexKey, TPk, TState>, mapper?: TOIMDBReduxIndexMapper<TIndexKey, TPk, TState>): Reducer<TState | undefined, Action>; } /** * Default Redux state structure for collections (RTK Entity Adapter style) */ type TOIMDBReduxDefaultCollectionState<TEntity, TPk extends TOIMPk> = { entities: Record<TPk, TEntity>; ids: TPk[]; }; /** * Default Redux state structure for indexes */ type TOIMDBReduxDefaultIndexState<TIndexKey extends TOIMPk, TPk extends TOIMPk> = { entities: Record<TIndexKey, { id: TIndexKey; ids: TPk[]; }>; }; /** * Default mapper for collections (RTK Entity Adapter style) * Creates state with entities and ids arrays */ declare function defaultCollectionMapper<TEntity extends object, TPk extends TOIMPk>(collection: OIMReactiveCollection<TEntity, TPk>, updatedKeys: Set<TPk>, currentState?: TOIMDBReduxDefaultCollectionState<TEntity, TPk>): TOIMDBReduxDefaultCollectionState<TEntity, TPk>; /** * Default mapper for indexes * Creates state with entities containing id and ids arrays */ declare function defaultIndexMapper<TIndexKey extends TOIMPk, TPk extends TOIMPk>(index: OIMReactiveIndexSetBased<TIndexKey, TPk, OIMIndexSetBased<TIndexKey, TPk>> | OIMReactiveIndexArrayBased<TIndexKey, TPk, OIMIndexArrayBased<TIndexKey, TPk>>, updatedKeys: Set<TIndexKey>, currentState?: TOIMDBReduxDefaultIndexState<TIndexKey, TPk>): TOIMDBReduxDefaultIndexState<TIndexKey, TPk>; /** * Result of finding updated entities between two records */ type TOIMDBReduxUpdatedEntitiesResult<TPk extends TOIMPk> = { /** * Set of primary keys that were added (present in newEntities but not in oldEntities) */ added: Set<TPk>; /** * Set of primary keys that were updated (present in both but with different values) */ updated: Set<TPk>; /** * Set of primary keys that were removed (present in oldEntities but not in newEntities) */ removed: Set<TPk>; /** * Combined set of all changed keys (added + updated + removed) */ all: Set<TPk>; }; /** * Result of finding updated items in arrays */ type TOIMDBReduxUpdatedArrayResult<TPk extends TOIMPk> = { /** * Array of primary keys that were added (present in newArray but not in oldArray) */ added: TPk[]; /** * Array of primary keys that were updated (present in both but with different values) */ updated: TPk[]; /** * Array of primary keys that were removed (present in oldArray but not in newArray) */ removed: TPk[]; /** * Combined array of all changed keys (added + updated + removed) */ all: TPk[]; }; /** * Find differences between two entity records (dictionaries) * Compares entities by reference (shallow comparison) * * @param oldEntities - Previous state of entities as Record * @param newEntities - New state of entities as Record * @returns Object with sets of added, updated, removed, and all changed keys * * @example * ```typescript * const oldEntities = { '1': { id: '1', name: 'Alice' }, '2': { id: '2', name: 'Bob' } }; * const newEntities = { '1': { id: '1', name: 'Alice Updated' }, '3': { id: '3', name: 'Charlie' } }; * const result = findUpdatedInRecord(oldEntities, newEntities); * // result.added = Set(['3']) * // result.updated = Set(['1']) * // result.removed = Set(['2']) * // result.all = Set(['1', '2', '3']) * ``` */ declare function findUpdatedInRecord<TEntity extends object, TPk extends TOIMPk>(oldEntities: Record<TPk, TEntity>, newEntities: Record<TPk, TEntity>): TOIMDBReduxUpdatedEntitiesResult<TPk>; /** * Find differences between two arrays of primary keys * Compares arrays to find added, removed, and common items * * @param oldArray - Previous array of primary keys * @param newArray - New array of primary keys * @returns Object with arrays of added, updated (common), removed, and all changed keys * * @example * ```typescript * const oldArray = ['1', '2', '3']; * const newArray = ['1', '3', '4']; * const result = findUpdatedInArray(oldArray, newArray); * // result.added = ['4'] * // result.updated = ['1', '3'] (common items) * // result.removed = ['2'] * // result.all = ['1', '2', '3', '4'] * ``` */ declare function findUpdatedInArray<TPk extends TOIMPk>(oldArray: readonly TPk[], newArray: readonly TPk[]): TOIMDBReduxUpdatedArrayResult<TPk>; /** * Check if two arrays are equal by comparing their elements. * Uses strict equality (===) for comparison. * Order matters - arrays with same elements in different order are considered different. * * @param a - First array * @param b - Second array * @returns true if arrays have same length and all elements are equal, false otherwise * * @example * ```typescript * arraysEqual([1, 2, 3], [1, 2, 3]); // true * arraysEqual([1, 2, 3], [1, 2]); // false * arraysEqual([1, 2, 3], [1, 3, 2]); // false (order matters) * arraysEqual([], []); // true * ``` */ declare function arraysEqual<T>(a: readonly T[], b: readonly T[]): boolean; /** * Check if two arrays of primary keys are equal (same elements in same order). * Optimized version for TOIMPk arrays. * * @param a - First array of primary keys * @param b - Second array of primary keys * @returns true if arrays are equal, false otherwise */ declare function arraysEqualPk<TPk extends TOIMPk>(a: readonly TPk[], b: readonly TPk[]): boolean; export { EOIMDBReduxReducerActionType, OIMDBReduxAdapter, type OIMDBReduxUpdateAction, type TOIMDBReduxAdapterOptions, type TOIMDBReduxCollectionMapper, type TOIMDBReduxCollectionReducerChildOptions, type TOIMDBReduxDefaultCollectionState, type TOIMDBReduxDefaultIndexState, type TOIMDBReduxIndexMapper, type TOIMDBReduxIndexReducerChildOptions, type TOIMDBReduxLinkedIndex, type TOIMDBReduxUpdatedArrayResult, type TOIMDBReduxUpdatedEntitiesResult, arraysEqual, arraysEqualPk, defaultCollectionMapper, defaultIndexMapper, findUpdatedInArray, findUpdatedInRecord };