@oimdb/redux-adapter
Version:
Redux adapter for OIMDB - Create Redux reducers from OIMDB collections and indexes
336 lines (322 loc) • 15.4 kB
TypeScript
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 };