--- /dev/null
+import { createStore } from '../src'
+
+describe('Store', () => {
+ function buildStore() {
+ return createStore(
+ 'main',
+ () => ({
+ name: 'Eduardo',
+ }),
+ {
+ upperCaseName: ({ name }) => name.toUpperCase(),
+ }
+ )
+ }
+
+ it('adds getters to the store', () => {
+ const store = buildStore()
+ expect(store.upperCaseName.value).toBe('EDUARDO')
+ store.state.name = 'Ed'
+ expect(store.upperCaseName.value).toBe('ED')
+ })
+
+ it('updates the value', () => {
+ const store = buildStore()
+ store.state.name = 'Ed'
+ expect(store.upperCaseName.value).toBe('ED')
+ })
+})
-import { ref, watch } from '@vue/composition-api'
+import { ref, watch, computed } from '@vue/composition-api'
import { Ref } from '@vue/composition-api/dist/reactivity'
import {
StateTree,
SubscriptionCallback,
DeepPartial,
isPlainObject,
+ StoreGetters,
+ StoreGetter,
} from './types'
import { devtoolPlugin } from './devtools'
* they want, no? like user/cart
*/
+type CombinedStore<
+ Id extends string,
+ S extends StateTree,
+ G extends Record<string, StoreGetter<S>>
+> = Store<Id, S> & StoreGetters<S, G>
+
/**
* Creates a store instance
* @param id unique identifier of the store, like a name. eg: main, cart, user
* @param initialState initial state applied to the store, Must be correctly typed to infer typings
*/
-export function createStore<Id extends string, S extends StateTree>(
+export function createStore<
+ Id extends string,
+ S extends StateTree,
+ G extends Record<string, StoreGetter<S>>
+>(
id: Id,
- buildState: () => S
+ buildState: () => S,
+ getters: G
// methods: Record<string | symbol, StoreMethod>
-): Store<Id, S> {
+): CombinedStore<Id, S, G> {
const state: Ref<S> = ref(buildState())
function replaceState(newState: S) {
state.value = newState
// TODO: return function to remove subscription
}
- const store: Store<Id, S> = {
+ const storeWithState: Store<Id, S> = {
id,
// it is replaced below by a getter
state: state.value,
},
}
+ // @ts-ignore we have to build it
+ const computedGetters: StoreGetters<S, G> = {}
+ for (const getterName in getters) {
+ const method = getters[getterName]
+ // @ts-ignore
+ computedGetters[getterName] = computed<ReturnType<typeof method>>(() =>
+ getters[getterName](state.value)
+ )
+ }
+
+ const store = {
+ ...storeWithState,
+ ...computedGetters,
+ }
+
// make state access invisible
Object.defineProperty(store, 'state', {
get: () => state.value,
* @param buildState function that returns a state
*/
-export function makeStore<Id extends string, S extends StateTree>(
- id: Id,
- buildState: () => S
-) {
- let store: Store<Id, S> | undefined
+export function makeStore<
+ Id extends string,
+ S extends StateTree,
+ G extends Record<string, StoreGetter<S>>
+>(id: Id, buildState: () => S, getters: G) {
+ let store: CombinedStore<Id, S, G> | undefined
- function useStore(): Store<Id, S> {
- if (!store) store = createStore(id, buildState)
+ function useStore(): CombinedStore<Id, S, G> {
+ if (!store) store = createStore(id, buildState, getters)
return store
}
}
// export const store = createStore('main', initialState)
-// export const cartStore = createStore('cart', {
+
+// type StateI = ReturnType<typeof buildState>
+// const buildState = () => ({
// items: ['thing 1'],
// })
+// export const cartStore = createStore('cart', buildState, {
+// amount: state => state.items.length,
+// })
+
+// cartStore.nonueo
+// cartStore.amount.value * 2
// store.patch({
// toggle: 'off',
+import { Ref } from '@vue/composition-api'
+
interface JSONSerializable {
toJSON(): string
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface StateTreeArray extends Array<StateTreeValue> {}
-// type TODO = any
+export interface StoreGetter<S extends StateTree, T = any> {
+ (state: S): T
+}
+
+type TODO = any
// type StoreMethod = TODO
export type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> }
// type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> }
state: S
) => void
+export type StoreGetters<
+ S extends StateTree,
+ G extends Record<string, StoreGetter<S>>
+> = {
+ [k in keyof G]: G[k] extends StoreGetter<S, infer V> ? Ref<V> : never
+}
+
export interface Store<Id extends string, S extends StateTree> {
/**
* Unique identifier of the store
* State of the Store
*/
state: S
+
/**
* Applies a state patch to current state. Allows passing nested values
* @param partialState patch to apply to the state