Skip to content

Commit dc341b2

Browse files
authored
Support for internal instance events (#116)
* support of internal events
1 parent ae1b028 commit dc341b2

File tree

4 files changed

+261
-61
lines changed

4 files changed

+261
-61
lines changed

packages/core/src/AsyncState.ts

Lines changed: 198 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export class AsyncState<T, E, R> implements StateInterface<T, E, R> {
2626

2727
state: State<T, E, R>;
2828
lastSuccess: SuccessState<T> | InitialState<T>;
29+
events?: InstanceEvents<T, E, R>;
2930

3031

3132
originalProducer: Producer<T, E, R> | undefined;
@@ -80,6 +81,27 @@ export class AsyncState<T, E, R> implements StateInterface<T, E, R> {
8081
this.lastSuccess = this.state;
8182

8283

84+
this.bindMethods();
85+
let instance = this;
86+
this.producer = producerWrapper.bind(null, {
87+
setProducerType: (type: ProducerType) => instance.producerType = type,
88+
setState: instance.setState,
89+
getState: instance.getState,
90+
instance: instance,
91+
setSuspender: (suspender: Promise<T>) => instance.suspender = suspender,
92+
replaceState: instance.replaceState.bind(instance),
93+
getProducer: () => instance.originalProducer,
94+
});
95+
96+
this._source = makeSource(this);
97+
98+
if (__DEV__) {
99+
devtools.emitCreation(this);
100+
}
101+
}
102+
103+
private bindMethods() {
104+
this.on = this.on.bind(this);
83105
this.abort = this.abort.bind(this);
84106
this.getState = this.getState.bind(this);
85107
this.setState = this.setState.bind(this);
@@ -97,17 +119,6 @@ export class AsyncState<T, E, R> implements StateInterface<T, E, R> {
97119
this.invalidateCache = this.invalidateCache.bind(this);
98120
this.replaceProducer = this.replaceProducer.bind(this);
99121

100-
let instance = this;
101-
this.producer = producerWrapper.bind(null, {
102-
setProducerType: (type: ProducerType) => instance.producerType = type,
103-
setState: instance.setState,
104-
getState: instance.getState,
105-
instance: instance,
106-
setSuspender: (suspender: Promise<T>) => instance.suspender = suspender,
107-
replaceState: instance.replaceState.bind(instance),
108-
getProducer: () => instance.originalProducer,
109-
});
110-
111122
this._source = makeSource(this);
112123

113124
if (__DEV__) {
@@ -128,12 +139,45 @@ export class AsyncState<T, E, R> implements StateInterface<T, E, R> {
128139
return this.config;
129140
}
130141

142+
143+
on(
144+
eventType: InstanceChangeEvent,
145+
eventHandler: InstanceChangeEventHandlerType<T, E, R>
146+
): (() => void)
147+
on(
148+
eventType: InstanceDisposeEvent,
149+
eventHandler: InstanceDisposeEventHandlerType<T, E, R>
150+
): (() => void)
151+
on(
152+
eventType: InstanceCacheChangeEvent,
153+
eventHandler: InstanceCacheChangeEventHandlerType<T, E, R>
154+
): (() => void)
155+
on(
156+
eventType: InstanceEventType,
157+
eventHandler: InstanceEventHandlerType<T, E, R>
158+
): (() => void) {
159+
let that = this;
160+
if (!this.events) {
161+
this.events = {} as InstanceEvents<T, E, R>;
162+
}
163+
164+
// @ts-ignore
165+
this.events[eventType] = eventHandler;
166+
167+
return function () {
168+
let prevEvent = that.events![eventType];
169+
if (prevEvent && prevEvent === eventHandler) {
170+
delete that.events![eventType];
171+
}
172+
}
173+
174+
}
175+
131176
patchConfig(partialConfig?: Partial<ProducerConfig<T, E, R>>) {
132177
Object.assign(this.config, partialConfig);
133178
}
134179

135180

136-
137181
getPayload(): Record<string, any> {
138182
if (!this.payload) {
139183
this.payload = {};
@@ -208,6 +252,7 @@ export class AsyncState<T, E, R> implements StateInterface<T, E, R> {
208252
if (__DEV__) devtools.startUpdate(this);
209253
this.state = newState;
210254
this.version += 1;
255+
invokeInstanceEvents(this, "change");
211256
if (__DEV__) devtools.emitUpdate(this);
212257

213258
if (this.state.status === Status.success) {
@@ -378,7 +423,10 @@ export class AsyncState<T, E, R> implements StateInterface<T, E, R> {
378423
});
379424
}
380425

381-
runc(createProducerEffects: ProducerEffectsCreator<T, E, R>, props?: RUNCProps<T, E, R>) {
426+
runc(
427+
createProducerEffects: ProducerEffectsCreator<T, E, R>,
428+
props?: RUNCProps<T, E, R>
429+
) {
382430
return this.runWithCallbacks(createProducerEffects, props, props?.args ?? []);
383431
}
384432

@@ -560,6 +608,7 @@ export class AsyncState<T, E, R> implements StateInterface<T, E, R> {
560608
if (__DEV__) devtools.emitDispose(this);
561609

562610
this.willUpdate = false;
611+
invokeInstanceEvents(this, "dispose");
563612
return true;
564613
}
565614

@@ -613,6 +662,62 @@ export class AsyncState<T, E, R> implements StateInterface<T, E, R> {
613662

614663
//region AsyncState methods helpers
615664

665+
function invokeSingleChangeEvent<T, E, R>(
666+
state: State<T, E, R>,
667+
event: StateChangeEventHandler<T, E, R>
668+
) {
669+
if (isFunction(event)) {
670+
(event as ((newState: State<T, E, R>) => void))(state);
671+
} else if (typeof event === "object" && event.status === state.status) {
672+
event.handler(state);
673+
}
674+
}
675+
676+
function invokeInstanceEvents<T, E, R>(
677+
instance: StateInterface<T, E, R>, type: InstanceEventType) {
678+
if (!instance.events || !instance.events[type]) {
679+
return;
680+
}
681+
switch (type) {
682+
case "change": {
683+
let changeEvents = instance.events[type];
684+
if (changeEvents) {
685+
let newState = instance.getState();
686+
if (Array.isArray(changeEvents)) {
687+
changeEvents.forEach(evt => {
688+
invokeSingleChangeEvent(newState, evt);
689+
});
690+
} else {
691+
invokeSingleChangeEvent(newState, changeEvents);
692+
}
693+
}
694+
return;
695+
}
696+
case "dispose": {
697+
let disposeEvents = instance.events[type];
698+
if (disposeEvents) {
699+
if (Array.isArray(disposeEvents)) {
700+
disposeEvents.forEach(evt => evt());
701+
} else {
702+
disposeEvents();
703+
}
704+
}
705+
return;
706+
}
707+
case "cache-change": {
708+
let cacheChangeEvents = instance.events[type];
709+
if (cacheChangeEvents) {
710+
if (Array.isArray(cacheChangeEvents)) {
711+
cacheChangeEvents.forEach(evt => evt(instance.cache));
712+
} else {
713+
cacheChangeEvents(instance.cache);
714+
}
715+
}
716+
return;
717+
}
718+
}
719+
}
720+
616721
export type ProducerWrapperInput<T, E, R> = {
617722
setProducerType(type: ProducerType): void,
618723
setState: StateUpdater<T, E, R>,
@@ -622,6 +727,7 @@ export type ProducerWrapperInput<T, E, R> = {
622727
replaceState(newState: State<T, E, R>, notify?: boolean),
623728
getProducer(): Producer<T, E, R> | undefined | null,
624729
}
730+
625731
export function producerWrapper<T, E = any, R = any>(
626732
input: ProducerWrapperInput<T, E, R>,
627733
props: ProducerProps<T, E, R>,
@@ -996,6 +1102,7 @@ function getTopLevelParent<T, E, R>(base: StateInterface<T, E, R>): StateInterfa
9961102
}
9971103

9981104
function spreadCacheChangeOnLanes<T, E, R>(topLevelParent: StateInterface<T, E, R>) {
1105+
invokeInstanceEvents(topLevelParent, "cache-change");
9991106
if (!topLevelParent.lanes) {
10001107
return;
10011108
}
@@ -1013,6 +1120,7 @@ function makeSource<T, E, R>(instance: StateInterface<T, E, R>): Readonly<Source
10131120
key: instance.key,
10141121
uniqueId: instance.uniqueId,
10151122

1123+
on: instance.on,
10161124
abort: instance.abort,
10171125
replay: instance.replay,
10181126
hasLane: instance.hasLane,
@@ -1313,6 +1421,7 @@ export interface BaseSource<T, E = any, R = any> {
13131421
uniqueId: number,
13141422

13151423
getVersion(): number,
1424+
13161425
getPayload(): Record<string, any>,
13171426

13181427
mergePayload(partialPayload?: Record<string, any>),
@@ -1345,15 +1454,75 @@ export interface BaseSource<T, E = any, R = any> {
13451454
patchConfig(partialConfig?: Partial<ProducerConfig<T, E, R>>),
13461455

13471456
getConfig(): ProducerConfig<T, E, R>,
1457+
1458+
on(
1459+
eventType: InstanceChangeEvent,
1460+
eventHandler: InstanceChangeEventHandlerType<T, E, R>
1461+
): (() => void),
1462+
1463+
on(
1464+
eventType: InstanceDisposeEvent,
1465+
eventHandler: InstanceDisposeEventHandlerType<T, E, R>
1466+
): (() => void),
1467+
1468+
on(
1469+
eventType: InstanceCacheChangeEvent,
1470+
eventHandler: InstanceCacheChangeEventHandlerType<T, E, R>
1471+
): (() => void),
1472+
13481473
}
13491474

1475+
1476+
export type InstanceEventHandlerType<T, E, R> =
1477+
InstanceChangeEventHandlerType<T, E, R>
1478+
|
1479+
InstanceDisposeEventHandlerType<T, E, R>
1480+
|
1481+
InstanceCacheChangeEventHandlerType<T, E, R>;
1482+
1483+
1484+
export type StateChangeEventHandler<T, E = any, R = any> =
1485+
((newState: State<T, E, R>) => void)
1486+
|
1487+
InstanceChangeEventObject<T, E, R>;
1488+
1489+
export type InstanceChangeEventObject<T, E = any, R = any> = {
1490+
status: Status
1491+
handler: ((newState: State<T, E, R>) => void),
1492+
}
1493+
1494+
export type InstanceChangeEventHandlerType<T, E, R> =
1495+
StateChangeEventHandler<T, E, R>
1496+
| StateChangeEventHandler<T, E, R>[];
1497+
1498+
export type InstanceDisposeEventHandlerType<T, E, R> =
1499+
(() => void)
1500+
| (() => void)[];
1501+
export type InstanceCacheChangeEventHandlerType<T, E, R> =
1502+
((cache: Record<string, CachedState<T, E, R>> | null | undefined) => void)
1503+
| ((cache: Record<string, CachedState<T, E, R>> | null | undefined) => void)[];
1504+
1505+
export type InstanceChangeEvent = "change";
1506+
export type InstanceDisposeEvent = "dispose";
1507+
export type InstanceCacheChangeEvent = "cache-change";
1508+
1509+
export type InstanceEventType = InstanceChangeEvent |
1510+
InstanceDisposeEvent |
1511+
InstanceCacheChangeEvent;
1512+
13501513
export type AsyncStateSubscribeProps<T, E, R> = {
13511514
key?: string,
13521515
flags?: number,
13531516
origin?: number,
13541517
cb(s: State<T, E, R>): void,
13551518
}
13561519

1520+
export type InstanceEvents<T, E, R> = {
1521+
change?: InstanceChangeEventHandlerType<T, E, R>,
1522+
dispose?: InstanceDisposeEventHandlerType<T, E, R>,
1523+
['cache-change']?: InstanceCacheChangeEventHandlerType<T, E, R>,
1524+
}
1525+
13571526
export interface StateInterface<T, E = any, R = any> extends BaseSource<T, E, R> {
13581527
// identity
13591528
version: number,
@@ -1389,6 +1558,8 @@ export interface StateInterface<T, E = any, R = any> extends BaseSource<T, E, R>
13891558
// cache
13901559
cache?: Record<string, CachedState<T, E, R>> | null,
13911560

1561+
events?: InstanceEvents<T, E, R>;
1562+
13921563
// dev properties
13931564
journal?: any[], // for devtools, dev only
13941565

@@ -1415,7 +1586,9 @@ export interface StateInterface<T, E = any, R = any> extends BaseSource<T, E, R>
14151586
),
14161587

14171588
run(
1418-
createProducerEffects: ProducerEffectsCreator<T, E, R>, ...args: any[]): AbortFn,
1589+
createProducerEffects: ProducerEffectsCreator<T, E, R>,
1590+
...args: any[]
1591+
): AbortFn,
14191592

14201593
runp(
14211594
createProducerEffects: ProducerEffectsCreator<T, E, R>,
@@ -1635,7 +1808,8 @@ export interface StateBuilderInterface {
16351808
pending: <T>(props: ProducerSavedProps<T>) => PendingState<T>,
16361809
success: <T>(data: T, props: ProducerSavedProps<T> | null) => SuccessState<T>,
16371810
error: <T, E>(data: any, props: ProducerSavedProps<T>) => ErrorState<T, E>,
1638-
aborted: <T, E, R>(reason: any, props: ProducerSavedProps<T>) => AbortedState<T, E, R>,
1811+
aborted: <T, E, R>(
1812+
reason: any, props: ProducerSavedProps<T>) => AbortedState<T, E, R>,
16391813
}
16401814

16411815
export type ForkConfig = {
@@ -1644,7 +1818,9 @@ export type ForkConfig = {
16441818
keepCache?: boolean,
16451819
}
16461820

1647-
export type AsyncStateKeyOrSource<T, E = any, R = any> = string | Source<T, E, R>;
1821+
export type AsyncStateKeyOrSource<T, E = any, R = any> =
1822+
string
1823+
| Source<T, E, R>;
16481824

16491825
export interface ProducerEffects {
16501826
run: <T, E, R>(
@@ -1658,12 +1834,16 @@ export interface ProducerEffects {
16581834
) => Promise<State<T, E, R>> | undefined,
16591835

16601836
select: <T, E, R>(
1661-
input: AsyncStateKeyOrSource<T, E, R>, lane?: string) => State<T, E, R> | undefined,
1837+
input: AsyncStateKeyOrSource<T, E, R>,
1838+
lane?: string
1839+
) => State<T, E, R> | undefined,
16621840
}
16631841

16641842
export type ProducerEffectsCreator<T, E, R> = (props: ProducerProps<T, E, R>) => ProducerEffects;
16651843

1666-
export type ProducerRunInput<T, E = any, R = any> = AsyncStateKeyOrSource<T, E, R> | Producer<T, E, R>;
1844+
export type ProducerRunInput<T, E = any, R = any> =
1845+
AsyncStateKeyOrSource<T, E, R>
1846+
| Producer<T, E, R>;
16671847

16681848
export type ProducerRunConfig = {
16691849
lane?: string,

0 commit comments

Comments
 (0)