Skip to content

Commit

Permalink
feat: add support for overriding
Browse files Browse the repository at this point in the history
state, getters and actions can be overridden for multiple reasons, which are in the docs
  • Loading branch information
Rettend committed Jun 11, 2023
1 parent cb70f32 commit e751420
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 31 deletions.
44 changes: 44 additions & 0 deletions docs/guide/generic-stores.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,50 @@ function baseStore<T extends Category | Todo>() { // [!code ++]
}
```

## Overriding the generic store

The generic store's properties are passed as optionals when defining the store. This means that we can override them.

This is useful when you want to add a default value to the generic store's state.

```ts
export const useTodoStore = useStore<TodoStore, BaseStore<Todo>>(
'todo',
{
state: { // [!code ++]
all: [ // [!code ++]
{ id: 1, name: 'First Todo', done: false }, // [!code ++]
], // [!code ++]
}, // [!code ++]
actions: {
remove(id: number) {
this.all = this.all.filter(item => item.id !== id)
},
},
},
baseStore<Todo>(),
)
```

Or when you want to disable a getter or action.

```ts
export const useTodoStore = useStore<TodoStore, BaseStore<Todo>>(
'todo',
{
getters: { // [!code ++]
getName: undefined, // getName no longer shows up in the store // [!code ++]
}, // [!code ++]
actions: {
remove(id: number) {
this.all = this.all.filter(item => item.id !== id)
},
},
},
baseStore<Todo>(),
)
```

That's it!

The generic store can also be split up. See an example here: [Generic Examples](/examples/generic).
3 changes: 1 addition & 2 deletions playground/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<script setup lang="ts">
import { useCategoryStore, useTodoStore } from './store'
import { useCategoryStore } from './store'
const category = useCategoryStore()
const todo = useTodoStore()
</script>

<template>
Expand Down
62 changes: 38 additions & 24 deletions playground/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ type CategoryStore = PiniaStore<

const state = createState<CategoryStore, BaseStore<Category>>({
description: 'This is a category store',
all: [
{ id: 1, name: 'Laptops' },
],
})

const getters = createGetters<CategoryStore, BaseStore<Category>>({
getMaxId() {
return this.all.reduce((max, item) => Math.max(max, item.id), 0)
},
getLength: undefined,
})

const actions = createActions<CategoryStore, BaseStore<Category>>({
Expand All @@ -44,7 +48,6 @@ type BaseStore<T> = PiniaStore<
{
getLength(): number
getName(): string | undefined
isDone(): boolean | undefined
},
{
add(item: T): void
Expand Down Expand Up @@ -79,25 +82,21 @@ type BaseStore<T> = PiniaStore<

// splitting the generic store too

function baseState<T>() {
function baseState<T extends Category>() {
return createState<BaseStore<T>>({
current: null,
all: [],
})
}

function baseGetters<T extends Category | Todo>() {
function baseGetters<T extends Category>() {
return createGetters<BaseStore<T>>({
getLength() {
return this.all.length
},
getName() {
return this.current?.name
},
isDone() {
if (this.current && 'done' in this.current)
return this.current?.done
},
})
}

Expand All @@ -109,7 +108,7 @@ function baseActions<T>() {
})
}

function baseStore<T extends Category | Todo>() {
function baseStore<T extends Category>() {
return defineGenericStore<BaseStore<T>>({
state: baseState<T>(),
getters: baseGetters<T>(),
Expand All @@ -129,30 +128,45 @@ export const useCategoryStore = useStore<CategoryStore, BaseStore<Category>>(

// TODO: support options (persistedstate, etc.)

interface Todo {
interface Product {
id: number
name: string
done: boolean
price: number
}

type TodoStore = PiniaStore<
'todo',
{},
{},
type BaseStore1<T> = PiniaStore<
'base1',
{
remove(id: number): void
all: T[]
},
{
getTotal(): number
getMaxPrice(): number
},
BaseStore<Todo>
{
add(item: T): void
}
>

export const useTodoStore = useStore<TodoStore, BaseStore<Todo>>(
'todo',
{
function baseStore1<T extends Product>() {
return defineGenericStore<BaseStore1<T>>({
state: {
all: [
{ id: 1, name: 'Laptop', price: 1000 } as T,
],
},
getters: {
getTotal() {
return this.all.reduce((total, item) => total + item.price, 0)
},
getMaxPrice() {
return this.all.reduce((max, item) => Math.max(max, item.price), 0)
},
},
actions: {
remove(id: number) {
this.all = this.all.filter(item => item.id !== id)
add(item: T) {
this.all.push(item)
},
},
},
baseStore<Todo>(),
)
})
}
35 changes: 34 additions & 1 deletion src/generic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { ExtractStore, PiniaActionThis, PiniaGetterThis, StoreThis } from '
export function createState<
TStore extends Store, TGenericStore extends Store = Store,
>(
state: Omit<ExtractStore<TStore>['state'], keyof ExtractStore<TGenericStore>['state']>,
state: Omit<ExtractStore<TStore>['state'], keyof ExtractStore<TGenericStore>['state']> & Partial<ExtractStore<TGenericStore>['state']>,
): ExtractStore<TStore>['state'] {
return state
}
Expand Down Expand Up @@ -66,6 +66,34 @@ export function defineGenericStore<
}
// #endregion defineGenericStore

function filterUndefined<T extends Record<string, any>>(obj: T, undefinedProps: Set<string>): T {
const result = Object.keys (obj).reduce ((result, key) => {
const value = obj[key]
if (value !== undefined) {
if (typeof value === 'object' && value !== obj && value !== null) {
for (const subKey of Object.keys(value)) {
if (value[subKey] === undefined)
undefinedProps.add (subKey)
else
result[key as keyof T] = value
}
}
}
return result
}, {} as T)

for (const prop of undefinedProps) {
for (const key of Object.keys(result)) {
for (const subKey of Object.keys(result[key])) {
if (subKey === prop)
delete result[key][subKey]
}
}
}

return result
}

// #region useStore
/**
* Defines a store. Can extend a generic store.
Expand All @@ -82,6 +110,11 @@ export function useStore<
store: StoreThis<TStore, TGenericStore>,
genericStore: StoreThis<TGenericStore> = {},
) {
const undefinedProps = new Set<string>()

store = filterUndefined(store, undefinedProps)
genericStore = filterUndefined(genericStore, undefinedProps)

return defineStore(id, {
state: () => ({
...genericStore.state,
Expand Down
11 changes: 7 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ type PiniaActionsTree = Record<string | number | symbol, (...args: any[]) => any
export type PiniaGetterThis<TStore extends Store, TGenericStore extends Store = TStore> = {
[K in keyof Omit<ExtractStore<TStore>['getters'], keyof ExtractStore<TGenericStore>['getters']>]:
(this: TStore) => ReturnType<ExtractStore<TStore>['getters'][K]>
} & {
[K in keyof Partial<ExtractStore<TGenericStore>['getters']>]:
((this: TStore) => ReturnType<ExtractStore<TGenericStore>['getters'][K]>) | undefined
}
// #endregion PiniaGetterThis

Expand All @@ -24,9 +27,9 @@ export type PiniaGetterThis<TStore extends Store, TGenericStore extends Store =
*/
export type PiniaActionThis<TStore extends Store, TGenericStore extends Store = TStore> = {
[K in keyof Omit<ExtractStore<TStore>['actions'], keyof ExtractStore<TGenericStore>['actions']>]:
(this: TStore, ...args: Parameters<ExtractStore<TStore>['actions'][K]>)
=> ReturnType<ExtractStore<TStore>['actions'][K]>
}
((this: TStore, ...args: Parameters<ExtractStore<TStore>['actions'][K]>)
=> ReturnType<ExtractStore<TStore>['actions'][K]>) | undefined
} & Partial<ExtractStore<TGenericStore>['actions']>
// #endregion PiniaActionThis

// #region StoreThis
Expand All @@ -36,7 +39,7 @@ export type PiniaActionThis<TStore extends Store, TGenericStore extends Store =
* @internal
*/
export interface StoreThis<TStore extends Store, TGenericStore extends Store = Store> {
state?: Omit<ExtractStore<TStore>['state'], keyof ExtractStore<TGenericStore>['state']>
state?: Omit<ExtractStore<TStore>['state'], keyof ExtractStore<TGenericStore>['state']> & Partial<ExtractStore<TGenericStore>['state']>
getters?: PiniaGetterThis<TStore, TGenericStore>
actions?: PiniaActionThis<TStore, TGenericStore>
}
Expand Down

0 comments on commit e751420

Please sign in to comment.