Skip to content

Commit

Permalink
feat: allow strict option
Browse files Browse the repository at this point in the history
Close #58
  • Loading branch information
posva committed May 12, 2021
1 parent e8f4b0e commit 5745ce8
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 23 deletions.
31 changes: 31 additions & 0 deletions __tests__/strictMode.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createPinia, defineStore, setActivePinia } from '../src'

describe('Strict mode', () => {
const useStore = defineStore({
id: 'main',
strict: true,
state: () => ({
a: true,
nested: {
foo: 'foo',
a: { b: 'string' },
},
}),
})

it('cannot change the state directly', () => {
setActivePinia(createPinia())
const store = useStore()
// @ts-expect-error
store.a = false
// @ts-expect-error
store.nested.foo = 'bar'

// TODO: should direct $state be allowed?
// this could be an escape hatch if we want one
store.$state.a = false

store.$patch({ a: false })
store.$patch({ nested: { foo: 'bar' } })
})
})
5 changes: 3 additions & 2 deletions src/rootStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ export interface PiniaPluginContext<
Id extends string = string,
S extends StateTree = StateTree,
G extends GettersTree<S> = GettersTree<S>,
A /* extends ActionsTree */ = ActionsTree
A /* extends ActionsTree */ = ActionsTree,
Strict extends boolean = false
> {
/**
* pinia instance.
Expand All @@ -140,7 +141,7 @@ export interface PiniaPluginContext<
/**
* Current store being extended.
*/
options: DefineStoreOptions<Id, S, G, A>
options: DefineStoreOptions<Id, S, G, A, Strict>
}

/**
Expand Down
26 changes: 16 additions & 10 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,14 +276,15 @@ function buildStoreToUse<
Id extends string,
S extends StateTree,
G extends GettersTree<S>,
A extends ActionsTree
A extends ActionsTree,
Strict extends boolean
>(
partialStore: StoreWithState<Id, S, G, A>,
descriptor: StateDescriptor<S>,
$id: Id,
getters: G = {} as G,
actions: A = {} as A,
options: DefineStoreOptions<Id, S, G, A>
options: DefineStoreOptions<Id, S, G, A, Strict>
) {
const pinia = getActivePinia()

Expand Down Expand Up @@ -337,7 +338,7 @@ function buildStoreToUse<
} as StoreWithActions<A>[typeof actionName]
}

const store: Store<Id, S, G, A> = reactive(
const store: Store<Id, S, G, A, Strict> = reactive(
assign(
{},
partialStore,
Expand All @@ -346,7 +347,7 @@ function buildStoreToUse<
computedGetters,
wrappedActions
)
) as Store<Id, S, G, A>
) as Store<Id, S, G, A, Strict>

// use this instead of a computed with setter to be able to create it anywhere
// without linking the computed lifespan to wherever the store is first
Expand Down Expand Up @@ -375,11 +376,14 @@ export function defineStore<
S extends StateTree,
G extends GettersTree<S>,
// cannot extends ActionsTree because we loose the typings
A /* extends ActionsTree */
>(options: DefineStoreOptions<Id, S, G, A>): StoreDefinition<Id, S, G, A> {
A /* extends ActionsTree */,
Strict extends boolean
>(
options: DefineStoreOptions<Id, S, G, A, Strict>
): StoreDefinition<Id, S, G, A, Strict> {
const { id, state, getters, actions } = options

function useStore(pinia?: Pinia | null): Store<Id, S, G, A> {
function useStore(pinia?: Pinia | null): Store<Id, S, G, A, Strict> {
const hasInstance = getCurrentInstance()
// only run provide when pinia hasn't been manually passed
const shouldProvide = hasInstance && !pinia
Expand All @@ -395,7 +399,7 @@ export function defineStore<
| [
StoreWithState<Id, S, G, A>,
StateDescriptor<S>,
InjectionKey<Store<Id, S, G, A>>
InjectionKey<Store<Id, S, G, A, Strict>>
]
| undefined
if (!storeAndDescriptor) {
Expand All @@ -409,7 +413,8 @@ export function defineStore<
S,
G,
// @ts-expect-error: A without extends
A
A,
Strict
>(
storeAndDescriptor[0],
storeAndDescriptor[1],
Expand All @@ -436,7 +441,8 @@ export function defineStore<
S,
G,
// @ts-expect-error: A without extends
A
A,
Strict
>(
storeAndDescriptor[0],
storeAndDescriptor[1],
Expand Down
35 changes: 27 additions & 8 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,15 @@ export function isPlainObject(
)
}

/**
* @internal
*/
export type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> }
// type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> }

/**
* @internal
*/
export type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> }

/**
* Possible types for SubscriptionCallback
Expand Down Expand Up @@ -142,6 +149,7 @@ export interface StoreWithState<
S extends StateTree,
G extends GettersTree<S> = GettersTree<S>,
A /* extends ActionsTree */ = ActionsTree
// Strict extends boolean = false
> {
/**
* Unique identifier of the store
Expand Down Expand Up @@ -300,9 +308,10 @@ export type Store<
S extends StateTree = StateTree,
G extends GettersTree<S> = GettersTree<S>,
// has the actions without the context (this) for typings
A /* extends ActionsTree */ = ActionsTree
A /* extends ActionsTree */ = ActionsTree,
Strict extends boolean = false
> = StoreWithState<Id, S, G, A> &
UnwrapRef<S> &
(false extends Strict ? UnwrapRef<S> : DeepReadonly<UnwrapRef<S>>) &
StoreWithGetters<G> &
StoreWithActions<A> &
PiniaCustomProperties<Id, S, G, A>
Expand All @@ -314,14 +323,15 @@ export interface StoreDefinition<
Id extends string = string,
S extends StateTree = StateTree,
G extends GettersTree<S> = GettersTree<S>,
A /* extends ActionsTree */ = ActionsTree
A /* extends ActionsTree */ = ActionsTree,
Strict extends boolean = false
> {
/**
* Returns a store, creates it if necessary.
*
* @param pinia - Pinia instance to retrieve the store
*/
(pinia?: Pinia | null | undefined): Store<Id, S, G, A>
(pinia?: Pinia | null | undefined): Store<Id, S, G, A, Strict>

/**
* Id of the store. Used by map helpers.
Expand All @@ -342,7 +352,8 @@ export interface PiniaCustomProperties<
Id extends string = string,
S extends StateTree = StateTree,
G extends GettersTree<S> = GettersTree<S>,
A /* extends ActionsTree */ = ActionsTree
A /* extends ActionsTree */ = ActionsTree,
Strict extends boolean = false
> {}

/**
Expand Down Expand Up @@ -370,21 +381,29 @@ export interface DefineStoreOptions<
Id extends string,
S extends StateTree,
G extends GettersTree<S>,
A /* extends Record<string, StoreAction> */
A /* extends Record<string, StoreAction> */,
Strict extends boolean
> {
/**
* Unique string key to identify the store across the application.
*/
id: Id

strict?: Strict

/**
* Function to create a fresh state.
*/
state?: () => S

/**
* Optional object of getters.
*/
getters?: G &
ThisType<UnwrapRef<S> & StoreWithGetters<G> & PiniaCustomProperties>
ThisType<
DeepReadonly<UnwrapRef<S>> & StoreWithGetters<G> & PiniaCustomProperties
>

/**
* Optional object of actions.
*/
Expand Down
4 changes: 2 additions & 2 deletions test-dts/customizations.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ declare module '../dist/pinia' {
suffix: 'Store'
}

export interface PiniaCustomProperties<Id, S, G, A> {
export interface PiniaCustomProperties<Id, S, G, A, Strict> {
$actions: Array<keyof A>
}

export interface DefineStoreOptions<Id, S, G, A> {
export interface DefineStoreOptions<Id, S, G, A, Strict> {
debounce?: {
// Record<keyof A, number>
[k in keyof A]?: number
Expand Down
5 changes: 4 additions & 1 deletion test-dts/plugins.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
expectType,
createPinia,
GenericStore,
Store,
Pinia,
StateTree,
DefineStoreOptions,
Expand All @@ -12,14 +13,16 @@ const pinia = createPinia()

pinia.use(({ store, app, options, pinia }) => {
expectType<GenericStore>(store)
expectType<Store>(store)
expectType<Pinia>(pinia)
expectType<App>(app)
expectType<
DefineStoreOptions<
string,
StateTree,
Record<string, any>,
Record<string, any>
Record<string, any>,
boolean
>
>(options)
})

0 comments on commit 5745ce8

Please sign in to comment.