1+ import type { AFunction , NonEmptyString , PotentialPromise , Prefix } from "../types" ;
2+ import { useSyncExternalStore } from "react" ;
3+ import { type SharedApi , SharedData } from "../SharedData" ;
4+ import useShared from "./use-shared" ;
5+ import { log } from "../lib/utils" ;
6+
7+ type Unsubscribe = ( ) => void ;
8+ namespace SubscriberEvents {
9+ export type OnError = ( error : unknown ) => void ;
10+ export type Set < T > = ( value : T ) => void
11+ }
12+
13+ type Subscriber < T > = ( set : SubscriberEvents . Set < T > , onError : SubscriberEvents . OnError ) => PotentialPromise < Unsubscribe | void | undefined > ;
14+
15+ type SharedSubscriptionsState < T > = {
16+ fnState : {
17+ data ?: T ;
18+ isLoading : boolean ;
19+ error ?: unknown ;
20+ track : number
21+ } ,
22+ unsubscribe ?: Unsubscribe | void ;
23+ }
24+
25+ class SharedSubscriptionsData extends SharedData < SharedSubscriptionsState < unknown > > {
26+ defaultValue ( ) {
27+ return {
28+ fnState : {
29+ data : undefined ,
30+ isLoading : false ,
31+ error : undefined ,
32+ track : 0 ,
33+ }
34+ } ;
35+ }
36+
37+ init ( key : string , prefix : Prefix ) {
38+ super . init ( key , prefix , this . defaultValue ( ) ) ;
39+ }
40+
41+ setValue < T > ( key : string , prefix : Prefix , data : SharedSubscriptionsState < T > ) {
42+ super . setValue ( key , prefix , data ) ;
43+ }
44+
45+ removeListener ( key : string , prefix : Prefix , listener : AFunction ) {
46+ super . removeListener ( key , prefix , listener ) ;
47+ /*const entry = this.get(key, prefix);
48+ if (entry?.listeners.length === 0) {
49+ entry.unsubscribe?.();
50+ entry.unsubscribe = undefined;
51+ }*/
52+ }
53+ }
54+
55+ export class SharedSubscriptionsApi implements SharedApi < SharedSubscriptionsState < unknown > > {
56+ get < T , S extends string = string > ( key : NonEmptyString < S > , scopeName : Prefix = "_global" ) {
57+ const prefix : Prefix = scopeName || "_global" ;
58+ return sharedSubscriptionsData . get ( key , prefix ) ?. fnState as T ;
59+ }
60+ set < T , S extends string = string > ( key : NonEmptyString < S > , fnState : SharedSubscriptionsState < T > , scopeName : Prefix = "_global" ) {
61+ const prefix : Prefix = scopeName || "_global" ;
62+ sharedSubscriptionsData . setValue ( key , prefix , fnState ) ;
63+ }
64+ clearAll ( ) {
65+ sharedSubscriptionsData . clearAll ( ) ;
66+ }
67+ clear ( key : string , scopeName : Prefix = "_global" ) {
68+ const prefix : Prefix = scopeName || "_global" ;
69+ sharedSubscriptionsData . clear ( key , prefix ) ;
70+ }
71+ has ( key : string , scopeName : Prefix = "_global" ) {
72+ const prefix : Prefix = scopeName || "_global" ;
73+ return Boolean ( sharedSubscriptionsData . has ( key , prefix ) ) ;
74+ }
75+ getAll ( ) {
76+ return sharedSubscriptionsData . data ;
77+ }
78+ }
79+
80+ export const sharedSubscriptionsApi = new SharedSubscriptionsApi ( ) ;
81+
82+ const sharedSubscriptionsData = new SharedSubscriptionsData ( ) ;
83+
84+ export const useSharedSubscription = < T , S extends string = string > ( key : NonEmptyString < S > , subscriber : Subscriber < T > , scopeName ?: Prefix ) => {
85+
86+ const { prefix} = useShared ( scopeName ) ;
87+
88+ sharedSubscriptionsData . init ( key , prefix ) ;
89+
90+ const state = useSyncExternalStore < NonNullable < SharedSubscriptionsState < T > [ 'fnState' ] > > ( ( listener ) => {
91+ sharedSubscriptionsData . init ( key , prefix ) ;
92+ sharedSubscriptionsData . addListener ( key , prefix , listener ) ;
93+
94+ return ( ) => {
95+ sharedSubscriptionsData . removeListener ( key , prefix , listener ) ;
96+ }
97+ } , ( ) => sharedSubscriptionsData . get ( key , prefix ) ! . fnState as NonNullable < SharedSubscriptionsState < T > [ 'fnState' ] > ) ;
98+
99+ const set = ( value : T ) => {
100+ const entry = sharedSubscriptionsData . get ( key , prefix ) ! ;
101+ entry . fnState = { ...entry . fnState , data : value , track : entry . fnState . track + 1 } ;
102+ entry . listeners . forEach ( l => l ( ) ) ;
103+ }
104+
105+ const onError = ( error : unknown ) => {
106+ const entry = sharedSubscriptionsData . get ( key , prefix ) ! ;
107+ entry . fnState = { ...entry . fnState , isLoading : false , data : undefined , error } ;
108+ entry . listeners . forEach ( l => l ( ) ) ;
109+ }
110+
111+ const trigger = async ( force : boolean ) => {
112+ const entry = sharedSubscriptionsData . get ( key , prefix ) ! ;
113+ if ( force ) {
114+ const unsubscribe = entry . unsubscribe ;
115+ if ( unsubscribe ) {
116+ unsubscribe ( ) ;
117+ entry . unsubscribe = undefined ;
118+ }
119+ entry . fnState = { ...entry . fnState , isLoading : false , data : undefined , error : undefined } ;
120+ }
121+ if ( entry . fnState . isLoading || entry . fnState . data !== undefined ) return entry . fnState ;
122+ log ( "triggered !!" ) ;
123+ entry . fnState = { ...entry . fnState , isLoading : true , error : undefined } ;
124+ entry . listeners . forEach ( l => l ( ) ) ;
125+ try {
126+ entry . unsubscribe = await subscriber ( set , onError ) ;
127+ } catch ( error ) {
128+ entry . fnState = { ...entry . fnState , isLoading : false , error } ;
129+ }
130+ entry . listeners . forEach ( l => l ( ) ) ;
131+ } ;
132+
133+ // noinspection JSUnusedGlobalSymbols
134+ return {
135+ state,
136+ trigger : ( ) => {
137+ void trigger ( false ) ;
138+ } ,
139+ forceTrigger : ( ) => {
140+ void trigger ( true ) ;
141+ } ,
142+ unsubscribe : ( ) => {
143+ //TODO: think of something
144+ }
145+ } as const ;
146+ } ;
0 commit comments