Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow strict option #493

Open
wants to merge 1 commit into
base: v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
})