Skip to content

Commit bba9474

Browse files
authored
Merge pull request #26 from dottostack/feat/atomic
feat: atomic store
2 parents 82477af + 513aa4e commit bba9474

File tree

19 files changed

+343
-51
lines changed

19 files changed

+343
-51
lines changed

packages/dotto.x/computed/container.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ export const createContainer = (cb, emit, invalidate) => {
2727
return unbind
2828
})
2929

30-
if (!listenerBox[query]) listenerBox[query] = store.watch(query, emit)
30+
if (!listenerBox[query]) {
31+
listenerBox[query] = store.watch
32+
? store.watch(query, emit)
33+
: store.listen(emit)
34+
}
3135
},
3236
call() {
3337
return decorate(cb, () => {

packages/dotto.x/computed/take.d.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
/* eslint-disable no-shadow */
22
import type { ReadableStore } from './computed'
3+
import type { DotXStore } from '../create-store'
4+
import type { ResolveType } from '../utils/get'
5+
import type { DotXAtom } from '../create-atom'
36

4-
import { DotXStore } from '../create-store'
5-
import { ResolveType } from '../utils/get'
7+
export function take<Sub extends ReadableStore>(
8+
dep: Sub
9+
): ReturnType<Sub['get']>
610

7-
export function take<Sub extends ReadableStore>(dep: Sub): ReturnType<Sub['get']>
11+
export function take<Data>(
12+
dep: DotXAtom<Data>,
13+
): Data
814

915
export function take<Data, Query extends string>(
1016
dep: DotXStore<Data>,
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export type DotXAtom<State> = {
2+
/**
3+
* Get store value.
4+
*
5+
* ```js
6+
* store.get('some.value')
7+
* ```
8+
*
9+
* @param path Path with dots to your data.
10+
*/
11+
get(): State
12+
13+
set(payload: State): State
14+
15+
listen(cb: (value: State) => void): () => void
16+
subscribe(cb: (value: State) => void): () => void
17+
18+
off(): void
19+
}
20+
21+
/**
22+
* Define simple store.
23+
*
24+
* ```js
25+
* const store = createStore({ some: { path: 0 } })
26+
* ```
27+
*
28+
* @param name Name of your initializing store.
29+
* @param initial Your initial data or interface.
30+
* @returns The store object with methods to subscribe, get and set.
31+
*/
32+
export function createAtom<State>(initial?: State = {}): DotXAtom<State>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export const createAtom = (data = {}) => {
2+
let listenners = []
3+
let store = {
4+
lc: 0,
5+
set(value) {
6+
data = value
7+
store._emit()
8+
return data
9+
},
10+
get() {
11+
return data
12+
},
13+
_emit() {
14+
for (let listener of listenners) {
15+
listener(data)
16+
}
17+
},
18+
listen(cb) {
19+
store.lc = listenners.push(cb)
20+
return () => {
21+
listenners.splice(listenners.indexOf(cb), 1)
22+
store.lc--
23+
if (!store.lc) store.off()
24+
}
25+
},
26+
subscribe(cb) {
27+
let unbind = store.listen(cb)
28+
cb(data)
29+
return unbind
30+
},
31+
off() {
32+
listenners = []
33+
store.lc = 0
34+
}
35+
}
36+
return store
37+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { jest } from '@jest/globals'
2+
3+
import { computed, take } from '../computed'
4+
import { createAtom } from './index'
5+
6+
jest.useFakeTimers()
7+
8+
describe('atom:', () => {
9+
it('listen', async () => {
10+
expect.assertions(2)
11+
let store = createAtom({ some: { path: 0 } })
12+
let unbind = store.listen(value => {
13+
expect(value).toBe(store.get())
14+
})
15+
16+
store.set({ some: { path: 1 } })
17+
store.set({ some: { path: 2 } })
18+
unbind()
19+
})
20+
21+
it('subscribe', () => {
22+
expect.assertions(3)
23+
let store = createAtom({ some: { path: 0 } })
24+
let unbind = store.subscribe(value => {
25+
expect(value).toBe(store.get())
26+
})
27+
28+
store.set({ some: { path: 1 } })
29+
store.set({ some: { path: 2 } })
30+
unbind()
31+
})
32+
33+
it('use computed function', () => {
34+
let events: number[] = []
35+
let time = createAtom(1)
36+
let num = computed(() => {
37+
return 10 + take(time)
38+
})
39+
let unsub = num.subscribe(value => {
40+
events.push(value)
41+
})
42+
time.set(2)
43+
time.set(3)
44+
expect(events).toEqual([11, 12, 13])
45+
unsub()
46+
})
47+
})
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { onChange, onCreate, onGet, onOff, onSet } from '../lifecycle'
2+
import { run_all } from '../utils/run_all'
3+
import { createAtom } from './index'
4+
5+
describe('atom lifecycle', () => {
6+
it('onCreate (from listen)', () => {
7+
let events: string[] = []
8+
let atom = createAtom(2)
9+
let unsubHook = onCreate(atom, () => events.push('ok'))
10+
let unsubAtom = atom.listen(() => {})
11+
expect(events).toEqual(['ok'])
12+
run_all([unsubHook, unsubAtom])
13+
})
14+
15+
it('onCreate (from subscribe)', () => {
16+
let events: string[] = []
17+
let atom = createAtom(2)
18+
let unsubHook = onCreate(atom, () => events.push('ok'))
19+
let unsubAtom = atom.subscribe(() => {})
20+
expect(events).toEqual(['ok'])
21+
run_all([unsubHook, unsubAtom])
22+
})
23+
24+
it('onOff (from listen)', () => {
25+
let events: string[] = []
26+
let atom = createAtom(2)
27+
let unsubHook = onOff(atom, () => events.push('ok'))
28+
let unsubAtom = atom.listen(() => {})
29+
unsubAtom()
30+
expect(events).toEqual(['ok'])
31+
unsubHook()
32+
})
33+
34+
it('onOff (from subscribe)', () => {
35+
let events: string[] = []
36+
let atom = createAtom(2)
37+
let unsubHook = onOff(atom, () => events.push('ok'))
38+
let unsubAtom = atom.subscribe(() => {})
39+
unsubAtom()
40+
expect(events).toEqual(['ok'])
41+
unsubHook()
42+
})
43+
44+
it('onSet', () => {
45+
let events: string[] = []
46+
let atom = createAtom(2)
47+
let unsubHook = onSet(atom, () => events.push('ok'))
48+
atom.set(3)
49+
expect(events).toEqual(['ok'])
50+
unsubHook()
51+
})
52+
53+
it('onSet (abort)', () => {
54+
let events: string[] = []
55+
let atom = createAtom(2)
56+
let unsubHook = onSet(atom, (_, { methods }) => {
57+
methods.abort()
58+
})
59+
atom.listen(() => {
60+
events.push('i will never call')
61+
})
62+
atom.set(3)
63+
expect(events).toEqual([])
64+
unsubHook()
65+
})
66+
67+
it('onChange', () => {
68+
let events: string[] = []
69+
let atom = createAtom(2)
70+
let unsubHook = onChange(atom, () => events.push('ok'))
71+
atom.set(3)
72+
expect(events).toEqual(['ok'])
73+
unsubHook()
74+
})
75+
76+
it('onChange (abort)', () => {
77+
let events: string[] = []
78+
let atom = createAtom(2)
79+
let unsubHook = onChange(atom, (_, { methods }) => {
80+
methods.abort()
81+
})
82+
atom.listen(() => {
83+
events.push('i will never call')
84+
})
85+
atom.set(3)
86+
expect(events).toEqual([])
87+
unsubHook()
88+
})
89+
90+
it('onGet', () => {
91+
let events: string[] = []
92+
let atom = createAtom(2)
93+
let unsubHook = onGet(atom, () => events.push('ok'))
94+
atom.get()
95+
expect(events).toEqual(['ok'])
96+
unsubHook()
97+
})
98+
})

packages/dotto.x/effect/index.d.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { DotXStore } from '../create-store'
2-
import { ReadableStore } from '../computed'
1+
import type { DotXStore } from '../create-store'
2+
import type { ReadableStore } from '../computed'
3+
import type { DotXAtom } from '../create-atom'
34

4-
type EffectDep<Data> = DotXStore<Data> | ReadableStore<Data>
5+
type EffectDep<Data> = DotXStore<Data> | ReadableStore<Data> | DotXAtom
56

67
export function effect(store: EffectDep<any>, cb: () => void): () => void
78

packages/dotto.x/effect/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const effect = (deps, cb) => {
1313
if (dep._run) {
1414
unbinds.push(dep.listen(handle))
1515
} else {
16+
// TODO listen
1617
unbinds.push(onOff(dep, handle))
1718
}
1819
return unbinds

packages/dotto.x/index.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
export * from './create-store'
2+
export * from './create-atom'
23

34
export * from './computed'
45
export { mount } from './mount'
6+
export { update } from './update'
57

68
export { task, allTasks } from './task'
79
export { ResolveType } from './utils/get'
810

911
export { effect } from './effect'
1012
export { bind, BindedStore } from './bind'
11-
export { watchDeep } from './watch-deep'
12-
export { deep } from './deep'

packages/dotto.x/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
export { createAtom } from './create-atom'
12
export { createStore } from './create-store'
23

34
export { computed, take } from './computed'
45
export { mount } from './mount'
6+
export { update } from './update'
57

68
export { task, allTasks } from './task'
79

810
export { effect } from './effect'
911
export { bind } from './bind'
10-
export { watchDeep } from './watch-deep'
11-
export { deep } from './deep'

0 commit comments

Comments
 (0)