11/// <reference path="./global.d.ts" />
22
3- import 'https://deno.land/x/corejs@v3.24.1/index.js' ;
3+ // Polyfill for browsers that don't support the native `structuredClone`.
4+ ( async ( ) => {
5+ if ( 'structuredClone' in window === false ) {
6+ await import ( 'https://deno.land/x/corejs@v3.24.1/index.js' ) ;
7+ }
8+ } ) ( ) ;
49
510export interface Observer < T > {
611 update ( subject : Store < T > ) : void ;
712}
813
914export interface Subject < T > {
15+ readonly state : T ;
1016 attach ( observer : Observer < T > ) : void ;
1117 detach ( observer : Observer < T > ) : void ;
1218 notify ( ) : void ;
1319 set ( options : T ) : void ;
1420 set ( options : ( prevState : T ) => T ) : void ;
1521}
1622
23+ /**
24+ * A simple store that follows the Observer pattern.
25+ */
1726export class Store < T > implements Subject < T > {
18- private observers : Observer < T > [ ] = [ ] ;
27+ /**
28+ * @internal
29+ * List of all the observers attached to the store.
30+ */
31+ #observers: Observer < T > [ ] = [ ] ;
32+
33+ /**
34+ * @internal
35+ * The internal representation of state.
36+ */
37+ #state: T ;
1938
20- constructor ( public state : T ) { }
39+ /**
40+ * A deep copy of the internal state, to prevent referenced objects to be directly mutated.
41+ *
42+ * @remarks
43+ * To mutate the state, use {@link Store.set}.
44+ */
45+ public get state ( ) : T { return structuredClone ( this . #state) as T ; }
2146
47+ /**
48+ * Creates a new `Store` (aka {@link Subject}) that can be subscribed to, or observed for a state change.
49+ */
50+ constructor ( state : T ) {
51+ this . #state = state ;
52+ }
53+
54+ /**
55+ * Attaches/subscribes a new {@link Observer} to the store.
56+ *
57+ * @param observer Subscribes a new {@link Observer} to the store.
58+ */
2259 public attach ( observer : Observer < T > ) : void {
23- const isExist = this . observers . includes ( observer ) ;
60+ const isExist = this . # observers. includes ( observer ) ;
2461 if ( isExist ) {
2562 return console . warn ( 'Subject: Observer has been attached already.' ) ;
2663 }
2764
28- this . observers . push ( observer ) ;
65+ this . # observers. push ( observer ) ;
2966 }
3067
68+ /**
69+ * Detaches/unsubscribes an {@link Observer} from the store.
70+ *
71+ * @param observer The `Observer` to remove.
72+ */
3173 public detach ( observer : Observer < T > ) : void {
32- const observerIndex = this . observers . indexOf ( observer ) ;
74+ const observerIndex = this . # observers. indexOf ( observer ) ;
3375 if ( observerIndex === - 1 ) {
3476 return console . error ( 'Subject: Nonexistent observer.' , observer ) ;
3577 }
3678
37- this . observers . splice ( observerIndex , 1 ) ;
79+ this . # observers. splice ( observerIndex , 1 ) ;
3880 }
3981
82+ /**
83+ * Notifies all the observers of a change. Will trigger automatically when the state is changed through
84+ * {@link Store.set}, but it can be forced by calling this method directly.
85+ */
4086 public notify ( ) : void {
41- for ( const observer of this . observers ) {
87+ for ( const observer of this . # observers) {
4288 observer . update ( this ) ;
4389 }
4490 }
4591
92+ /**
93+ * Method to mutate the internal state. If a value is directly provided, assign the internal state to that value.
94+ * A function can also be provided to access a dereferenced copy of the previous state.
95+ *
96+ * @param options A value, or function to access the previous state.
97+ */
4698 public set ( options : T | ( ( prevState : T ) => T ) ) : void {
4799 if ( typeof options === "function" ) {
48- this . state = ( options as ( prevState : T ) => T ) ( structuredClone ( this . state ) as T ) ;
100+ this . # state = structuredClone ( ( options as ( prevState : T ) => T ) ( structuredClone ( this . state ) as T ) ) as T ;
49101 } else {
50- this . state = structuredClone ( options ) as T ;
102+ this . # state = structuredClone ( options ) as T ;
51103 }
52104
53105 this . notify ( ) ;
54106 }
55107}
56108
109+ /**
110+ * Convenience type alias for a pointer.
111+ */
57112export type Pointer = string ;
58113
59114/**
@@ -101,7 +156,9 @@ export interface StoreOptions<T> {
101156 * Convenience function to create a store. Multiple options can be provided, see {@link StoreOptions}.
102157 *
103158 * Unless the `override` option is set to `true`, this function will not override an existing store, it
104- * is therefor safe to use to make sure a store is defined in multiple components.
159+ * is therefore safe to use to make sure a store is defined in multiple components.
160+ *
161+ * **If no pointer is provided, a pointer will be assigned and returned.**
105162 *
106163 * ### Example
107164 *
@@ -118,15 +175,14 @@ export interface StoreOptions<T> {
118175 * Stores.get<number>(store2Ptr).set((prevState) => prevState + 1); // Ouput: 1
119176 * ```
120177 *
121- * @remarks
122- * If no pointer is provided, a pointer will be assigned and returned.
123- *
124178 * @param state The initial state. If the store already exists, it will not overwrite the state unless the `override` option is set to `true`.
125179 * @param options See {@link StoreOptions}.
126180 * @returns The {@link Pointer} to the location of the store.
127181 */
128182export function useStore < T > ( state : T , options ?: StoreOptions < T > ) : Pointer {
129183 if ( typeof options === "object" ) {
184+ // Options are provided
185+
130186 const pointer = options . pointer ?? crypto . randomUUID ( ) ;
131187 const callback = options . onChange ?? ( ( ) => null ) ;
132188 const observers = options . observers ?? [ ] ;
@@ -141,6 +197,8 @@ export function useStore<T>(state: T, options?: StoreOptions<T>): Pointer {
141197 observers . push ( new StoreObserver ( ) ) ;
142198
143199 if ( override ) {
200+ // If a store exists at address, it will be overrided.
201+
144202 const store = new Store ( state ) ;
145203
146204 for ( const observer of observers ) {
@@ -149,11 +207,15 @@ export function useStore<T>(state: T, options?: StoreOptions<T>): Pointer {
149207
150208 Stores . addStoreAtPointer ( store , pointer , true , false ) ;
151209 } else {
210+ // If a store exists at address, it will NOT be overrided, but additional observers will be attached to the store.
211+
152212 Stores . upsert ( state , pointer , ...observers ) ;
153213 }
154214
155215 return pointer ;
156216 } else {
217+ // No options are provided
218+
157219 return Stores . addStore ( new Store ( state ) ) ;
158220 }
159221}
@@ -221,7 +283,7 @@ export class StoreStack {
221283 }
222284}
223285
224- export const Stores = ( function ( ) {
286+ export const Stores : StoreStack = ( function ( ) {
225287 StoreStack . configure ( ) ;
226288 return window . stores ;
227289} ) ( ) ;
0 commit comments