import { createStore } from '../src'
describe('Store', () => {
- function buildStore() {
- return createStore(
- 'main',
- () => ({
- name: 'Eduardo',
- }),
- {
- upperCaseName: ({ name }) => name.toUpperCase(),
- }
- )
- }
+ const useStore = createStore(
+ 'main',
+ () => ({
+ name: 'Eduardo',
+ }),
+ {
+ upperCaseName: ({ name }) => name.toUpperCase(),
+ }
+ ).bind(null, true)
it('adds getters to the store', () => {
- const store = buildStore()
+ const store = useStore()
expect(store.upperCaseName.value).toBe('EDUARDO')
store.state.name = 'Ed'
expect(store.upperCaseName.value).toBe('ED')
})
it('updates the value', () => {
- const store = buildStore()
+ const store = useStore()
store.state.name = 'Ed'
expect(store.upperCaseName.value).toBe('ED')
})
-import { makeStore } from '../../../src'
-import { userStore } from './user'
+import { createStore } from '../../../src'
+import { useUserStore } from './user'
-export const cartStore = makeStore(
+export const useCartStore = createStore(
'cart',
() => ({
rawItems: [] as string[],
)
export function addItem(name: string) {
- const store = cartStore.useStore()
+ const store = useCartStore()
store.state.rawItems.push(name)
}
export function removeItem(name: string) {
- const store = cartStore.useStore()
+ const store = useCartStore()
const i = store.state.rawItems.indexOf(name)
if (i > -1) store.state.rawItems.splice(i, 1)
}
export async function purchaseItems() {
- const cart = cartStore.useStore()
- const user = userStore.useStore()
+ const cart = useCartStore()
+ const user = useUserStore()
if (!user.state.name) return
console.log('Purchasing', cart.items.value)
-import { makeStore } from '../../../src'
+import { createStore } from '../../../src'
-export const userStore = makeStore(
+export const useUserStore = createStore(
'user',
() => ({
name: 'Eduardo',
)
export function logout() {
- const store = userStore.useStore()
+ const store = useUserStore()
store.patch({
name: '',
}
export async function login(user: string, password: string) {
- const store = userStore.useStore()
+ const store = useUserStore()
const userData = await apiLogin(user, password)
store.patch({
import Vue from 'vue'
// import VueCompositionApi from '@vue/composition-api'
import App from './App'
-import { useStore, clear } from './store'
+import { useStore } from './store'
// Done in setup.ts
// Vue.use(VueCompositionApi)
export function createApp() {
// create router and store instances
- const store = useStore()
+ const store = useStore(true)
store.reset()
store.state.counter++
-import { makeStore } from '../../../src'
+import { createStore } from '../../../src'
-export const { useStore, clear } = makeStore('main', () => ({
+export const useStore = createStore('main', () => ({
counter: 0,
name: 'anon',
}))
import { createStore } from '../src'
describe('store.patch', () => {
- function buildStore() {
- return createStore('main', () => ({
- // TODO: the boolean cas shouldn't be necessary
- // https://www.typescriptlang.org/play/#code/MYewdgzgLgBCMF4YG8CwAoGWYEMBcMUATgK4CmGAvhhiAHQ6IwBmOANhBehqJLMETI4oZJgAoAlIgB8MMclwFi5GJQk10vaDGBMBQkZI3AGTVhzJA
- a: true as boolean,
- nested: {
- foo: 'foo',
- a: { b: 'string' },
- },
- }))
- }
+ const useStore = createStore('main', () => ({
+ // TODO: the boolean cas shouldn't be necessary
+ // https://www.typescriptlang.org/play/#code/MYewdgzgLgBCMF4YG8CwAoGWYEMBcMUATgK4CmGAvhhiAHQ6IwBmOANhBehqJLMETI4oZJgAoAlIgB8MMclwFi5GJQk10vaDGBMBQkZI3AGTVhzJA
+ a: true as boolean,
+ nested: {
+ foo: 'foo',
+ a: { b: 'string' },
+ },
+ })).bind(null, true) // force always a fresh instance
it('patches a property without touching the rest', () => {
- const store = buildStore()
+ const store = useStore()
store.patch({ a: false })
expect(store.state).toEqual({
a: false,
})
it('patches a nested property without touching the rest', () => {
- const store = buildStore()
+ const store = useStore()
store.patch({ nested: { foo: 'bar' } })
expect(store.state).toEqual({
a: true,
})
it('patches multiple properties at the same time', () => {
- const store = buildStore()
+ const store = useStore()
store.patch({ a: false, nested: { foo: 'hello' } })
expect(store.state).toEqual({
a: false,
import { createStore } from '../src'
describe('Store', () => {
- function buildStore() {
- return createStore('main', () => ({
- a: true as boolean,
- nested: {
- foo: 'foo',
- a: { b: 'string' },
- },
- }))
- }
+ const useStore = createStore('main', () => ({
+ // TODO: the boolean cas shouldn't be necessary
+ // https://www.typescriptlang.org/play/#code/MYewdgzgLgBCMF4YG8CwAoGWYEMBcMUATgK4CmGAvhhiAHQ6IwBmOANhBehqJLMETI4oZJgAoAlIgB8MMclwFi5GJQk10vaDGBMBQkZI3AGTVhzJA
+ a: true as boolean,
+ nested: {
+ foo: 'foo',
+ a: { b: 'string' },
+ },
+ })).bind(null, true) // force always a fresh instance
it('sets the initial state', () => {
- const store = buildStore()
+ const store = useStore()
expect(store.state).toEqual({
a: true,
nested: {
})
it('can replace its state', () => {
- const store = buildStore()
+ const store = useStore()
store.state = {
a: false,
nested: {
})
it('do not share the state between same id store', () => {
- const store = buildStore()
- const store2 = buildStore()
+ const store = useStore()
+ const store2 = useStore()
expect(store.state).not.toBe(store2.state)
store.state.nested.a.b = 'hey'
expect(store2.state.nested.a.b).toBe('string')
import { createStore } from '../../src'
import { expectType, expectError } from 'tsd'
-const store = createStore('name', () => ({ a: 'on' as 'on' | 'off' }), {
+const useStore = createStore('name', () => ({ a: 'on' as 'on' | 'off' }), {
upper: state => state.a.toUpperCase(),
})
+const store = useStore()
+
expectType<{ a: 'on' | 'off' }>(store.state)
expectError(() => store.nonExistant)
terser({
module: format === 'es',
output: {
- preamble: banner,
+ // comments: false,
+ // already added by rollup
+ // only necessary if removing others
+ // preamble: banner,
},
})
)
-import { ref, watch, computed } from '@vue/composition-api'
-import { Ref } from '@vue/composition-api/dist/reactivity'
-import {
- StateTree,
- Store,
- SubscriptionCallback,
- DeepPartial,
- isPlainObject,
- StoreGetters,
- StoreGetter,
-} from './types'
-import { devtoolPlugin } from './devtools'
-
-function innerPatch<T extends StateTree>(
- target: T,
- patchToApply: DeepPartial<T>
-): T {
- // TODO: get all keys
- for (const key in patchToApply) {
- const subPatch = patchToApply[key]
- const targetValue = target[key]
- if (isPlainObject(targetValue) && isPlainObject(subPatch)) {
- target[key] = innerPatch(targetValue, subPatch)
- } else {
- // @ts-ignore
- target[key] = subPatch
- }
- }
-
- return target
-}
-
-/**
- * NOTE: by allowing users to name stores correctly, they can nest them the way
- * 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,
- G extends Record<string, StoreGetter<S>>
->(
- id: Id,
- buildState: () => S,
- getters: G = {} as G
- // methods: Record<string | symbol, StoreMethod>
-): CombinedStore<Id, S, G> {
- const state: Ref<S> = ref(buildState())
-
- let isListening = true
- let subscriptions: SubscriptionCallback<S>[] = []
-
- watch(
- () => state.value,
- state => {
- if (isListening) {
- subscriptions.forEach(callback => {
- callback({ storeName: id, type: '🧩 in place', payload: {} }, state)
- })
- }
- },
- {
- deep: true,
- flush: 'sync',
- }
- )
-
- function patch(partialState: DeepPartial<S>): void {
- isListening = false
- innerPatch(state.value, partialState)
- isListening = true
- subscriptions.forEach(callback => {
- callback(
- { storeName: id, type: '⤵️ patch', payload: partialState },
- state.value
- )
- })
- }
-
- function subscribe(callback: SubscriptionCallback<S>): void {
- subscriptions.push(callback)
- // TODO: return function to remove subscription
- }
-
- function reset() {
- subscriptions = []
- state.value = buildState()
- }
-
- const storeWithState: Store<Id, S> = {
- id,
- // it is replaced below by a getter
- state: state.value,
-
- patch,
- subscribe,
- reset,
- }
-
- // @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,
- set: (newState: S) => {
- isListening = false
- state.value = newState
- isListening = true
- },
- })
-
- // Devtools injection hue hue
- devtoolPlugin(store)
-
- return store
-}
-
-/**
- * The api needs more work we must be able to use the store easily in any
- * function by calling `useStore` to get the store Instance and we also need to
- * be able to reset the store instance between requests on the server
- */
-
-/**
- *
- * @param id id of the store we are creating
- * @param buildState function that returns a state
- */
-
-export function makeStore<
- Id extends string,
- S extends StateTree,
- G extends Record<string, StoreGetter<S>>
->(id: Id, buildState: () => S, getters: G = {} as G) {
- let store: CombinedStore<Id, S, G> | undefined
-
- function useStore(): CombinedStore<Id, S, G> {
- if (!store) store = createStore(id, buildState, getters)
-
- return store
- }
-
- function clear(): void {
- store = undefined
- }
-
- return {
- useStore,
- clear,
- }
-}
-
-// export const store = createStore('main', initialState)
-
-// 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',
-// nested: {
-// a: {
-// b: {
-// c: 'one',
-// },
-// },
-// },
-// })
+export { createStore } from './store'
--- /dev/null
+import { ref, watch, computed } from '@vue/composition-api'
+import { Ref } from '@vue/composition-api/dist/reactivity'
+import {
+ StateTree,
+ Store,
+ SubscriptionCallback,
+ DeepPartial,
+ isPlainObject,
+ StoreGetters,
+ StoreGetter,
+} from './types'
+import { devtoolPlugin } from './devtools'
+
+function innerPatch<T extends StateTree>(
+ target: T,
+ patchToApply: DeepPartial<T>
+): T {
+ // TODO: get all keys like symbols as well
+ for (const key in patchToApply) {
+ const subPatch = patchToApply[key]
+ const targetValue = target[key]
+ if (isPlainObject(targetValue) && isPlainObject(subPatch)) {
+ target[key] = innerPatch(targetValue, subPatch)
+ } else {
+ // @ts-ignore
+ target[key] = subPatch
+ }
+ }
+
+ return target
+}
+
+/**
+ * NOTE: by allowing users to name stores correctly, they can nest them the way
+ * 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 buildStore<
+ Id extends string,
+ S extends StateTree,
+ G extends Record<string, StoreGetter<S>>
+>(
+ id: Id,
+ buildState: () => S,
+ getters: G = {} as G
+ // methods: Record<string | symbol, StoreMethod>
+): CombinedStore<Id, S, G> {
+ const state: Ref<S> = ref(buildState())
+
+ let isListening = true
+ let subscriptions: SubscriptionCallback<S>[] = []
+
+ watch(
+ () => state.value,
+ state => {
+ if (isListening) {
+ subscriptions.forEach(callback => {
+ callback({ storeName: id, type: '🧩 in place', payload: {} }, state)
+ })
+ }
+ },
+ {
+ deep: true,
+ flush: 'sync',
+ }
+ )
+
+ function patch(partialState: DeepPartial<S>): void {
+ isListening = false
+ innerPatch(state.value, partialState)
+ isListening = true
+ subscriptions.forEach(callback => {
+ callback(
+ { storeName: id, type: '⤵️ patch', payload: partialState },
+ state.value
+ )
+ })
+ }
+
+ function subscribe(callback: SubscriptionCallback<S>): void {
+ subscriptions.push(callback)
+ // TODO: return function to remove subscription
+ }
+
+ function reset() {
+ subscriptions = []
+ state.value = buildState()
+ }
+
+ const storeWithState: Store<Id, S> = {
+ id,
+ // it is replaced below by a getter
+ state: state.value,
+
+ patch,
+ subscribe,
+ reset,
+ }
+
+ // @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,
+ set: (newState: S) => {
+ isListening = false
+ state.value = newState
+ isListening = true
+ },
+ })
+
+ // Devtools injection hue hue
+ devtoolPlugin(store)
+
+ return store
+}
+
+/**
+ * The api needs more work we must be able to use the store easily in any
+ * function by calling `useStore` to get the store Instance and we also need to
+ * be able to reset the store instance between requests on the server
+ */
+
+/**
+ * Creates a `useStore` function that retrieves the store instance
+ * @param id id of the store we are creating
+ * @param buildState function that returns a state
+ * @param getters optional object of getters
+ */
+
+export function createStore<
+ Id extends string,
+ S extends StateTree,
+ G extends Record<string, StoreGetter<S>>
+>(id: Id, buildState: () => S, getters: G = {} as G) {
+ let store: CombinedStore<Id, S, G> | undefined
+
+ return function useStore(forceNewStore = false): CombinedStore<Id, S, G> {
+ if (!store || forceNewStore) store = buildStore(id, buildState, getters)
+
+ return store
+ }
+}