--- /dev/null
+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' } })
+ })
+})
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.
/**
* Current store being extended.
*/
- options: DefineStoreOptions<Id, S, G, A>
+ options: DefineStoreOptions<Id, S, G, A, Strict>
}
/**
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()
} as StoreWithActions<A>[typeof actionName]
}
- const store: Store<Id, S, G, A> = reactive(
+ const store: Store<Id, S, G, A, Strict> = reactive(
assign(
{},
partialStore,
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
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
| [
StoreWithState<Id, S, G, A>,
StateDescriptor<S>,
- InjectionKey<Store<Id, S, G, A>>
+ InjectionKey<Store<Id, S, G, A, Strict>>
]
| undefined
if (!storeAndDescriptor) {
S,
G,
// @ts-expect-error: A without extends
- A
+ A,
+ Strict
>(
storeAndDescriptor[0],
storeAndDescriptor[1],
S,
G,
// @ts-expect-error: A without extends
- A
+ A,
+ Strict
>(
storeAndDescriptor[0],
storeAndDescriptor[1],
)
}
+/**
+ * @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
S extends StateTree,
G extends GettersTree<S> = GettersTree<S>,
A /* extends ActionsTree */ = ActionsTree
+ // Strict extends boolean = false
> {
/**
* Unique identifier of the 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>
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.
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
> {}
/**
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.
*/
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
expectType,
createPinia,
GenericStore,
+ Store,
Pinia,
StateTree,
DefineStoreOptions,
pinia.use(({ store, app, options, pinia }) => {
expectType<GenericStore>(store)
+ expectType<Store>(store)
expectType<Pinia>(pinia)
expectType<App>(app)
expectType<
string,
StateTree,
Record<string, any>,
- Record<string, any>
+ Record<string, any>,
+ boolean
>
>(options)
})