11import {
2- catchError ,
2+ BehaviorSubject ,
33 distinctUntilChanged ,
4- EMPTY ,
5- map ,
64 NEVER ,
75 type Observable ,
6+ type Subscription ,
87 share ,
9- shareReplay ,
108 tap ,
119} from "rxjs"
12- import { filterObjectByKey } from "../../utils/filterObjectByKey"
13- import { makeObservable } from "../../utils/makeObservable"
1410
15- export class ObservableStore < T > {
16- state : {
17- data : T | undefined
18- status : "pending" | "success" | "error"
19- observableState : "complete" | "error" | "live"
20- error : Error | undefined
21- } = {
22- data : undefined ,
23- status : "pending" ,
24- observableState : "live" ,
25- error : undefined ,
26- }
11+ export type State < T , DefaultValue , Error = unknown > = {
12+ data : T | DefaultValue
13+ status : "pending" | "success" | "error"
14+ observableState : "complete" | "error" | "live"
15+ error : Error | undefined
16+ }
17+
18+ export interface ObservableStoreOptions < T , DefaultValue > {
19+ defaultValue : DefaultValue
20+ compareFn : ( ( a : T , b : T ) => boolean ) | undefined
21+ }
2722
23+ export class ObservableStore < T , DefaultValue , Error = unknown > {
24+ state : State < T , DefaultValue , Error >
2825 source$ : Observable < T | undefined >
26+ sub : Subscription
2927
3028 constructor ( {
3129 source$ : miscSource$ ,
3230 defaultValue,
33- selectorKeys,
3431 compareFn,
3532 } : {
3633 source$ : Observable < T > | ( ( ) => Observable < T > | undefined )
37- defaultValue : T | undefined
38- selectorKeys : ( keyof T ) [ ] | undefined
39- compareFn : ( ( a : T , b : T ) => boolean ) | undefined
40- } ) {
41- this . state . data = defaultValue
42-
34+ } & ObservableStoreOptions < T , DefaultValue > ) {
4335 const source$ =
4436 typeof miscSource$ === "function" ? miscSource$ ( ) : miscSource$
4537
46- console . log ( "source$" , source$ )
38+ const hasNoDefinedSource = source$ === undefined
4739
48- if ( source$ === undefined ) {
49- this . state = {
50- ...this . state ,
51- status : "success" ,
52- observableState : "complete" ,
53- }
40+ this . state = {
41+ data : source$ instanceof BehaviorSubject ? source$ . value : defaultValue ,
42+ status : hasNoDefinedSource ? "success" : "pending" ,
43+ observableState : hasNoDefinedSource ? "complete" : "live" ,
44+ error : undefined ,
5445 }
5546
5647 this . source$ = ( source$ ?? NEVER ) . pipe (
57- // Maybe selector
58- map ( ( v ) => {
59- if ( selectorKeys && typeof v === "object" && v !== null ) {
60- return filterObjectByKey ( v , selectorKeys ) as T | undefined
61- }
62-
63- return v
64- } ) ,
65- // Maybe compareFn
66- distinctUntilChanged ( ( a , b ) => {
67- if ( a === undefined || b === undefined ) return false
68-
69- if ( compareFn ) {
70- return compareFn ( a as T , b as T )
71- }
72-
73- return a === b
74- } ) ,
48+ distinctUntilChanged ( compareFn ) ,
7549 tap ( {
7650 complete : ( ) => {
77- console . log ( "complete" )
78-
7951 this . state = {
8052 ...this . state ,
8153 status : "success" ,
@@ -91,19 +63,31 @@ export class ObservableStore<T> {
9163 }
9264 } ,
9365 next : ( data ) => {
94- console . log ( "next" , data )
95-
9666 this . state = { ...this . state , data }
9767 } ,
9868 } ) ,
99- shareReplay ( { refCount : true , bufferSize : 1 } ) ,
69+ share ( ) ,
10070 )
10171
102- // to clean up
103- this . source$ . subscribe ( )
72+ /**
73+ * @important This eager subscription will optimistically update the state.
74+ * Any observable that is non async (behavior, of(x), etc) will have in fact their state completed
75+ * by the first render cycle.
76+ * Although we only correctly type sync state for `BehaviorSubject` we can in fact get the value in sync
77+ * for more than them.
78+ * Technically the whole pipe chain runs "synchronously".
79+ *
80+ * This is not a guarantee, just that in best case scenario there will be only one render.
81+ */
82+ this . sub = this . source$ . subscribe ( )
10483 }
10584
10685 subscribe = ( next : ( ) => void ) => {
86+ // in some case the observable is already complete by the time we subscribe to it.
87+ if ( this . state . observableState === "complete" ) {
88+ return ( ) => { }
89+ }
90+
10791 const sub = this . source$ . subscribe ( {
10892 complete : next ,
10993 error : next ,
@@ -115,11 +99,7 @@ export class ObservableStore<T> {
11599 }
116100 }
117101
118- getSnapshot = ( ) : typeof this . state => {
119- // console.log("getSnapshot", this.state)
120-
102+ getSnapshot = ( ) => {
121103 return this . state
122104 }
123105}
124-
125- export const storeMap = new Map < string , ObservableStore < unknown > > ( )
0 commit comments