11/// <reference path="./global.d.ts" />
22
3+ // Mark: Polyfill
34// Polyfill for browsers that don't support the native `structuredClone`.
45( async ( ) => {
56 if ( 'structuredClone' in window === false ) {
67 await import ( 'https://deno.land/x/corejs@v3.24.1/index.js' ) ;
78 }
89} ) ( ) ;
910
11+ // Mark: Definition Interfaces
1012export interface Observer < T > {
1113 update ( subject : Store < T > ) : void ;
1214}
@@ -20,6 +22,65 @@ export interface Subject<T> {
2022 set ( options : ( prevState : T ) => T ) : void ;
2123}
2224
25+ // Mark: Custom Types
26+ /**
27+ * Convenience type alias for a pointer.
28+ */
29+ export type Pointer = string ;
30+
31+ /**
32+ * Convenience type for `Store` of any type.
33+ */
34+ // deno-lint-ignore no-explicit-any
35+ export type AnyStore = Store < any > ;
36+
37+ // Mark: Custom Errors
38+ /**
39+ * Error that is triggered when a duplicated observer is being attached to a store.
40+ *
41+ * @final
42+ */
43+ export class DuplicateObserverError extends Error {
44+ constructor ( ) {
45+ super ( "This observer is already attached to the store." ) ;
46+ }
47+ }
48+
49+ /**
50+ * Error that is triggered when a non-existing/non-attached observer is being detached from a store.
51+ *
52+ * @final
53+ */
54+ export class UnknownObserverError extends Error {
55+ constructor ( ) {
56+ super ( "Attempted removal of unattached observer." ) ;
57+ }
58+ }
59+
60+ /**
61+ * Error that is triggered when attempting to insert a store at an address that was already allocated,
62+ * without explicit override.
63+ *
64+ * @final
65+ */
66+ export class MemoryAllocationError extends Error {
67+ constructor ( ) {
68+ super ( "Attempted to insert store at already allocated memory address without explicit override." ) ;
69+ }
70+ }
71+
72+ /**
73+ * Error that is triggered when attempting to access a memory address that is unallocated.
74+ *
75+ * @final
76+ */
77+ export class NullPointerError extends Error {
78+ constructor ( ) {
79+ super ( "Attempted to access unallocated memory address." ) ;
80+ }
81+ }
82+
83+ // Mark: Store
2384/**
2485 * A simple store that follows the Observer pattern.
2586 */
@@ -55,11 +116,12 @@ export class Store<T> implements Subject<T> {
55116 * Attaches/subscribes a new {@link Observer} to the store.
56117 *
57118 * @param observer Subscribes a new {@link Observer} to the store.
119+ * @throws {DuplicateObserverError } If the observer has already been attached.
58120 */
59121 public attach ( observer : Observer < T > ) : void {
60122 const isExist = this . #observers. includes ( observer ) ;
61123 if ( isExist ) {
62- return console . warn ( 'Subject: Observer has been attached already.' ) ;
124+ throw new DuplicateObserverError ( ) ;
63125 }
64126
65127 this . #observers. push ( observer ) ;
@@ -69,11 +131,12 @@ export class Store<T> implements Subject<T> {
69131 * Detaches/unsubscribes an {@link Observer} from the store.
70132 *
71133 * @param observer The `Observer` to remove.
134+ * @throws {UnknownObserverError } If the observer was non-existent in the store.
72135 */
73136 public detach ( observer : Observer < T > ) : void {
74137 const observerIndex = this . #observers. indexOf ( observer ) ;
75138 if ( observerIndex === - 1 ) {
76- return console . error ( 'Subject: Nonexistent observer.' , observer ) ;
139+ throw new UnknownObserverError ( ) ;
77140 }
78141
79142 this . #observers. splice ( observerIndex , 1 ) ;
@@ -106,10 +169,7 @@ export class Store<T> implements Subject<T> {
106169 }
107170}
108171
109- /**
110- * Convenience type alias for a pointer.
111- */
112- export type Pointer = string ;
172+ // Mark: useStore Options
113173
114174/**
115175 * The options to create a store.
@@ -148,8 +208,33 @@ export interface StoreOptions<T> {
148208 * @optional
149209 */
150210 override ?: boolean ;
211+
212+ /**
213+ * See {@link StoreOptionsErrorHandling}
214+ */
215+ errorHandling ?: StoreOptionsErrorHandling ;
151216}
152217
218+ /**
219+ * Defines how {@link useStore} should handle errors.
220+ */
221+ export interface StoreOptionsErrorHandling {
222+ /**
223+ * Set to `true` if error messages should be outputed to the console.
224+ *
225+ * `verbose` is set to `false` by default.
226+ */
227+ verbose ?: boolean ;
228+
229+ /**
230+ * By default {@link useStore} will silently ignore errors to allow the program to
231+ * continue its execution. If `stopOnError` is set to `true`, the function will stop
232+ * if an error is thrown and will rethrow it.
233+ */
234+ stopOnError ?: boolean ;
235+ }
236+
237+ // Mark: useStore
153238/**
154239 * Convenience function to create a store. Multiple options can be provided, see {@link StoreOptions}.
155240 *
@@ -173,6 +258,8 @@ export interface StoreOptions<T> {
173258 * Stores.get<number>(store2Ptr).set((prevState) => prevState + 1); // Ouput: 1
174259 * ```
175260 *
261+ * @throws {DuplicateObserverError } If provided observer(s) is/are already attached to the store or duplicates.
262+ *
176263 * @param state The initial state. If the store already exists, it will not overwrite the state unless the `override` option is set to `true`.
177264 * @param options See {@link StoreOptions}.
178265 * @returns The {@link Pointer} to the location of the store.
@@ -200,14 +287,34 @@ export function useStore<T>(state: T, options?: StoreOptions<T>): Pointer {
200287 const store = new Store ( state ) ;
201288
202289 for ( const observer of observers ) {
203- store . attach ( observer ) ;
290+ try {
291+ store . attach ( observer ) ;
292+ } catch ( error ) {
293+ if ( options . errorHandling ?. verbose === true ) {
294+ console . error ( error ) ;
295+ }
296+
297+ if ( options . errorHandling ?. stopOnError === true ) {
298+ throw error ;
299+ }
300+ }
204301 }
205302
206303 Stores . addStoreAtPointer ( store , pointer , { override : true } ) ;
207304 } else {
208305 // If a store exists at address, it will NOT be overrided, but additional observers will be attached to the store.
209306
210- Stores . upsert ( state , pointer , ...observers ) ;
307+ try {
308+ Stores . upsert ( state , pointer , ...observers ) ;
309+ } catch ( error ) {
310+ if ( options . errorHandling ?. verbose === true ) {
311+ console . error ( error ) ;
312+ }
313+
314+ if ( options . errorHandling ?. stopOnError === true ) {
315+ throw error ;
316+ }
317+ }
211318 }
212319
213320 return pointer ;
@@ -218,12 +325,7 @@ export function useStore<T>(state: T, options?: StoreOptions<T>): Pointer {
218325 }
219326}
220327
221- /**
222- * Convenience type for `Store` of any type.
223- */
224- // deno-lint-ignore no-explicit-any
225- export type AnyStore = Store < any > ;
226-
328+ // Mark: StoreStack
227329/**
228330 * @internal
229331 * Structure for the `Store` memory stack.
@@ -272,16 +374,18 @@ export class StoreStack {
272374 * @param newItem The {@link Store} to be added to the stack.
273375 * @param pointer The {@link Pointer} to the memory address where the store should be inserted.
274376 * @param options Defines if a store should be overrided if it exists at the `Pointer` and if a verbose error should be returned if override is set to false.
377+ *
378+ * @throws {MemoryAllocationError } If the address is already allocated and `override` isn't set to `true`.
275379 */
276380 public addStoreAtPointer ( newItem : AnyStore , pointer : Pointer , options : { override ?: boolean , verbose ?: boolean } ) : void {
277381 const { override, verbose } = options ;
278382
279383 if ( typeof this . #stores[ pointer ] !== "undefined" && ! override ) {
280384 if ( verbose ) {
281- return console . warn ( 'Error: Cannot add store at pointer, address is already allocated.' ) ;
282- } else {
283- return ;
385+ console . error ( 'Error: Cannot add store at pointer, address is already allocated.' ) ;
284386 }
387+
388+ throw new MemoryAllocationError ( ) ;
285389 }
286390
287391 this . #stores[ pointer ] = newItem ;
@@ -292,6 +396,8 @@ export class StoreStack {
292396 * If no store is instantiated, it will create one holding the `defaultValue` provided.
293397 * Otherwise, it will only attach {@link Observer Observers} if they are provided.
294398 *
399+ * @throws {DuplicateObserverError } If observer is duplicate.
400+ *
295401 * @param defaultValue A default state value to insert if a new store is created.
296402 * @param pointer The {@link Pointer} to the memory address.
297403 * @param observers {@link Observer Observers } to attach to the store.
@@ -302,7 +408,11 @@ export class StoreStack {
302408 }
303409
304410 for ( const observer of observers ) {
305- this . #stores[ pointer ] . attach ( observer ) ;
411+ try {
412+ this . #stores[ pointer ] . attach ( observer ) ;
413+ } catch ( error ) {
414+ throw error ;
415+ }
306416 }
307417 }
308418
@@ -312,21 +422,24 @@ export class StoreStack {
312422 *
313423 * @param ptr The {@link Pointer}'s address of the store.
314424 * @param options If the removal fails, should the function verbose it to the console? Defaults to false.
425+ *
426+ * @throws {NullPointerError } If attempting to delete a store at unallocated memory address.
315427 */
316428 public removeStore ( ptr : Pointer , options : { verbose ?: boolean } ) : void {
317429 if ( typeof this . #stores[ ptr ] === "undefined" ) {
318430 if ( options . verbose ) {
319- return console . error ( 'Error: The pointer address points to unallocated memory.' ) ;
320- } else {
321- return ;
431+ console . error ( 'Error: The pointer address points to unallocated memory.' ) ;
322432 }
433+
434+ throw new NullPointerError ( ) ;
323435 }
324436
325437 delete this . #stores[ ptr ] ;
326438 }
327439
328440 /**
329- * Returns a reference to the store at the address. A type can be passed in order to make the return typed.
441+ * Returns a reference to the store at the address if it exists, otherwise undefined. A type can be
442+ * passed in order to make the return typed.
330443 *
331444 * ## Example
332445 * ```typescript
@@ -347,6 +460,7 @@ export class StoreStack {
347460 }
348461}
349462
463+ // Mark: Stores
350464/**
351465 * Convenience constant, provide access to the global `StoreStack` and creates it if it doesn't exist.
352466 */
0 commit comments