Skip to content

Commit 942049d

Browse files
committed
Dynamic import for polyfills partial documentation
1 parent a2b2f38 commit 942049d

1 file changed

Lines changed: 77 additions & 15 deletions

File tree

mod.ts

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,114 @@
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

510
export interface Observer<T> {
611
update(subject: Store<T>): void;
712
}
813

914
export 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+
*/
1726
export 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+
*/
57112
export 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
*/
128182
export 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

Comments
 (0)