@@ -54,63 +54,115 @@ export class Store<T> implements Subject<T> {
5454 }
5555}
5656
57+ export type Pointer = string ;
58+
59+ /**
60+ * The options to create a store.
61+ */
62+ interface StoreOptions < T > {
63+ /**
64+ * A key or "pointer" to the store location in `window.stores`.
65+ *
66+ * @remarks
67+ * If no pointer is provided, it will be automatically generated and the function will return the pointer as a `string`.
68+ *
69+ * @optional
70+ */
71+ pointer ?: Pointer ;
72+
73+ /**
74+ * A "free" observer already configured. Will execute the callback when the state change.
75+ *
76+ * @optional
77+ */
78+ onChange ?: ( state : T ) => void ;
79+
80+ /**
81+ * Additional observers that need to be attached to the store.
82+ *
83+ * @optional
84+ */
85+ observers ?: Observer < T > [ ] ;
86+
87+ /**
88+ * If set to true, will override the store at the pointer's address if it exists.
89+ *
90+ * @remark
91+ * Has no effect if the address is unallocated.
92+ *
93+ * @default false
94+ *
95+ * @optional
96+ */
97+ override ?: boolean ;
98+ }
99+
57100/**
58- * Create a store and insert it into `window.stores` object. Returns the index pointing to its location .
101+ * Convenience function to create a store. Multiple options can be provided, see { @link StoreOptions} .
59102 *
60- * @example
61- * ```
62- * // Let the store create its own pointer
63- * const ptr = useStore("Test", (state) => console.log(`I was changed to ${state}`));
103+ * 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.
64105 *
65- * // Changing the value
66- * window.stores.get<string>(ptr).set("New Value"); // Will notify observers. Output to console: "I was changed to New Value".
106+ * ### Example
67107 *
68- * // Creating a store with a given pointer
69- * const pointer = crypto.randomUUID();
70- * useStore("Test with pointer", (state) => console.log(`New state: ${state}`), pointer);
108+ * ```typescript
109+ * // 1. Creating a store without any options
110+ * const store1Ptr = useStore(0);
111+ * console.log(Stores.get<number>(store1Ptr).state); // Output: 0
71112 *
72- * window.stores.get<string>(pointer).set("New Value 2"); // Will notify observers. Ouput to console: "New state: New Value 2".
113+ * // 2. Creating a store with options
114+ * const store2Ptr = crypto.randomUUID();
115+ * useStore(0, { pointer: store2Ptr, onChange: (state) => console.log(state), });
116+ *
117+ * console.log(Stores.get<number>(store2Ptr).state); // Ouput: 0
118+ * Stores.get<number>(store2Ptr).set((prevState) => prevState + 1); // Ouput: 1
73119 * ```
74120 *
75- * @param state The initial state value.
76- * @param onChange A "free" callback to be executed on state change.
77- * @param pointer An optionnal pre-defined pointer. If provided, it will use that pointer.
78- * @param observers Additional observers to be attached to the store.
79- * @returns A pointer to the store in `window.stores`.
121+ * @remarks
122+ * If no pointer is provided, a pointer will be assigned and returned.
123+ *
124+ * @param state The initial state. If the store already exists, it will not overwrite the state unless the `override` option is set to `true`.
125+ * @param options See {@link StoreOptions}.
126+ * @returns The {@link Pointer} to the location of the store.
80127 */
81- export function useStore < T > ( state : T , onChange : ( state : T ) => void , pointer ?: string , ...observers : Observer < T > [ ] ) : string {
82- const store = new Store ( state ) ;
83-
84- class StoreObserver implements Observer < T > {
85- public update ( subject : Store < T > ) : void {
86- onChange ( subject . state ) ;
128+ export function useStore < T > ( state : T , options ?: StoreOptions < T > ) : Pointer {
129+ if ( typeof options === "object" ) {
130+ const pointer = options . pointer ?? crypto . randomUUID ( ) ;
131+ const callback = options . onChange ?? ( ( ) => null ) ;
132+ const observers = options . observers ?? [ ] ;
133+ const override = options . override ?? false ;
134+
135+ class StoreObserver implements Observer < T > {
136+ public update ( subject : Store < T > ) : void {
137+ callback ( subject . state ) ;
138+ }
87139 }
88- }
89140
90- store . attach ( new StoreObserver ( ) ) ;
141+ observers . push ( new StoreObserver ( ) ) ;
91142
92- if ( typeof observers !== "undefined" ) {
93- for ( const observer of observers ) {
94- store . attach ( observer ) ;
95- }
96- }
143+ if ( override ) {
144+ const store = new Store ( state ) ;
97145
98- StoreStack . configure ( ) ;
146+ for ( const observer of observers ) {
147+ store . attach ( observer ) ;
148+ }
149+
150+ Stores . addStoreAtPointer ( store , pointer , true , false ) ;
151+ } else {
152+ Stores . upsert ( state , pointer , ...observers ) ;
153+ }
99154
100- if ( typeof pointer === "string" ) {
101- window . stores . addStoreAtPointer ( store , pointer , false , false ) ;
102155 return pointer ;
103156 } else {
104- return window . stores . addStore ( store ) ;
157+ return Stores . addStore ( new Store ( state ) ) ;
105158 }
106-
107159}
108160
109161// deno-lint-ignore no-explicit-any
110162export type AnyStore = Store < any > ;
111163
112164interface _StoreStack {
113- [ key : string ] : AnyStore ;
165+ [ key : Pointer ] : AnyStore ;
114166}
115167
116168export class StoreStack {
@@ -122,14 +174,14 @@ export class StoreStack {
122174
123175 private stores : _StoreStack = { } ;
124176
125- public addStore ( newItem : AnyStore ) : string {
177+ public addStore ( newItem : AnyStore ) : Pointer {
126178 const ptr = crypto . randomUUID ( ) ;
127179
128180 this . stores [ ptr ] = newItem ;
129181 return ptr ;
130182 }
131183
132- public addStoreAtPointer ( newItem : AnyStore , pointer : string , override ?: boolean , verbose ?: boolean ) : void {
184+ public addStoreAtPointer ( newItem : AnyStore , pointer : Pointer , override ?: boolean , verbose ?: boolean ) : void {
133185 if ( typeof this . stores [ pointer ] !== "undefined" && ! override ) {
134186 if ( verbose ) {
135187 return console . warn ( 'Error: Cannot add store at pointer, address is already allocated.' ) ;
@@ -141,7 +193,7 @@ export class StoreStack {
141193 this . stores [ pointer ] = newItem ;
142194 }
143195
144- public upsert < T > ( defaultValue : T , pointer : string , ...observers : Observer < T > [ ] ) : void {
196+ public upsert < T > ( defaultValue : T , pointer : Pointer , ...observers : Observer < T > [ ] ) : void {
145197 if ( typeof this . stores [ pointer ] === "undefined" ) {
146198 this . stores [ pointer ] = new Store ( defaultValue ) ;
147199 }
@@ -151,7 +203,7 @@ export class StoreStack {
151203 }
152204 }
153205
154- public removeStore ( ptr : string ) : void {
206+ public removeStore ( ptr : Pointer ) : void {
155207 if ( typeof this . stores [ ptr ] === "undefined" ) {
156208 return console . error ( 'Error: The pointer address points to unallocated memory.' ) ;
157209 }
@@ -160,11 +212,16 @@ export class StoreStack {
160212 }
161213
162214 // deno-lint-ignore no-explicit-any
163- public get < T = any > ( ptr : string ) : Store < T > | undefined {
215+ public get < T = any > ( ptr : Pointer ) : Store < T > | undefined {
164216 if ( typeof this . stores [ ptr ] !== "undefined" ) {
165217 return this . stores [ ptr ] as Store < T > ;
166218 }
167219
168220 return undefined ;
169221 }
170222}
223+
224+ export const Stores = ( function ( ) {
225+ StoreStack . configure ( ) ;
226+ return window . stores ;
227+ } ) ( ) ;
0 commit comments