-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
123 lines (110 loc) · 4.17 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { StoreonStore, createStoreon, StoreonDispatch } from 'storeon';
import DispatchableEvents = createStoreon.DispatchableEvents;
const p = Object.getPrototypeOf({});
const isChangeEventHandler = <State, Event extends PropertyKey>(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
event: Event, handler: createStoreon.EventHandler<State, any, any>): handler is createStoreon.EventHandler<State, any, '@changed'> => {
return event === '@changed';
}
const isPromise = <T>(x: T | Promise<any>): x is Promise<any> => {
return typeof (x as Promise<any>).then === 'function'
}
const isObject = (x: any): boolean => {
return x && Object.getPrototypeOf(x) === p;
}
const isSymbol = (x: any): boolean => {
return x && typeof x === 'symbol';
}
const diff = (oldState: any = {}, newState: any = {}): any => {
return [...Object.keys(oldState), ...Object.keys(newState)].reduce((r, key) => {
if (oldState[key] !== newState[key]) {
r[key] = newState[key]
}
return r;
}, {} as any)
}
/**
* Creates instance of storeon feature sub store.
*
* @example
* import createStore from "storeon";
* import { substore } from "storeon-substore";
* // create store
* const store = createStore([(store) => {
* store('@init', () => ({
* feature: {
* flag: true,
* },
* }));
* }]);
* const featureStore = createSubstore(store, 'feature');
* featureStore.on('toggleFeatureBooleanFlag', (state) => ({
* flag: state ? !state.flag : true,
* }));
* featureStore.dispatch('toggleFeatureBooleanFlag');
* featureStore.get(); // returns { flag: false }
*
*/
export function createSubstore<State, K extends keyof NonNullable<State>, Events>(
store: StoreonStore<State, Events>,
key: K,
scopeEvents?: boolean): StoreonStore<NonNullable<State>[K], Events> {
const k = key as unknown as keyof State;
const scopeEventsMap: Record<PropertyKey, PropertyKey> = {
'@init': '@init',
'@changed': '@changed',
'@dispatch': '@dispatch'
};
const getEvent = (event: any): any => {
if (!scopeEvents) {
return event;
}
if (!scopeEventsMap[event]) {
scopeEventsMap[event] = isSymbol(event)
? Symbol(`[@${key}] ${event.description || event.toString()}`)
: scopeEventsMap[event] = `[@${key}] ${event} `;
}
return scopeEventsMap[event];
}
const get = () => {
const s = store.get();
return s ? (s as Readonly<NonNullable<State>>)[key] : undefined;
}
const newStore: StoreonStore<NonNullable<State>[K], Events> = {
on: (event, handler) => {
if (isChangeEventHandler(event, handler)) {
let localState = get();
const unregister = store.on('@changed', (state) => {
const newState = state ? (state as any)[key] : undefined;
if (localState !== newState) {
const changes = isObject(newState) ? diff(localState, newState): {};
localState = newState;
handler(newState, changes, newStore);
}
});
return () => {
localState = undefined;
unregister();
}
}
return store.on(getEvent(event), (state, data) => {
const r = handler(state ? (state as any)[key] : undefined, data as any, newStore);
if (typeof r !== 'undefined' && r !== null) {
if (isPromise(r)) return r;
if (!state || r !== state[k]) {
return ({
[key]: isObject(r)
? { ...(state ? state[k] : undefined), ...r }
: r,
}) as Partial<State>;
}
}
});
},
get,
dispatch: ((event, ...data): void => {
store.dispatch(getEvent(event), ...(data as any || []));
}) as StoreonDispatch<Events & DispatchableEvents<NonNullable<State>[K]>>
};
return newStore;
}