From 1a3332dd88f15fe3f4b6db007cf463d0a7fbde4e Mon Sep 17 00:00:00 2001
From: Omar Luq <84993125+omarluq@users.noreply.github.com>
Date: Thu, 23 Nov 2023 19:24:45 -0600
Subject: [PATCH] I do not enjoy this commit

---
 README.md                |  8 +++--
 src/createStore.ts       | 19 +++++++++--
 src/store.ts             |  4 +--
 test/createStore.test.ts | 69 +++++++++++++++++++++++++++++++++++-----
 test/store.test.ts       | 36 +++++++++++++--------
 test/useStore.test.ts    |  4 +--
 6 files changed, 110 insertions(+), 30 deletions(-)

diff --git a/README.md b/README.md
index d6cf0bbd..9ce9f375 100644
--- a/README.md
+++ b/README.md
@@ -80,10 +80,14 @@ Let's dive into a simple use case to see how Stimulus Store works. In this examp
 ```
 
 ```js
-// controllers/stores/counter.js
+// controllers/stores.js
 import { createStore } from "stimulus-store";
 
-export const counterStore = createStore(name: 'counterStore', initialValue: 0, type: Number)
+export let counterStore;
+
+(async function initializeStores() {
+  counterStore = await createStore({ name: 'counterStore', initialValue: 0, type: Number });
+})();
 ```
 
 ```js
diff --git a/src/createStore.ts b/src/createStore.ts
index 36efa769..ed30cb92 100644
--- a/src/createStore.ts
+++ b/src/createStore.ts
@@ -31,7 +31,7 @@ import { Store } from './store';
 import type { StoreOptions } from './storeOptions';
 import { typeMap } from './storeValuesTypeMap';
 
-export function createStore<T>(options: StoreOptions<T>): Store<T> {
+export async function createStore<T>(options: StoreOptions<T>): Promise<Store<T>>{
   const { name, type, initialValue } = options;
   if (typeof initialValue === "undefined") {
     throw new Error("Store must be initialized with a value");
@@ -45,5 +45,20 @@ export function createStore<T>(options: StoreOptions<T>): Store<T> {
     throw new Error(`Invalid type: ${type?.name}`);
   }
 
-  return new Store<T>(symbolName, initialValue, type);
+  // return new Store<T>(symbolName,initialValue, type);
+  
+  const store: Store<T> = new Store<T>(symbolName, type);
+  try {
+    await store.set(initialValue);
+    return store;
+  } catch (error) {
+    // Assert that error is an instance of Error
+    if (error instanceof Error) {
+      // Handle the error here. For example, you can re-throw it with a more meaningful message:
+      throw new Error(`Failed to create store: ${error.message}`);
+    } else {
+      // If error is not an instance of Error, you can throw a generic error or handle it differently
+      throw new Error('An unknown error occurred while creating the store');
+    }
+  }
 }
\ No newline at end of file
diff --git a/src/store.ts b/src/store.ts
index 78dcf466..fd40fe9d 100644
--- a/src/store.ts
+++ b/src/store.ts
@@ -34,14 +34,12 @@ export class Store<T> {
    * Creates a new store.
    *
    * @param {symbol} name - The name of the store.
-   * @param {T} initialValue - The initial value of the store.
    * @param {new (...args: unknown[]) => unknown} type - The type of the store's value.
    */
-  constructor(name: symbol, initialValue: T, type: new (...args: unknown[]) => unknown) {
+  constructor(name: symbol, type: new (...args: unknown[]) => unknown) {
     this.name = name;
     this.subscribers = new Set();
     this.type = type;
-    this.set(initialValue);
   }
 
   /**
diff --git a/test/createStore.test.ts b/test/createStore.test.ts
index 65435265..60e69eee 100644
--- a/test/createStore.test.ts
+++ b/test/createStore.test.ts
@@ -1,22 +1,75 @@
 import { createStore } from '../src/createStore';
 
 describe('createStore', () => {
-  it('should throw an error if no initial value is provided', () => {
-    expect(() => createStore({ name: 'testStore', type: Number } as any)).toThrow('Store must be initialized with a value');
+  it('should throw an error if no initial value is provided', async () => {
+    await expect(createStore({ name: 'testStore', type: Number } as any))
+      .rejects.toThrow('Store must be initialized with a value');
   });
 
-  it('should throw an error if no name is provided', () => {
-    expect(() => createStore({ initialValue: 0, type: Number } as any)).toThrow('Store name must be of Type string');
+  it('should throw an error if no initial name is provided', async () => {
+    await expect(createStore({ initialValue: 0, type: Number } as any))
+      .rejects.toThrow('Store name must be of Type string');
   });
 
-  it('should create a new store with the provided name, initial value, and type', () => {
-    const store = createStore({ name: 'testStore', initialValue: 0, type: Number });
+  it('should create a new store with the provided name, initial value, and type', async () => {
+    const store = await createStore({ name: 'testStore', initialValue: 0, type: Number });
     expect(store.get()).toBe(0);
     expect(typeof store.get()).toBe('number');
     expect(store.name.toString()).toBe('Symbol(testStore)');
   });
 
-  it('should throw an error if an invalid type is provided', () => {
-    expect(() => createStore({ name: 'testStore', initialValue: 0, type: Set } as any)).toThrow('Invalid type: Set');
+  it('should throw an error if an invalid type is provided', async () => {
+    await expect(createStore({ name: 'testStore', type: Set, initialValue: 0 } as any))
+    .rejects.toThrow('Invalid type: Set');
+  });
+
+  it('should create a store with a number type', async () => {
+    const store = await createStore({ name: 'testStore', initialValue: 0, type: Number });
+    expect(typeof store.get()).toBe('number');
+  });
+
+  it('should create a store with a string type', async () => {
+    const store = await createStore({ name: 'testStore', initialValue: 'test', type: String });
+    expect(typeof store.get()).toBe('string');
+  });
+
+  it('should create a store with a boolean type', async () => {
+    const store = await createStore({ name: 'testStore', initialValue: true, type: Boolean });
+    expect(typeof store.get()).toBe('boolean');
+  });
+
+  it('should create a store with an array type', async () => {
+    const store = await createStore({ name: 'testStore', initialValue: [1, 2, 3], type: Array });
+    expect(Array.isArray(store.get())).toBe(true);
+  });
+  
+  it('should create a store with an object type', async () => {
+    const store = await createStore({ name: 'testStore', initialValue: { key: 'value' }, type: Object });
+    expect(typeof store.get()).toBe('object');
+  });
+  
+  it('should throw an error when createStore is called with an initialValue that does not match the number type', async () => {
+    await expect(createStore({ name: 'testStore', initialValue: 'not a number', type: Number }))
+      .rejects.toThrow(`Failed to create store: Value 'not a number' must be of type Number`);
+  });
+  
+  it('should throw an error when createStore is called with an initialValue that does not match the string type', async () => {
+    await expect(createStore({ name: 'testStore', initialValue: 123, type: String }))
+      .rejects.toThrow(`Failed to create store: Value '123' must be of type String`);
+  });
+  
+  it('should throw an error when createStore is called with an initialValue that does not match the boolean type', async () => {
+    await expect(createStore({ name: 'testStore', initialValue: 'not a boolean', type: Boolean }))
+      .rejects.toThrow(`Failed to create store: Value 'not a boolean' must be of type Boolean`);
+  });
+  
+  it('should throw an error when createStore is called with an initialValue that does not match the array type', async () => {
+    await expect(createStore({ name: 'testStore', initialValue: 'not an array', type: Array }))
+      .rejects.toThrow(`Failed to create store: Value 'not an array' must be of type Array`);
+  });
+  
+  it('should throw an error when createStore is called with an initialValue that does not match the object type', async () => {
+    await expect(createStore({ name: 'testStore', initialValue: 'not an object', type: Object }))
+      .rejects.toThrow(`Failed to create store: Value 'not an object' must be of type Object`);
   });
 });
\ No newline at end of file
diff --git a/test/store.test.ts b/test/store.test.ts
index d55a5b26..f0155c75 100644
--- a/test/store.test.ts
+++ b/test/store.test.ts
@@ -4,11 +4,11 @@ describe('Store', () => {
   let store: Store<number>;
 
   beforeEach(() => {
-    store = new Store(Symbol('testStore'), 0, Number);
+    store = new Store(Symbol('testStore'), Number);
   });
 
   it('should initialize with the correct value', () => {
-    expect(store.get()).toBe(0);
+    expect(store.get()).toBeUndefined();
   });
 
   it('should update the value correctly', () => {
@@ -33,32 +33,42 @@ describe('Store', () => {
     expect(mockCallback).not.toHaveBeenCalledWith(15);
   });
 
-  it('should not notify subscribers when value is the same', () => {
+  it('should not notify subscribers when value is the same', async () => {
     const mockCallback = jest.fn();
+    
+    // First set: initial value
+    await store.set(0);
+    
+    // Subscribe to the store and invoke the callback
     store.subscribe(mockCallback);
-
-    // Set the value to the same value
-    // called only once cause the callback is invoked on subscription as well
-    store.set(0);
-    expect(mockCallback).not.toHaveBeenCalledWith(1);
+    
+    // Second set: change value to 1 and invoke the callback
+    await store.set(1);
+    
+    // Third set: attempt to set the same value (1), ignore the callback
+    await store.set(1);
+    
+    // Expect the callback to have been called twice: once for the subscription, and once for the second set
+    expect(mockCallback).toHaveBeenCalledTimes(2);
   });
 
-  it('should not notify subscribers when filter returns false', () => {
+  it('should not notify subscribers when filter returns false', async () => {
     const mockCallback = jest.fn();
     store.subscribe(mockCallback);
-
-    store.set(20, { filter: () => false });
+  
+    await store.set(20, { filter: () => false });
     expect(mockCallback).not.toHaveBeenCalledWith(20);
   });
 
   it('should call the callback with the current value when a function is passed to set', () => {
     const callback = jest.fn().mockImplementation(currentValue => currentValue + 10);
+    store.set(0);
     store.set(callback);
     expect(callback).toHaveBeenCalledWith(0);
     expect(store.get()).toBe(10);
   });
 
   it('should throw an error when setting a value of the wrong type', async () => {
-    await expect(Promise.resolve(store.set('wrong type' as any))).rejects.toThrow(`Value 'wrong type' must be of type ${Number.name}`);
+    await expect(store.set('wrong type' as any)).rejects.toThrow(`Value 'wrong type' must be of type ${Number.name}`);
   });
-});
+});
\ No newline at end of file
diff --git a/test/useStore.test.ts b/test/useStore.test.ts
index 3beac807..d3c8857e 100644
--- a/test/useStore.test.ts
+++ b/test/useStore.test.ts
@@ -8,8 +8,8 @@ describe('useStore', () => {
   let mockController: StoreController<any>;
   let testStore: Store<any>;
 
-  beforeEach(() => {
-    testStore = createStore({ name: 'testStore', type: Number, initialValue: 0 });
+  beforeEach(async () => {
+    testStore = await createStore({ name: 'testStore', type: Number, initialValue: 0 });
     mockController = {
       constructor: {
         stores: [testStore]