Skip to content

Commit 5745ce8

Browse files
committed
feat: allow strict option
Close #58
1 parent e8f4b0e commit 5745ce8

File tree

6 files changed

+83
-23
lines changed

6 files changed

+83
-23
lines changed

__tests__/strictMode.spec.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { createPinia, defineStore, setActivePinia } from '../src'
2+
3+
describe('Strict mode', () => {
4+
const useStore = defineStore({
5+
id: 'main',
6+
strict: true,
7+
state: () => ({
8+
a: true,
9+
nested: {
10+
foo: 'foo',
11+
a: { b: 'string' },
12+
},
13+
}),
14+
})
15+
16+
it('cannot change the state directly', () => {
17+
setActivePinia(createPinia())
18+
const store = useStore()
19+
// @ts-expect-error
20+
store.a = false
21+
// @ts-expect-error
22+
store.nested.foo = 'bar'
23+
24+
// TODO: should direct $state be allowed?
25+
// this could be an escape hatch if we want one
26+
store.$state.a = false
27+
28+
store.$patch({ a: false })
29+
store.$patch({ nested: { foo: 'bar' } })
30+
})
31+
})

src/rootStore.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ export interface PiniaPluginContext<
120120
Id extends string = string,
121121
S extends StateTree = StateTree,
122122
G extends GettersTree<S> = GettersTree<S>,
123-
A /* extends ActionsTree */ = ActionsTree
123+
A /* extends ActionsTree */ = ActionsTree,
124+
Strict extends boolean = false
124125
> {
125126
/**
126127
* pinia instance.
@@ -140,7 +141,7 @@ export interface PiniaPluginContext<
140141
/**
141142
* Current store being extended.
142143
*/
143-
options: DefineStoreOptions<Id, S, G, A>
144+
options: DefineStoreOptions<Id, S, G, A, Strict>
144145
}
145146

146147
/**

src/store.ts

+16-10
Original file line numberDiff line numberDiff line change
@@ -276,14 +276,15 @@ function buildStoreToUse<
276276
Id extends string,
277277
S extends StateTree,
278278
G extends GettersTree<S>,
279-
A extends ActionsTree
279+
A extends ActionsTree,
280+
Strict extends boolean
280281
>(
281282
partialStore: StoreWithState<Id, S, G, A>,
282283
descriptor: StateDescriptor<S>,
283284
$id: Id,
284285
getters: G = {} as G,
285286
actions: A = {} as A,
286-
options: DefineStoreOptions<Id, S, G, A>
287+
options: DefineStoreOptions<Id, S, G, A, Strict>
287288
) {
288289
const pinia = getActivePinia()
289290

@@ -337,7 +338,7 @@ function buildStoreToUse<
337338
} as StoreWithActions<A>[typeof actionName]
338339
}
339340

340-
const store: Store<Id, S, G, A> = reactive(
341+
const store: Store<Id, S, G, A, Strict> = reactive(
341342
assign(
342343
{},
343344
partialStore,
@@ -346,7 +347,7 @@ function buildStoreToUse<
346347
computedGetters,
347348
wrappedActions
348349
)
349-
) as Store<Id, S, G, A>
350+
) as Store<Id, S, G, A, Strict>
350351

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

382-
function useStore(pinia?: Pinia | null): Store<Id, S, G, A> {
386+
function useStore(pinia?: Pinia | null): Store<Id, S, G, A, Strict> {
383387
const hasInstance = getCurrentInstance()
384388
// only run provide when pinia hasn't been manually passed
385389
const shouldProvide = hasInstance && !pinia
@@ -395,7 +399,7 @@ export function defineStore<
395399
| [
396400
StoreWithState<Id, S, G, A>,
397401
StateDescriptor<S>,
398-
InjectionKey<Store<Id, S, G, A>>
402+
InjectionKey<Store<Id, S, G, A, Strict>>
399403
]
400404
| undefined
401405
if (!storeAndDescriptor) {
@@ -409,7 +413,8 @@ export function defineStore<
409413
S,
410414
G,
411415
// @ts-expect-error: A without extends
412-
A
416+
A,
417+
Strict
413418
>(
414419
storeAndDescriptor[0],
415420
storeAndDescriptor[1],
@@ -436,7 +441,8 @@ export function defineStore<
436441
S,
437442
G,
438443
// @ts-expect-error: A without extends
439-
A
444+
A,
445+
Strict
440446
>(
441447
storeAndDescriptor[0],
442448
storeAndDescriptor[1],

src/types.ts

+27-8
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,15 @@ export function isPlainObject(
2626
)
2727
}
2828

29+
/**
30+
* @internal
31+
*/
2932
export type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> }
30-
// type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> }
33+
34+
/**
35+
* @internal
36+
*/
37+
export type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> }
3138

3239
/**
3340
* Possible types for SubscriptionCallback
@@ -142,6 +149,7 @@ export interface StoreWithState<
142149
S extends StateTree,
143150
G extends GettersTree<S> = GettersTree<S>,
144151
A /* extends ActionsTree */ = ActionsTree
152+
// Strict extends boolean = false
145153
> {
146154
/**
147155
* Unique identifier of the store
@@ -300,9 +308,10 @@ export type Store<
300308
S extends StateTree = StateTree,
301309
G extends GettersTree<S> = GettersTree<S>,
302310
// has the actions without the context (this) for typings
303-
A /* extends ActionsTree */ = ActionsTree
311+
A /* extends ActionsTree */ = ActionsTree,
312+
Strict extends boolean = false
304313
> = StoreWithState<Id, S, G, A> &
305-
UnwrapRef<S> &
314+
(false extends Strict ? UnwrapRef<S> : DeepReadonly<UnwrapRef<S>>) &
306315
StoreWithGetters<G> &
307316
StoreWithActions<A> &
308317
PiniaCustomProperties<Id, S, G, A>
@@ -314,14 +323,15 @@ export interface StoreDefinition<
314323
Id extends string = string,
315324
S extends StateTree = StateTree,
316325
G extends GettersTree<S> = GettersTree<S>,
317-
A /* extends ActionsTree */ = ActionsTree
326+
A /* extends ActionsTree */ = ActionsTree,
327+
Strict extends boolean = false
318328
> {
319329
/**
320330
* Returns a store, creates it if necessary.
321331
*
322332
* @param pinia - Pinia instance to retrieve the store
323333
*/
324-
(pinia?: Pinia | null | undefined): Store<Id, S, G, A>
334+
(pinia?: Pinia | null | undefined): Store<Id, S, G, A, Strict>
325335

326336
/**
327337
* Id of the store. Used by map helpers.
@@ -342,7 +352,8 @@ export interface PiniaCustomProperties<
342352
Id extends string = string,
343353
S extends StateTree = StateTree,
344354
G extends GettersTree<S> = GettersTree<S>,
345-
A /* extends ActionsTree */ = ActionsTree
355+
A /* extends ActionsTree */ = ActionsTree,
356+
Strict extends boolean = false
346357
> {}
347358

348359
/**
@@ -370,21 +381,29 @@ export interface DefineStoreOptions<
370381
Id extends string,
371382
S extends StateTree,
372383
G extends GettersTree<S>,
373-
A /* extends Record<string, StoreAction> */
384+
A /* extends Record<string, StoreAction> */,
385+
Strict extends boolean
374386
> {
375387
/**
376388
* Unique string key to identify the store across the application.
377389
*/
378390
id: Id
391+
392+
strict?: Strict
393+
379394
/**
380395
* Function to create a fresh state.
381396
*/
382397
state?: () => S
398+
383399
/**
384400
* Optional object of getters.
385401
*/
386402
getters?: G &
387-
ThisType<UnwrapRef<S> & StoreWithGetters<G> & PiniaCustomProperties>
403+
ThisType<
404+
DeepReadonly<UnwrapRef<S>> & StoreWithGetters<G> & PiniaCustomProperties
405+
>
406+
388407
/**
389408
* Optional object of actions.
390409
*/

test-dts/customizations.test-d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ declare module '../dist/pinia' {
77
suffix: 'Store'
88
}
99

10-
export interface PiniaCustomProperties<Id, S, G, A> {
10+
export interface PiniaCustomProperties<Id, S, G, A, Strict> {
1111
$actions: Array<keyof A>
1212
}
1313

14-
export interface DefineStoreOptions<Id, S, G, A> {
14+
export interface DefineStoreOptions<Id, S, G, A, Strict> {
1515
debounce?: {
1616
// Record<keyof A, number>
1717
[k in keyof A]?: number

test-dts/plugins.test-d.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
expectType,
44
createPinia,
55
GenericStore,
6+
Store,
67
Pinia,
78
StateTree,
89
DefineStoreOptions,
@@ -12,14 +13,16 @@ const pinia = createPinia()
1213

1314
pinia.use(({ store, app, options, pinia }) => {
1415
expectType<GenericStore>(store)
16+
expectType<Store>(store)
1517
expectType<Pinia>(pinia)
1618
expectType<App>(app)
1719
expectType<
1820
DefineStoreOptions<
1921
string,
2022
StateTree,
2123
Record<string, any>,
22-
Record<string, any>
24+
Record<string, any>,
25+
boolean
2326
>
2427
>(options)
2528
})

0 commit comments

Comments
 (0)