Skip to content

Commit 8189446

Browse files
committed
rewrite of useStore !!Breaking changes
1 parent 9cbe3f5 commit 8189446

1 file changed

Lines changed: 97 additions & 40 deletions

File tree

mod.ts

Lines changed: 97 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -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
110162
export type AnyStore = Store<any>;
111163

112164
interface _StoreStack {
113-
[key: string]: AnyStore;
165+
[key: Pointer]: AnyStore;
114166
}
115167

116168
export 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

Comments
 (0)