Created
April 8, 2019 20:00
-
-
Save MikeRyanDev/6cabbf3adee62fe0d052be5f4af5950f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Observable, Observer } from 'rxjs'; | |
import { createSelector, MemoizedSelector } from '@ngrx/store'; | |
import { createEntityAdapter } from '@ngrx/entity'; | |
export interface View<T> extends Observable<T> { | |
isView: true; | |
release(): void; | |
} | |
export interface Value<T> extends View<T> { | |
update(updater: (previousValue: T) => T): void; | |
update(newValue: T): void; | |
} | |
type Dict<T, Key extends string | number> = Key extends number | |
? { [key: number]: T } | |
: { [key: string]: T }; | |
type Update<T, Key extends string | number> = { id: Key; changes: Partial<T> }; | |
export interface Collection<T, Key extends string | number> extends View<T[]> { | |
addOne(entity: T): void; | |
addMany(entities: T[]): void; | |
addAll(entities: T[]): void; | |
updateOne(update: Update<T, Key>): void; | |
updateMany(updates: Update<T, Key>[]): void; | |
upsertOne(entity: T): void; | |
upsertMany(entities: T[]): void; | |
removeOne(id: Key): void; | |
removeMany(ids: Key[]): void; | |
removeAll(): void; | |
viewIds: View<Key[]>; | |
viewDict: View<Dict<T, Key>>; | |
viewTotal: View<number>; | |
} | |
const state: { [ref: number]: any } = {}; | |
const callbacks = new Set<() => void>(); | |
const selectorMap = new WeakMap<View<any>, (state: any) => any>(); | |
let nextRef: number = 0; | |
let locked: boolean = false; | |
function emitStateChange() { | |
if (locked) return; | |
for (const callback of callbacks) { | |
callback(); | |
} | |
} | |
function lock() { | |
locked = true; | |
} | |
function unlock() { | |
locked = false; | |
} | |
function observeState<T>(selector: (state: any) => T): Observable<T> { | |
return new Observable((observer: Observer<T>) => { | |
let lastValue = selector(state); | |
observer.next(lastValue); | |
function onStateChange() { | |
const nextValue = selector(state); | |
if (nextValue === lastValue) return; | |
observer.next(nextValue); | |
lastValue = nextValue; | |
} | |
callbacks.add(onStateChange); | |
return () => callbacks.delete(onStateChange); | |
}); | |
} | |
function makeView<T>(selector: MemoizedSelector<any, T>): View<T> { | |
const source$ = observeState(selector); | |
function release() { | |
selector.release(); | |
} | |
const view$: View<any> = Object.assign(source$, { release, isView: true as true }); | |
selectorMap.set(view$, selector); | |
return view$; | |
} | |
export function updateMany(update: () => void): void { | |
if (locked) return update(); | |
lock(); | |
update(); | |
unlock(); | |
emitStateChange(); | |
} | |
export function value<T>(initialValue: T): Value<T> { | |
let ref = ++nextRef; | |
let released: boolean = false; | |
state[ref] = initialValue; | |
const selector = (state: any) => { | |
if (released) throw new Error('Cannot access a released value'); | |
return state[ref]; | |
}; | |
const source$ = observeState(selector); | |
function release() { | |
delete state[ref]; | |
released = true; | |
} | |
function update(value: T): void; | |
function update(updater: (oldValue: T) => T): void; | |
function update(valueOrUpdater: T | ((oldValue: T) => T)) { | |
state[ref] = | |
valueOrUpdater instanceof Function | |
? valueOrUpdater(selector(state)) | |
: valueOrUpdater; | |
emitStateChange(); | |
} | |
const value$ = Object.assign(source$, { release, update, isView: true as true }); | |
selectorMap.set(value$, selector); | |
return value$; | |
} | |
export function view<T1, R>(t1: View<T1>, projector: (t1: T1) => R): View<R>; | |
export function view<T1, T2, R>( | |
t1: View<T1>, | |
t2: View<T2>, | |
projector: (t1: T1, t2: T2) => R, | |
): View<R>; | |
export function view<T1, T2, T3, R>( | |
t1: View<T1>, | |
t2: View<T2>, | |
t3: View<T3>, | |
projector: (t1: T1, t2: T2, t3: T3) => R, | |
): View<R>; | |
export function view<T1, T2, T3, T4, R>( | |
t1: View<T1>, | |
t2: View<T2>, | |
t3: View<T3>, | |
t4: View<T4>, | |
projector: (t1: T1, t2: T2, t3: T3, t4: T4) => R, | |
): View<R>; | |
export function view<T1, T2, T3, T4, T5, R>( | |
t1: View<T1>, | |
t2: View<T2>, | |
t3: View<T3>, | |
t4: View<T4>, | |
t5: View<T5>, | |
projector: (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5) => R, | |
): View<R>; | |
export function view<T1, T2, T3, T4, T5, T6, R>( | |
t1: View<T1>, | |
t2: View<T2>, | |
t3: View<T3>, | |
t4: View<T4>, | |
t5: View<T5>, | |
t6: View<T6>, | |
projector: (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6) => R, | |
): View<R>; | |
export function view<T1, T2, T3, T4, T5, T6, T7, R>( | |
t1: View<T1>, | |
t2: View<T2>, | |
t3: View<T3>, | |
t4: View<T4>, | |
t5: View<T5>, | |
t6: View<T6>, | |
t7: View<T7>, | |
projector: (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7) => R, | |
): View<R>; | |
export function view<T1, T2, T3, T4, T5, T6, T7, T8, R>( | |
t1: View<T1>, | |
t2: View<T2>, | |
t3: View<T3>, | |
t4: View<T4>, | |
t5: View<T5>, | |
t6: View<T6>, | |
t7: View<T7>, | |
t8: View<T8>, | |
projector: (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7, t8: T8) => R, | |
): View<R>; | |
export function view(...args: any[]): View<any> { | |
const views: View<any>[] = args.slice(0, args.length - 1); | |
const projector: (...args: any[]) => any = args[args.length - 1]; | |
const viewSelectors = views.map(view => selectorMap.get(view)!); | |
const selector: MemoizedSelector<any, any> = (createSelector as any)( | |
...viewSelectors, | |
projector, | |
); | |
return makeView(selector); | |
} | |
export function collection<T extends { id: string }>( | |
initialValues: T[], | |
options?: { sortComparer?: (a: T, b: T) => number }, | |
): Collection<T, string>; | |
export function collection<T extends { id: number }>( | |
initialValues: T[], | |
options?: { sortComparer?: (a: T, b: T) => number }, | |
): Collection<T, number>; | |
export function collection<T, V extends string | number>( | |
initialValues: T[], | |
options?: { selectId: (entity: T) => V; sortComparer?: (a: T, b: T) => number }, | |
): Collection<T, V>; | |
export function collection<T, V extends string | number>( | |
initialValues: T[], | |
{ | |
selectId = (item: any) => item.id, | |
sortComparer, | |
}: { selectId?: (entity: T) => V; sortComparer?: (a: T, b: T) => number } = {}, | |
): Collection<T, V> { | |
const a = createEntityAdapter<T>({ selectId: selectId as any, sortComparer }); | |
const initialValue = a.addAll(initialValues, a.getInitialState()); | |
const value$ = value(initialValue); | |
const u = value$.update; | |
const addOne = (entity: T) => u(state => a.addOne(entity, state)); | |
const addMany = (entities: T[]) => u(state => a.addMany(entities, state)); | |
const addAll = (entities: T[]) => u(state => a.addAll(entities, state)); | |
const updateOne = (update: Update<T, V>) => | |
u(state => a.updateOne(update as any, state)); | |
const updateMany = (updates: Update<T, V>[]) => | |
u(state => a.updateMany(updates as any, state)); | |
const upsertOne = (entity: T) => u(state => a.upsertOne(entity, state)); | |
const upsertMany = (entities: T[]) => u(state => a.upsertMany(entities, state)); | |
const removeOne = (id: V) => u(state => a.removeOne(id as any, state)); | |
const removeMany = (ids: V[]) => u(state => a.removeMany(ids as any, state)); | |
const removeAll = () => u(state => a.removeAll(state)); | |
const rootSelector = selectorMap.get(value$)!; | |
const { selectAll, selectEntities, selectIds, selectTotal } = a.getSelectors( | |
rootSelector, | |
); | |
const viewIds = makeView<V[]>(selectIds as any); | |
const viewDict = makeView<Dict<T, V>>(selectEntities as any); | |
const viewTotal = makeView<number>(selectTotal as any); | |
const viewAll = makeView<T[]>(selectAll as any); | |
function release() { | |
viewIds.release(); | |
viewDict.release(); | |
viewTotal.release(); | |
viewAll.release(); | |
value$.release(); | |
} | |
return Object.assign(viewAll, { | |
addOne, | |
addMany, | |
addAll, | |
updateOne, | |
updateMany, | |
upsertOne, | |
upsertMany, | |
removeOne, | |
removeMany, | |
removeAll, | |
viewIds, | |
viewDict, | |
viewTotal, | |
release, | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment