muya 2.0.0-beta.3 → 2.0.0

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.
Files changed (64) hide show
  1. package/README.md +124 -195
  2. package/cjs/index.js +1 -1
  3. package/esm/create-state.js +1 -0
  4. package/esm/create.js +1 -1
  5. package/esm/debug/development-tools.js +1 -1
  6. package/esm/index.js +1 -1
  7. package/esm/scheduler.js +1 -0
  8. package/esm/select.js +1 -0
  9. package/esm/use-value.js +1 -0
  10. package/esm/utils/__tests__/is.test.js +1 -1
  11. package/esm/utils/common.js +1 -1
  12. package/esm/utils/is.js +1 -1
  13. package/package.json +12 -12
  14. package/src/__tests__/bench.test.tsx +3 -108
  15. package/src/__tests__/create.test.tsx +122 -70
  16. package/src/__tests__/scheduler.test.tsx +52 -0
  17. package/src/__tests__/select.test.tsx +127 -0
  18. package/src/__tests__/use-value.test.tsx +78 -0
  19. package/src/create-state.ts +50 -0
  20. package/src/create.ts +42 -73
  21. package/src/debug/development-tools.ts +18 -3
  22. package/src/index.ts +2 -1
  23. package/src/{utils/global-scheduler.ts → scheduler.ts} +9 -3
  24. package/src/select.ts +69 -0
  25. package/src/types.ts +57 -6
  26. package/src/use-value.ts +22 -0
  27. package/src/utils/__tests__/is.test.ts +24 -7
  28. package/src/utils/common.ts +35 -10
  29. package/src/utils/is.ts +5 -8
  30. package/types/create-state.d.ts +12 -0
  31. package/types/create.d.ts +6 -18
  32. package/types/debug/development-tools.d.ts +2 -9
  33. package/types/index.d.ts +2 -1
  34. package/types/{utils/scheduler.d.ts → scheduler.d.ts} +4 -1
  35. package/types/select.d.ts +10 -0
  36. package/types/types.d.ts +55 -5
  37. package/types/use-value.d.ts +2 -0
  38. package/types/utils/common.d.ts +6 -5
  39. package/types/utils/is.d.ts +3 -4
  40. package/esm/__tests__/create-async.test.js +0 -1
  41. package/esm/subscriber.js +0 -1
  42. package/esm/use.js +0 -1
  43. package/esm/utils/__tests__/context.test.js +0 -1
  44. package/esm/utils/__tests__/sub-memo.test.js +0 -1
  45. package/esm/utils/create-context.js +0 -1
  46. package/esm/utils/global-scheduler.js +0 -1
  47. package/esm/utils/scheduler.js +0 -1
  48. package/esm/utils/sub-memo.js +0 -1
  49. package/src/__tests__/create-async.test.ts +0 -88
  50. package/src/__tests__/subscriber.test.tsx +0 -89
  51. package/src/__tests__/use-async.test.tsx +0 -45
  52. package/src/__tests__/use.test.tsx +0 -125
  53. package/src/subscriber.ts +0 -165
  54. package/src/use.ts +0 -57
  55. package/src/utils/__tests__/context.test.ts +0 -198
  56. package/src/utils/__tests__/sub-memo.test.ts +0 -13
  57. package/src/utils/create-context.ts +0 -60
  58. package/src/utils/scheduler.ts +0 -59
  59. package/src/utils/sub-memo.ts +0 -49
  60. package/types/subscriber.d.ts +0 -25
  61. package/types/use.d.ts +0 -2
  62. package/types/utils/create-context.d.ts +0 -5
  63. package/types/utils/global-scheduler.d.ts +0 -5
  64. package/types/utils/sub-memo.d.ts +0 -7
package/src/create.ts CHANGED
@@ -1,75 +1,57 @@
1
- import { canUpdate, generateId } from './utils/common'
2
- import type { Emitter } from './utils/create-emitter'
3
- import { createEmitter } from './utils/create-emitter'
1
+ import { canUpdate, handleAsyncUpdate } from './utils/common'
4
2
  import { isEqualBase, isFunction, isSetValueFunction, isUndefined } from './utils/is'
5
- // import { createScheduler } from './utils/scheduler'
6
- import type { Cache, Callable, DefaultValue, IsEqual, Listener, SetValue } from './types'
7
- import { context } from './subscriber'
8
- import { createGlobalScheduler } from './utils/global-scheduler'
3
+ import type { Cache, DefaultValue, IsEqual, SetValue, State } from './types'
4
+ import { createScheduler } from './scheduler'
5
+ import { subscribeToDevelopmentTools } from './debug/development-tools'
6
+ import { createState } from './create-state'
9
7
 
10
- export const createScheduler = createGlobalScheduler()
11
- interface RawState<T> {
12
- (): T
13
- id: number
14
- set: (value: SetValue<T>) => void
15
- emitter: Emitter<T>
16
- listen: Listener<T>
17
- destroy: () => void
18
- withName: (name: string) => RawState<T>
19
- stateName?: string
20
- }
21
-
22
- export type State<T> = {
23
- readonly [K in keyof RawState<T>]: RawState<T>[K]
24
- } & Callable<T>
8
+ export const stateScheduler = createScheduler()
25
9
 
10
+ /**
11
+ * Create state from a default value.
12
+ */
26
13
  export function create<T>(initialValue: DefaultValue<T>, isEqual: IsEqual<T> = isEqualBase): State<T> {
27
14
  const cache: Cache<T> = {}
28
15
 
29
16
  function getValue(): T {
30
- if (isUndefined(cache.current)) {
31
- cache.current = isFunction(initialValue) ? initialValue() : initialValue
17
+ try {
18
+ if (isUndefined(cache.current)) {
19
+ const value = isFunction(initialValue) ? initialValue() : initialValue
20
+ const resolvedValue = handleAsyncUpdate(cache, state.emitter.emit, value)
21
+ cache.current = resolvedValue
22
+ }
23
+ return cache.current
24
+ } catch (error) {
25
+ cache.current = error as T
32
26
  }
33
27
  return cache.current
34
28
  }
35
- function resolveValue(value: SetValue<T>) {
36
- const previous = getValue()
37
- cache.current = isSetValueFunction(value) ? value(previous) : value
38
- }
39
29
 
40
- // const schedule = createScheduler<SetValue<T>>({
41
- // onFinish() {
42
- // cache.current = getValue()
43
- // if (!canUpdate(cache, isEqual)) {
44
- // return
45
- // }
46
- // state.emitter.emit()
47
- // },
48
- // onResolveItem: resolveValue,
49
- // })
50
-
51
- const state: RawState<T> = function () {
52
- const stateValue = getValue()
53
- const ctx = context.use()
54
- // console.log('CTX', ctx?.id, 'STATE', state.id)
55
- if (ctx && !state.emitter.contains(ctx.sub)) {
56
- ctx.addEmitter(state.emitter)
30
+ function setValue(value: SetValue<T>) {
31
+ if (cache.abortController) {
32
+ cache.abortController.abort()
57
33
  }
58
- return stateValue
59
- }
60
- state.listen = function (listener: (value: T) => void) {
61
- return state.emitter.subscribe(() => {
62
- const final = cache.current
63
- if (isUndefined(final)) {
64
- throw new Error('The value is undefined')
65
- }
66
- listener(final)
67
- })
34
+
35
+ const previous = getValue()
36
+ const newValue = isSetValueFunction(value) ? value(previous) : value
37
+ const resolvedValue = handleAsyncUpdate(cache, state.emitter.emit, newValue)
38
+ cache.current = resolvedValue
68
39
  }
69
- state.emitter = createEmitter<T>(() => state())
70
- state.id = generateId()
71
40
 
72
- const clearScheduler = createScheduler.add(state.id, {
41
+ const state = createState<T>({
42
+ get: getValue,
43
+ destroy() {
44
+ getValue()
45
+ clearScheduler()
46
+ state.emitter.clear()
47
+ cache.current = undefined
48
+ },
49
+ set(value: SetValue<T>) {
50
+ stateScheduler.schedule(state.id, value)
51
+ },
52
+ })
53
+
54
+ const clearScheduler = stateScheduler.add(state.id, {
73
55
  onFinish() {
74
56
  cache.current = getValue()
75
57
  if (!canUpdate(cache, isEqual)) {
@@ -77,22 +59,9 @@ export function create<T>(initialValue: DefaultValue<T>, isEqual: IsEqual<T> = i
77
59
  }
78
60
  state.emitter.emit()
79
61
  },
80
- onResolveItem: resolveValue,
62
+ onResolveItem: setValue,
81
63
  })
82
- state.set = function (value) {
83
- createScheduler.schedule(state.id, value)
84
- }
85
-
86
- state.destroy = function () {
87
- cache.current = undefined
88
- getValue()
89
- clearScheduler()
90
- state.emitter.clear()
91
- }
92
- state.withName = function (name: string) {
93
- state.stateName = name
94
- return state
95
- }
96
64
 
65
+ subscribeToDevelopmentTools(state)
97
66
  return state
98
67
  }
@@ -1,4 +1,5 @@
1
- import { isPromise } from '../utils/is'
1
+ import type { GetState, State } from '../types'
2
+ import { isPromise, isState } from '../utils/is'
2
3
 
3
4
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
4
5
  // @ts-expect-error
@@ -19,7 +20,7 @@ interface SendOptions {
19
20
  value: unknown
20
21
  name: string
21
22
  }
22
- export function sendToDevelopmentTools(options: SendOptions) {
23
+ function sendToDevelopmentTools(options: SendOptions) {
23
24
  if (!reduxDevelopmentTools) {
24
25
  return
25
26
  }
@@ -30,8 +31,22 @@ export function sendToDevelopmentTools(options: SendOptions) {
30
31
  reduxDevelopmentTools.send(name, { value, type, message }, type)
31
32
  }
32
33
 
33
- export function developmentToolsListener(name: string, type: StateType) {
34
+ function developmentToolsListener(name: string, type: StateType) {
34
35
  return (value: unknown) => {
35
36
  sendToDevelopmentTools({ name, type, value, message: 'update' })
36
37
  }
37
38
  }
39
+
40
+ export function subscribeToDevelopmentTools<T>(state: State<T> | GetState<T>) {
41
+ if (process.env.NODE_ENV === 'production') {
42
+ return
43
+ }
44
+ let type: StateType = 'state'
45
+
46
+ if (!isState(state)) {
47
+ type = 'derived'
48
+ }
49
+ const name = state.stateName?.length ? state.stateName : `${type}(${state.id.toString()})`
50
+ sendToDevelopmentTools({ name, type, value: state.get(), message: 'initial' })
51
+ return state.listen(developmentToolsListener(name, type))
52
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './types'
2
2
  export { create } from './create'
3
- export { use } from './use'
3
+ export { select } from './select'
4
+ export { useValue } from './use-value'
4
5
  export { shallow } from './utils/shallow'
@@ -1,12 +1,18 @@
1
- import type { SchedulerOptions } from './scheduler'
2
- import { RESCHEDULE_COUNT, THRESHOLD, THRESHOLD_ITEMS } from './scheduler'
1
+ export const THRESHOLD = 0.2
2
+ export const THRESHOLD_ITEMS = 10
3
+ export const RESCHEDULE_COUNT = 0
3
4
 
4
5
  interface GlobalSchedulerItem<T> {
5
6
  value: T
6
7
  id: number
7
8
  }
8
9
 
9
- export function createGlobalScheduler() {
10
+ export interface SchedulerOptions<T> {
11
+ readonly onResolveItem?: (item: T) => void
12
+ readonly onFinish: () => void
13
+ }
14
+
15
+ export function createScheduler() {
10
16
  const listeners = new Map<number, SchedulerOptions<unknown>>()
11
17
  const batches = new Set<GlobalSchedulerItem<unknown>>()
12
18
 
package/src/select.ts ADDED
@@ -0,0 +1,69 @@
1
+ import { stateScheduler } from './create'
2
+ import { createState } from './create-state'
3
+ import { subscribeToDevelopmentTools } from './debug/development-tools'
4
+ import type { Cache, GetState, IsEqual } from './types'
5
+ import { canUpdate, handleAsyncUpdate } from './utils/common'
6
+ import { isUndefined } from './utils/is'
7
+
8
+ type StateDependencies<T extends Array<unknown>> = {
9
+ [K in keyof T]: GetState<T[K]>
10
+ }
11
+
12
+ /**
13
+ * Selecting state from multiple states.
14
+ * It will create new state in read-only mode (without set).
15
+ */
16
+ export function select<T = unknown, S extends Array<unknown> = []>(
17
+ states: StateDependencies<S>,
18
+ selector: (...values: S) => T,
19
+ isEqual?: IsEqual<T>,
20
+ ): GetState<T> {
21
+ const cache: Cache<T> = {}
22
+
23
+ function computedValue(): T {
24
+ const values = states.map((state) => state.get()) as S
25
+ return selector(...values)
26
+ }
27
+
28
+ function getValue(): T {
29
+ if (isUndefined(cache.current)) {
30
+ const newValue = computedValue()
31
+ cache.current = handleAsyncUpdate(cache, state.emitter.emit, newValue)
32
+ }
33
+ return cache.current
34
+ }
35
+
36
+ const cleanups: Array<() => void> = []
37
+ for (const dependencyState of states) {
38
+ const clean = dependencyState.emitter.subscribe(() => {
39
+ stateScheduler.schedule(state.id, null)
40
+ })
41
+ cleanups.push(clean)
42
+ }
43
+
44
+ const state = createState<T>({
45
+ destroy() {
46
+ for (const cleanup of cleanups) {
47
+ cleanup()
48
+ }
49
+ clearScheduler()
50
+ state.emitter.clear()
51
+ cache.current = undefined
52
+ },
53
+ get: getValue,
54
+ })
55
+
56
+ const clearScheduler = stateScheduler.add(state.id, {
57
+ onFinish() {
58
+ const newValue = computedValue()
59
+ cache.current = handleAsyncUpdate(cache, state.emitter.emit, newValue)
60
+ if (!canUpdate(cache, isEqual)) {
61
+ return
62
+ }
63
+ state.emitter.emit()
64
+ },
65
+ })
66
+
67
+ subscribeToDevelopmentTools(state)
68
+ return state
69
+ }
package/src/types.ts CHANGED
@@ -1,15 +1,66 @@
1
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
- export type AnyFunction = (...args: any[]) => any
1
+ import type { Emitter } from './utils/create-emitter'
3
2
 
4
3
  export type IsEqual<T = unknown> = (a: T, b: T) => boolean
5
- export type SetStateCb<T> = (value: T) => Awaited<T>
4
+ export type SetStateCb<T> = (value: T | Awaited<T>) => Awaited<T>
6
5
  export type SetValue<T> = SetStateCb<T> | Awaited<T>
7
6
  export type DefaultValue<T> = T | (() => T)
8
- export type Listener<T> = (listener: (value: T) => void) => () => void
7
+ export type Listener<T> = (listener: (value?: T) => void) => () => void
9
8
  export interface Cache<T> {
10
9
  current?: T
11
10
  previous?: T
11
+ abortController?: AbortController
12
12
  }
13
- export type Callable<T> = () => T
14
13
 
15
- export const EMPTY_SELECTOR = <T>(stateValue: T) => stateValue
14
+ export const EMPTY_SELECTOR = <T, S>(stateValue: T) => stateValue as unknown as S
15
+
16
+ export interface GetState<T> {
17
+ <S>(selector?: (stateValue: T) => S): undefined extends S ? T : S
18
+ /**
19
+ * Get the cached state value.
20
+ */
21
+ get: () => T
22
+ /**
23
+ * Get the unique id of the state.
24
+ */
25
+ id: number
26
+ /**
27
+ * Emitter to listen to changes with snapshots.
28
+ */
29
+ emitter: Emitter<T>
30
+ /**
31
+ * Listen to changes in the state.
32
+ */
33
+ listen: Listener<T>
34
+ /**
35
+ * Destroy / cleanup the state.
36
+ * Clean all listeners and make cache value undefined.
37
+ */
38
+ destroy: () => void
39
+ /**
40
+ * Set the state name. For debugging purposes.
41
+ */
42
+ withName: (name: string) => GetState<T>
43
+ /**
44
+ * Name of the state. For debugging purposes.
45
+ */
46
+ stateName?: string
47
+ /**
48
+ * Select particular slice of the state.
49
+ * It will create "another" state in read-only mode (without set).
50
+ */
51
+ select: <S>(selector: (state: T) => S, isEqual?: IsEqual<S>) => GetState<S>
52
+ }
53
+
54
+ export interface State<T> extends GetState<T> {
55
+ /**
56
+ * Setting new state value.
57
+ * It can be value or function that returns a value (similar to `setState` in React).
58
+ * If the state is initialized with async code, set will cancel the previous promise.
59
+ */
60
+ set: (value: SetValue<T>) => void
61
+ /**
62
+ * Set the state name. For debugging purposes.
63
+ */
64
+ withName: (name: string) => State<T>
65
+ isSet: true
66
+ }
@@ -0,0 +1,22 @@
1
+ import { useDebugValue, useSyncExternalStore } from 'react'
2
+ import { EMPTY_SELECTOR, type GetState } from './types'
3
+ import { isError, isPromise } from './utils/is'
4
+
5
+ export function useValue<T, S>(state: GetState<T>, selector: (stateValue: T) => S = EMPTY_SELECTOR): undefined extends S ? T : S {
6
+ const { emitter } = state
7
+ const value = useSyncExternalStore<S>(
8
+ state.emitter.subscribe,
9
+ () => selector(emitter.getSnapshot()),
10
+ () => selector(emitter.getInitialSnapshot ? emitter.getInitialSnapshot() : emitter.getSnapshot()),
11
+ )
12
+ useDebugValue(value)
13
+ if (isPromise(value)) {
14
+ throw value
15
+ }
16
+
17
+ if (isError(value)) {
18
+ throw value
19
+ }
20
+
21
+ return value as undefined extends S ? T : S
22
+ }
@@ -1,5 +1,5 @@
1
- import { Abort } from '../common'
2
- import { isPromise, isFunction, isSetValueFunction, isMap, isSet, isArray, isEqualBase, isAbortError } from '../is'
1
+ import { create } from '../../create'
2
+ import { isPromise, isFunction, isSetValueFunction, isMap, isSet, isArray, isEqualBase, isUndefined, isState } from '../is'
3
3
 
4
4
  describe('isPromise', () => {
5
5
  it('should return true for a Promise', () => {
@@ -64,11 +64,28 @@ describe('isEqualBase', () => {
64
64
  })
65
65
  })
66
66
 
67
- describe('isAbortError', () => {
68
- it('should return true for an AbortError', () => {
69
- expect(isAbortError(new DOMException('', Abort.Error))).toBe(true)
67
+ describe('isUndefined', () => {
68
+ it('should return true for undefined', () => {
69
+ // eslint-disable-next-line unicorn/no-useless-undefined
70
+ expect(isUndefined(undefined)).toBe(true)
70
71
  })
71
- it('should return false for a non-AbortError', () => {
72
- expect(isAbortError(new DOMException('', 'Error'))).toBe(false)
72
+ it('should return false for a non-undefined', () => {
73
+ expect(isUndefined(123)).toBe(false)
74
+ })
75
+ })
76
+
77
+ describe('isState', () => {
78
+ it('should return true for a State real', () => {
79
+ const state = create(1)
80
+ expect(isState(state)).toBe(true)
81
+ })
82
+
83
+ it('should return true for a State with derived', () => {
84
+ const state = create(1)
85
+ const derived = state.select((v) => v)
86
+ expect(isState(derived)).toBe(false)
87
+ })
88
+ it('should return false for a non-State', () => {
89
+ expect(isState(123)).toBe(false)
73
90
  })
74
91
  })
@@ -1,5 +1,5 @@
1
1
  import type { Cache, IsEqual } from '../types'
2
- import { isUndefined } from './is'
2
+ import { isAbortError, isEqualBase, isPromise, isUndefined } from './is'
3
3
 
4
4
  // eslint-disable-next-line no-shadow
5
5
  export enum Abort {
@@ -7,14 +7,13 @@ export enum Abort {
7
7
  }
8
8
 
9
9
  export interface CancelablePromise<T> {
10
- promise?: Promise<T>
10
+ promise: Promise<T>
11
11
  controller?: AbortController
12
- resolveInitialPromise?: (value: T) => void
13
12
  }
14
13
  /**
15
14
  * Cancelable promise function, return promise and controller
16
15
  */
17
- export function cancelablePromise<T>(promise: Promise<T>, previousController?: AbortController): CancelablePromise<T> {
16
+ function cancelablePromise<T>(promise: Promise<T>, previousController?: AbortController): CancelablePromise<T> {
18
17
  if (previousController) {
19
18
  previousController.abort()
20
19
  }
@@ -32,12 +31,10 @@ export function cancelablePromise<T>(promise: Promise<T>, previousController?: A
32
31
  return { promise: cancelable, controller }
33
32
  }
34
33
 
35
- let id = 0
36
- export function generateId() {
37
- return id++
38
- }
39
-
40
- export function canUpdate<T>(cache: Cache<T>, isEqual: IsEqual<T> = (previous, next) => previous === next): boolean {
34
+ /**
35
+ * Check if the cache value is different from the previous value.
36
+ */
37
+ export function canUpdate<T>(cache: Cache<T>, isEqual: IsEqual<T> = isEqualBase): boolean {
41
38
  if (!isUndefined(cache.current)) {
42
39
  if (!isUndefined(cache.previous) && isEqual(cache.current, cache.previous)) {
43
40
  return false
@@ -46,3 +43,31 @@ export function canUpdate<T>(cache: Cache<T>, isEqual: IsEqual<T> = (previous, n
46
43
  }
47
44
  return true
48
45
  }
46
+
47
+ /**
48
+ * Handle async updates for `create` and `select`
49
+ */
50
+ export function handleAsyncUpdate<T>(cache: Cache<T>, emit: () => void, value: T): T {
51
+ if (!isPromise(value)) {
52
+ return value
53
+ }
54
+ if (cache.abortController) {
55
+ cache.abortController.abort()
56
+ }
57
+
58
+ const { promise, controller } = cancelablePromise(value, cache.abortController)
59
+ cache.abortController = controller
60
+
61
+ return promise
62
+ .then((result) => {
63
+ cache.current = result as T
64
+ emit()
65
+ })
66
+ .catch((error) => {
67
+ if (isAbortError(error)) {
68
+ return
69
+ }
70
+ cache.current = error as T
71
+ emit()
72
+ }) as T
73
+ }
package/src/utils/is.ts CHANGED
@@ -1,6 +1,5 @@
1
+ import type { SetStateCb, SetValue, State } from '../types'
1
2
  import { Abort } from './common'
2
- import type { SetStateCb, SetValue } from '../types'
3
- import type { State } from '../create'
4
3
 
5
4
  export function isPromise<T>(value: unknown): value is Promise<T> {
6
5
  return value instanceof Promise
@@ -35,16 +34,14 @@ export function isAbortError(value: unknown): value is DOMException {
35
34
  return value instanceof DOMException && value.name === Abort.Error
36
35
  }
37
36
 
38
- export function isAnyOtherError(value: unknown): value is Error {
39
- return value instanceof Error && value.name !== Abort.Error
37
+ export function isError(value: unknown): value is Error {
38
+ return value instanceof Error
40
39
  }
41
40
 
42
41
  export function isUndefined(value: unknown): value is undefined {
43
42
  return value === undefined
44
43
  }
45
44
 
46
- export function isCreate(value: unknown): value is State<unknown> {
47
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
48
- // @ts-expect-error
49
- return isFunction(value) && value.set !== undefined
45
+ export function isState<T>(value: unknown): value is State<T> {
46
+ return isFunction(value) && 'get' in value && 'set' in value && 'isSet' in value && value.isSet === true
50
47
  }
@@ -0,0 +1,12 @@
1
+ import type { GetState, SetValue, State } from './types';
2
+ interface GetStateOptions<T> {
3
+ readonly get: () => T;
4
+ readonly set?: (value: SetValue<T>) => void;
5
+ readonly destroy: () => void;
6
+ }
7
+ type FullState<T> = GetStateOptions<T>['set'] extends undefined ? GetState<T> : State<T>;
8
+ /**
9
+ * This is just utility function to create state base data
10
+ */
11
+ export declare function createState<T>(options: GetStateOptions<T>): FullState<T>;
12
+ export {};
package/types/create.d.ts CHANGED
@@ -1,21 +1,9 @@
1
- import type { Emitter } from './utils/create-emitter';
2
- import type { Callable, DefaultValue, IsEqual, Listener, SetValue } from './types';
3
- export declare const createScheduler: {
4
- add<T>(id: number, option: import("./utils/scheduler").SchedulerOptions<T>): () => void;
1
+ import type { DefaultValue, IsEqual, State } from './types';
2
+ export declare const stateScheduler: {
3
+ add<T>(id: number, option: import("./scheduler").SchedulerOptions<T>): () => void;
5
4
  schedule<T>(id: number, value: T): void;
6
5
  };
7
- interface RawState<T> {
8
- (): T;
9
- id: number;
10
- set: (value: SetValue<T>) => void;
11
- emitter: Emitter<T>;
12
- listen: Listener<T>;
13
- destroy: () => void;
14
- withName: (name: string) => RawState<T>;
15
- stateName?: string;
16
- }
17
- export type State<T> = {
18
- readonly [K in keyof RawState<T>]: RawState<T>[K];
19
- } & Callable<T>;
6
+ /**
7
+ * Create state from a default value.
8
+ */
20
9
  export declare function create<T>(initialValue: DefaultValue<T>, isEqual?: IsEqual<T>): State<T>;
21
- export {};
@@ -1,10 +1,3 @@
1
+ import type { GetState, State } from '../types';
1
2
  export type StateType = 'state' | 'derived';
2
- interface SendOptions {
3
- message?: string;
4
- type: StateType;
5
- value: unknown;
6
- name: string;
7
- }
8
- export declare function sendToDevelopmentTools(options: SendOptions): void;
9
- export declare function developmentToolsListener(name: string, type: StateType): (value: unknown) => void;
10
- export {};
3
+ export declare function subscribeToDevelopmentTools<T>(state: State<T> | GetState<T>): (() => void) | undefined;
package/types/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './types';
2
2
  export { create } from './create';
3
- export { use } from './use';
3
+ export { select } from './select';
4
+ export { useValue } from './use-value';
4
5
  export { shallow } from './utils/shallow';
@@ -5,4 +5,7 @@ export interface SchedulerOptions<T> {
5
5
  readonly onResolveItem?: (item: T) => void;
6
6
  readonly onFinish: () => void;
7
7
  }
8
- export declare function createScheduler<T>(options: SchedulerOptions<T>): (value: T) => void;
8
+ export declare function createScheduler(): {
9
+ add<T>(id: number, option: SchedulerOptions<T>): () => void;
10
+ schedule<T>(id: number, value: T): void;
11
+ };
@@ -0,0 +1,10 @@
1
+ import type { GetState, IsEqual } from './types';
2
+ type StateDependencies<T extends Array<unknown>> = {
3
+ [K in keyof T]: GetState<T[K]>;
4
+ };
5
+ /**
6
+ * Selecting state from multiple states.
7
+ * It will create new state in read-only mode (without set).
8
+ */
9
+ export declare function select<T = unknown, S extends Array<unknown> = []>(states: StateDependencies<S>, selector: (...values: S) => T, isEqual?: IsEqual<T>): GetState<T>;
10
+ export {};
package/types/types.d.ts CHANGED
@@ -1,12 +1,62 @@
1
- export type AnyFunction = (...args: any[]) => any;
1
+ import type { Emitter } from './utils/create-emitter';
2
2
  export type IsEqual<T = unknown> = (a: T, b: T) => boolean;
3
- export type SetStateCb<T> = (value: T) => Awaited<T>;
3
+ export type SetStateCb<T> = (value: T | Awaited<T>) => Awaited<T>;
4
4
  export type SetValue<T> = SetStateCb<T> | Awaited<T>;
5
5
  export type DefaultValue<T> = T | (() => T);
6
- export type Listener<T> = (listener: (value: T) => void) => () => void;
6
+ export type Listener<T> = (listener: (value?: T) => void) => () => void;
7
7
  export interface Cache<T> {
8
8
  current?: T;
9
9
  previous?: T;
10
+ abortController?: AbortController;
11
+ }
12
+ export declare const EMPTY_SELECTOR: <T, S>(stateValue: T) => S;
13
+ export interface GetState<T> {
14
+ <S>(selector?: (stateValue: T) => S): undefined extends S ? T : S;
15
+ /**
16
+ * Get the cached state value.
17
+ */
18
+ get: () => T;
19
+ /**
20
+ * Get the unique id of the state.
21
+ */
22
+ id: number;
23
+ /**
24
+ * Emitter to listen to changes with snapshots.
25
+ */
26
+ emitter: Emitter<T>;
27
+ /**
28
+ * Listen to changes in the state.
29
+ */
30
+ listen: Listener<T>;
31
+ /**
32
+ * Destroy / cleanup the state.
33
+ * Clean all listeners and make cache value undefined.
34
+ */
35
+ destroy: () => void;
36
+ /**
37
+ * Set the state name. For debugging purposes.
38
+ */
39
+ withName: (name: string) => GetState<T>;
40
+ /**
41
+ * Name of the state. For debugging purposes.
42
+ */
43
+ stateName?: string;
44
+ /**
45
+ * Select particular slice of the state.
46
+ * It will create "another" state in read-only mode (without set).
47
+ */
48
+ select: <S>(selector: (state: T) => S, isEqual?: IsEqual<S>) => GetState<S>;
49
+ }
50
+ export interface State<T> extends GetState<T> {
51
+ /**
52
+ * Setting new state value.
53
+ * It can be value or function that returns a value (similar to `setState` in React).
54
+ * If the state is initialized with async code, set will cancel the previous promise.
55
+ */
56
+ set: (value: SetValue<T>) => void;
57
+ /**
58
+ * Set the state name. For debugging purposes.
59
+ */
60
+ withName: (name: string) => State<T>;
61
+ isSet: true;
10
62
  }
11
- export type Callable<T> = () => T;
12
- export declare const EMPTY_SELECTOR: <T>(stateValue: T) => T;