@codeleap/store 4.3.3 → 4.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/package.json.bak +1 -1
- package/src/array.ts +26 -0
- package/src/globalState.ts +18 -1
- package/src/index.ts +1 -1
- package/src/tests/globalState.spec.ts +69 -0
- package/src/tests/slice.spec.ts +48 -0
- package/src/types.ts +8 -3
- package/src/utils.ts +36 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codeleap/store",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.5",
|
|
4
4
|
"main": "src/index.ts",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"directory": "packages/store"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@codeleap/config": "4.3.
|
|
12
|
+
"@codeleap/config": "4.3.5",
|
|
13
13
|
"ts-node-dev": "1.1.8"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
package/package.json.bak
CHANGED
package/src/array.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { WritableAtom } from "nanostores"
|
|
2
|
+
|
|
3
|
+
export function arrayHandler<T extends any[]>(store: WritableAtom<T>) {
|
|
4
|
+
return new Proxy([], {
|
|
5
|
+
get(target, p, receiver){
|
|
6
|
+
const val = store.get()
|
|
7
|
+
|
|
8
|
+
const property = val[p]
|
|
9
|
+
|
|
10
|
+
if(typeof property == 'function') {
|
|
11
|
+
return (...args) => {
|
|
12
|
+
const r = val[p](...args)
|
|
13
|
+
|
|
14
|
+
store.set(val)
|
|
15
|
+
|
|
16
|
+
return r
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return property
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export const arrayOps = Object.getOwnPropertyNames(Array.prototype)
|
package/src/globalState.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useStore } from '@nanostores/react'
|
|
2
2
|
import { setPersistentEngine, persistentAtom } from '@nanostores/persistent'
|
|
3
|
-
import { atom } from 'nanostores'
|
|
3
|
+
import { atom, WritableAtom } from 'nanostores'
|
|
4
4
|
import { GlobalState, GlobalStateConfig, StateSelector } from './types'
|
|
5
5
|
import { stateAssign, useStateSelector } from './utils'
|
|
6
|
+
import { arrayHandler, arrayOps } from './array'
|
|
6
7
|
|
|
7
8
|
const defaultConfig: GlobalStateConfig = {
|
|
8
9
|
persistKey: null,
|
|
@@ -36,6 +37,22 @@ export function globalState<T>(value: T, config: GlobalStateConfig = defaultConf
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
if(prop == 'reset'){
|
|
41
|
+
return Reflect.get(target, 'set', receiver)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if(arrayOps.includes(prop as string)){
|
|
45
|
+
const currentValue = target.get()
|
|
46
|
+
|
|
47
|
+
if(!Array.isArray(currentValue)) {
|
|
48
|
+
throw new Error('Cannot call array methods on a non array store')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const handle = arrayHandler(target as WritableAtom<any[]>)
|
|
52
|
+
|
|
53
|
+
return Reflect.get(handle, prop, receiver)
|
|
54
|
+
}
|
|
55
|
+
|
|
39
56
|
return Reflect.get(target, prop, receiver)
|
|
40
57
|
}
|
|
41
58
|
}) as unknown as GlobalState<T>
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import {globalState} from '../globalState'
|
|
3
|
+
|
|
4
|
+
test("store.set()", () => {
|
|
5
|
+
const store = globalState(1)
|
|
6
|
+
|
|
7
|
+
store.set(4)
|
|
8
|
+
|
|
9
|
+
expect(store.get()).toBe(4)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test("store.set() with object", () => {
|
|
13
|
+
const store = globalState({
|
|
14
|
+
a: 1,
|
|
15
|
+
b: "Test"
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
store.set({
|
|
19
|
+
a: 4
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
expect(store.get().a).toBe(4)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test("store.reset() with object", () => {
|
|
26
|
+
const store = globalState({
|
|
27
|
+
a: 1,
|
|
28
|
+
b: "Test"
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
store.reset({
|
|
32
|
+
a: 4,
|
|
33
|
+
b: "Changed"
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const newVal = store.get()
|
|
37
|
+
expect(newVal.a).toBe(4)
|
|
38
|
+
expect(newVal.b).toBe('Changed')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('store array methods', () => {
|
|
42
|
+
const store = globalState([] as number[])
|
|
43
|
+
|
|
44
|
+
store.push(10)
|
|
45
|
+
store.unshift(100)
|
|
46
|
+
|
|
47
|
+
const doubled = store.map(v => v * 2)
|
|
48
|
+
|
|
49
|
+
const val = store.get()
|
|
50
|
+
|
|
51
|
+
expect(val[0]).toBe(100)
|
|
52
|
+
expect(val[1]).toBe(10)
|
|
53
|
+
|
|
54
|
+
expect(doubled[0]).toBe(200)
|
|
55
|
+
expect(doubled[1]).toBe(20)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("store.listen()", () => {
|
|
59
|
+
const store = globalState(1)
|
|
60
|
+
|
|
61
|
+
store.listen((current, prev) => {
|
|
62
|
+
expect(current).toBe(4)
|
|
63
|
+
expect(prev).toBe(1)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
store.set(4)
|
|
67
|
+
|
|
68
|
+
expect(store.get()).toBe(4)
|
|
69
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import {globalState} from '../globalState'
|
|
3
|
+
import { createStateSlice } from "../utils"
|
|
4
|
+
|
|
5
|
+
test("slice selects consistently", () => {
|
|
6
|
+
const store = globalState({
|
|
7
|
+
a: 1,
|
|
8
|
+
b: "Test"
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const slice = createStateSlice(store,
|
|
12
|
+
v => v.a
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
store.set({
|
|
16
|
+
a: 4
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
expect(slice.get()).toBe(4)
|
|
20
|
+
|
|
21
|
+
store.set({
|
|
22
|
+
a: 10
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
expect(slice.get()).toBe(10)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test("slice sets consistently", () => {
|
|
29
|
+
const store = globalState({
|
|
30
|
+
a: 1,
|
|
31
|
+
b: "Test"
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const slice = createStateSlice(store,
|
|
35
|
+
v => v.a,
|
|
36
|
+
v => ({ a: v })
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
slice.set(4)
|
|
40
|
+
|
|
41
|
+
expect(slice.get()).toBe(4)
|
|
42
|
+
expect(store.get().a).toBe(4)
|
|
43
|
+
|
|
44
|
+
slice.set(10)
|
|
45
|
+
|
|
46
|
+
expect(slice.get()).toBe(10)
|
|
47
|
+
expect(store.get().a).toBe(10)
|
|
48
|
+
})
|
package/src/types.ts
CHANGED
|
@@ -3,10 +3,15 @@ import { PersistentStore, PersistentEvents, PersistentEvent } from '@nanostores/
|
|
|
3
3
|
|
|
4
4
|
export type StateSelector<S, R> = (state: S) => R
|
|
5
5
|
|
|
6
|
-
export type GlobalState<T> = Omit<WritableAtom<T>,
|
|
6
|
+
export type GlobalState<T> = Omit<WritableAtom<T>,'set'> & {
|
|
7
7
|
use: <Selected = T>(selector?: StateSelector<T, Selected>) => Selected
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
|
|
9
|
+
set: T extends Record<string, any> ? (newValue: Partial<T>) => T : WritableAtom<T>['set']
|
|
10
|
+
|
|
11
|
+
reset: WritableAtom<T>['set']
|
|
12
|
+
} & (
|
|
13
|
+
T extends any[] ? Array<T[number]> : {}
|
|
14
|
+
)
|
|
10
15
|
|
|
11
16
|
export type GlobalStateConfig = {
|
|
12
17
|
persistKey?: string
|
package/src/utils.ts
CHANGED
|
@@ -1,37 +1,51 @@
|
|
|
1
1
|
import { useStore } from '@nanostores/react'
|
|
2
|
-
import {
|
|
2
|
+
import { WritableStore } from 'nanostores'
|
|
3
|
+
import { useMemo } from 'react'
|
|
3
4
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
export function stateAssign<T>(newValue: Partial<T>, stateValue: T): T {
|
|
6
|
+
if (
|
|
7
|
+
typeof stateValue === "object" && stateValue !== null
|
|
8
|
+
) {
|
|
9
|
+
return {
|
|
10
|
+
...stateValue,
|
|
11
|
+
...newValue,
|
|
12
|
+
} as T
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return newValue as T
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const createStateSlice = <T, R>(
|
|
20
|
+
store: WritableStore<T>,
|
|
21
|
+
selector: (state: T) => R,
|
|
22
|
+
deselector?: (result: R) => T extends Record<string, any> ? Partial<T> : T
|
|
7
23
|
) => ({
|
|
8
24
|
get: () => selector(store.get()),
|
|
9
25
|
listen: (listener: (value: R) => void) => {
|
|
10
26
|
return store.listen((state) => {
|
|
11
27
|
listener(selector(state))
|
|
12
28
|
})
|
|
29
|
+
},
|
|
30
|
+
set(v: R) {
|
|
31
|
+
|
|
32
|
+
if(!deselector) {
|
|
33
|
+
throw new Error('[createStateSelector] deselector must be implemented to call set on state slices')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const parsed = deselector(v)
|
|
37
|
+
|
|
38
|
+
const newValue = stateAssign(parsed, store.get())
|
|
39
|
+
|
|
40
|
+
store.set(newValue)
|
|
13
41
|
}
|
|
14
|
-
} as
|
|
42
|
+
} as WritableStore<R>)
|
|
15
43
|
|
|
16
44
|
export function useStateSelector<T, R>(
|
|
17
|
-
store:
|
|
45
|
+
store: WritableStore<T>,
|
|
18
46
|
selector: (state: T) => R
|
|
19
47
|
): R {
|
|
20
|
-
|
|
21
|
-
}
|
|
48
|
+
const slice = useMemo(() => createStateSlice(store, selector), [selector])
|
|
22
49
|
|
|
23
|
-
|
|
24
|
-
if (Array.isArray(stateValue)) {
|
|
25
|
-
return [
|
|
26
|
-
...stateValue,
|
|
27
|
-
...(Array.isArray(newValue) ? newValue : [newValue]),
|
|
28
|
-
] as T
|
|
29
|
-
} else if (typeof stateValue === "object" && stateValue !== null) {
|
|
30
|
-
return {
|
|
31
|
-
...stateValue,
|
|
32
|
-
...newValue,
|
|
33
|
-
} as T
|
|
34
|
-
} else {
|
|
35
|
-
return newValue as T
|
|
36
|
-
}
|
|
50
|
+
return useStore(slice)
|
|
37
51
|
}
|