muya 2.0.0-beta.3 → 2.0.1
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/README.md +127 -200
- package/cjs/index.js +1 -1
- package/esm/create-state.js +1 -0
- package/esm/create.js +1 -1
- package/esm/debug/development-tools.js +1 -1
- package/esm/index.js +1 -1
- package/esm/scheduler.js +1 -0
- package/esm/select.js +1 -0
- package/esm/use-value.js +1 -0
- package/esm/utils/__tests__/is.test.js +1 -1
- package/esm/utils/common.js +1 -1
- package/esm/utils/is.js +1 -1
- package/package.json +12 -12
- package/src/__tests__/bench.test.tsx +3 -108
- package/src/__tests__/create.test.tsx +122 -70
- package/src/__tests__/scheduler.test.tsx +52 -0
- package/src/__tests__/select.test.tsx +127 -0
- package/src/__tests__/use-value.test.tsx +78 -0
- package/src/create-state.ts +50 -0
- package/src/create.ts +42 -73
- package/src/debug/development-tools.ts +18 -3
- package/src/index.ts +2 -1
- package/src/{utils/global-scheduler.ts → scheduler.ts} +9 -3
- package/src/select.ts +69 -0
- package/src/types.ts +56 -5
- package/src/use-value.ts +22 -0
- package/src/utils/__tests__/is.test.ts +24 -7
- package/src/utils/common.ts +35 -10
- package/src/utils/is.ts +5 -8
- package/types/create-state.d.ts +12 -0
- package/types/create.d.ts +6 -18
- package/types/debug/development-tools.d.ts +2 -9
- package/types/index.d.ts +2 -1
- package/types/{utils/scheduler.d.ts → scheduler.d.ts} +4 -1
- package/types/select.d.ts +10 -0
- package/types/types.d.ts +54 -4
- package/types/use-value.d.ts +2 -0
- package/types/utils/common.d.ts +6 -5
- package/types/utils/is.d.ts +3 -4
- package/esm/__tests__/create-async.test.js +0 -1
- package/esm/subscriber.js +0 -1
- package/esm/use.js +0 -1
- package/esm/utils/__tests__/context.test.js +0 -1
- package/esm/utils/__tests__/sub-memo.test.js +0 -1
- package/esm/utils/create-context.js +0 -1
- package/esm/utils/global-scheduler.js +0 -1
- package/esm/utils/scheduler.js +0 -1
- package/esm/utils/sub-memo.js +0 -1
- package/src/__tests__/create-async.test.ts +0 -88
- package/src/__tests__/subscriber.test.tsx +0 -89
- package/src/__tests__/use-async.test.tsx +0 -45
- package/src/__tests__/use.test.tsx +0 -125
- package/src/subscriber.ts +0 -165
- package/src/use.ts +0 -57
- package/src/utils/__tests__/context.test.ts +0 -198
- package/src/utils/__tests__/sub-memo.test.ts +0 -13
- package/src/utils/create-context.ts +0 -60
- package/src/utils/scheduler.ts +0 -59
- package/src/utils/sub-memo.ts +0 -49
- package/types/subscriber.d.ts +0 -25
- package/types/use.d.ts +0 -2
- package/types/utils/create-context.d.ts +0 -5
- package/types/utils/global-scheduler.d.ts +0 -5
- package/types/utils/sub-memo.d.ts +0 -7
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
/* eslint-disable sonarjs/pseudo-random */
|
|
2
|
-
/* eslint-disable sonarjs/no-nested-functions */
|
|
3
|
-
import { createContext } from '../create-context'
|
|
4
|
-
import { longPromise } from '../../__tests__/test-utils'
|
|
5
|
-
|
|
6
|
-
describe('context', () => {
|
|
7
|
-
it('should check context', () => {
|
|
8
|
-
const context = createContext({ name: 'John Doe' })
|
|
9
|
-
|
|
10
|
-
const main = () => {
|
|
11
|
-
context.run({ name: 'Jane Doe' }, () => {
|
|
12
|
-
expect(context.use()).toEqual({ name: 'Jane Doe' })
|
|
13
|
-
})
|
|
14
|
-
}
|
|
15
|
-
expect(context.use()).toEqual({ name: 'John Doe' })
|
|
16
|
-
main()
|
|
17
|
-
expect(context.use()).toEqual({ name: 'John Doe' })
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('should test async context', (done) => {
|
|
21
|
-
const context = createContext<string>('empty')
|
|
22
|
-
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
23
|
-
const awaiter = async () => new Promise((resolve) => setTimeout(resolve, 10))
|
|
24
|
-
context.run('outer', () => {
|
|
25
|
-
expect(context.use()).toEqual('outer')
|
|
26
|
-
|
|
27
|
-
// Wrap the asynchronous callback to preserve 'outer' context
|
|
28
|
-
setTimeout(
|
|
29
|
-
context.wrap(async () => {
|
|
30
|
-
try {
|
|
31
|
-
await awaiter()
|
|
32
|
-
expect(context.use()).toEqual('outer')
|
|
33
|
-
innerDone()
|
|
34
|
-
} catch (error) {
|
|
35
|
-
done(error)
|
|
36
|
-
}
|
|
37
|
-
}),
|
|
38
|
-
10,
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
context.run('inner', () => {
|
|
42
|
-
expect(context.use()).toEqual('inner')
|
|
43
|
-
|
|
44
|
-
// Wrap the asynchronous callback to preserve 'inner' context
|
|
45
|
-
setTimeout(
|
|
46
|
-
context.wrap(() => {
|
|
47
|
-
try {
|
|
48
|
-
expect(context.use()).toEqual('inner')
|
|
49
|
-
innerDone()
|
|
50
|
-
} catch (error) {
|
|
51
|
-
done(error)
|
|
52
|
-
}
|
|
53
|
-
}),
|
|
54
|
-
10,
|
|
55
|
-
)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
expect(context.use()).toEqual('outer')
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
let completed = 0
|
|
62
|
-
function innerDone() {
|
|
63
|
-
completed += 1
|
|
64
|
-
if (completed === 2) {
|
|
65
|
-
done()
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
|
-
it('should test async nested context', (done) => {
|
|
70
|
-
const context = createContext(0)
|
|
71
|
-
context.run(1, () => {
|
|
72
|
-
expect(context.use()).toEqual(1)
|
|
73
|
-
context.run(2, () => {
|
|
74
|
-
context.run(3, () => {
|
|
75
|
-
expect(context.use()).toEqual(3)
|
|
76
|
-
setTimeout(
|
|
77
|
-
context.wrap(() => {
|
|
78
|
-
expect(context.use()).toEqual(3)
|
|
79
|
-
}),
|
|
80
|
-
10,
|
|
81
|
-
)
|
|
82
|
-
expect(context.use()).toEqual(3)
|
|
83
|
-
})
|
|
84
|
-
setTimeout(
|
|
85
|
-
context.wrap(() => {
|
|
86
|
-
expect(context.use()).toEqual(2)
|
|
87
|
-
}),
|
|
88
|
-
10,
|
|
89
|
-
)
|
|
90
|
-
expect(context.use()).toEqual(2)
|
|
91
|
-
context.run(3, () => {
|
|
92
|
-
expect(context.use()).toEqual(3)
|
|
93
|
-
setTimeout(
|
|
94
|
-
context.wrap(() => {
|
|
95
|
-
expect(context.use()).toEqual(3)
|
|
96
|
-
context.run(4, () => {
|
|
97
|
-
expect(context.use()).toEqual(4)
|
|
98
|
-
setTimeout(
|
|
99
|
-
context.wrap(() => {
|
|
100
|
-
expect(context.use()).toEqual(4)
|
|
101
|
-
done()
|
|
102
|
-
}),
|
|
103
|
-
10,
|
|
104
|
-
)
|
|
105
|
-
expect(context.use()).toEqual(4)
|
|
106
|
-
})
|
|
107
|
-
}),
|
|
108
|
-
10,
|
|
109
|
-
)
|
|
110
|
-
expect(context.use()).toEqual(3)
|
|
111
|
-
})
|
|
112
|
-
// check back to 2
|
|
113
|
-
expect(context.use()).toEqual(2)
|
|
114
|
-
})
|
|
115
|
-
// check back to 1
|
|
116
|
-
expect(context.use()).toEqual(1)
|
|
117
|
-
})
|
|
118
|
-
// check back to 0
|
|
119
|
-
expect(context.use()).toEqual(0)
|
|
120
|
-
})
|
|
121
|
-
it('should stress test context with async random code', async () => {
|
|
122
|
-
const stressCount = 10_000
|
|
123
|
-
const context = createContext(0)
|
|
124
|
-
for (let index = 0; index < stressCount; index++) {
|
|
125
|
-
context.run(index, () => {
|
|
126
|
-
expect(context.use()).toEqual(index)
|
|
127
|
-
})
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const promises: Promise<unknown>[] = []
|
|
131
|
-
for (let index = 0; index < stressCount; index++) {
|
|
132
|
-
context.run(index, () => {
|
|
133
|
-
expect(context.use()).toEqual(index)
|
|
134
|
-
const promise = new Promise((resolve) => {
|
|
135
|
-
setTimeout(
|
|
136
|
-
context.wrap(() => {
|
|
137
|
-
expect(context.use()).toEqual(index)
|
|
138
|
-
resolve(index)
|
|
139
|
-
}),
|
|
140
|
-
Math.random() * 100,
|
|
141
|
-
)
|
|
142
|
-
})
|
|
143
|
-
promises.push(promise)
|
|
144
|
-
})
|
|
145
|
-
}
|
|
146
|
-
await Promise.all(promises)
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
it('should-test-default-value-with-ctx', async () => {
|
|
150
|
-
const ctx = createContext({ counter: 1 })
|
|
151
|
-
ctx.run({ counter: 10 }, async () => {
|
|
152
|
-
await longPromise(10)
|
|
153
|
-
expect(ctx.use()?.counter).toBe(10)
|
|
154
|
-
})
|
|
155
|
-
ctx.run({ counter: 12 }, () => {
|
|
156
|
-
expect(ctx.use()?.counter).toBe(12)
|
|
157
|
-
})
|
|
158
|
-
})
|
|
159
|
-
it('should test nested context', () => {
|
|
160
|
-
const currentCount = 0
|
|
161
|
-
const context = createContext({ count: 0 })
|
|
162
|
-
function container() {
|
|
163
|
-
const isIn = context.use()
|
|
164
|
-
expect(isIn?.count).toBe(currentCount)
|
|
165
|
-
context.run({ count: currentCount + 1 }, () => {
|
|
166
|
-
const inner = context.use()
|
|
167
|
-
expect(inner?.count).toBe(currentCount + 1)
|
|
168
|
-
context.run({ count: currentCount + 2 }, () => {
|
|
169
|
-
const innerInner = context.use()
|
|
170
|
-
expect(innerInner?.count).toBe(currentCount + 2)
|
|
171
|
-
})
|
|
172
|
-
})
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
container()
|
|
176
|
-
container()
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
it('should test nested context with async when promise is returned, but not waited', async () => {
|
|
180
|
-
const context = createContext({ count: 0 })
|
|
181
|
-
|
|
182
|
-
async function insideContextNestedAwaited() {
|
|
183
|
-
await longPromise(10)
|
|
184
|
-
const ctx = context.use()
|
|
185
|
-
expect(ctx?.count).toBe(1)
|
|
186
|
-
}
|
|
187
|
-
async function insideContext() {
|
|
188
|
-
await insideContextNestedAwaited()
|
|
189
|
-
// HERE THIS IS NOT WAITED, SO CONTEXT IS LOST.
|
|
190
|
-
// insideContextNestedAwaited() // this will not work
|
|
191
|
-
context.wrap(insideContextNestedAwaited) // this work
|
|
192
|
-
const ctx = context.use()
|
|
193
|
-
expect(ctx?.count).toBe(1)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
context.run({ count: 1 }, insideContext)
|
|
197
|
-
})
|
|
198
|
-
})
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/* eslint-disable unicorn/consistent-function-scoping */
|
|
2
|
-
import { subMemo } from '../sub-memo'
|
|
3
|
-
|
|
4
|
-
describe('memo-fn', () => {
|
|
5
|
-
it('should create memo fn', () => {
|
|
6
|
-
function toBeMemoized(): boolean {
|
|
7
|
-
return true
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const memoized = subMemo(toBeMemoized)
|
|
11
|
-
expect(memoized.call().emitter).toBeDefined()
|
|
12
|
-
})
|
|
13
|
-
})
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { isPromise } from './is'
|
|
2
|
-
|
|
3
|
-
const EMPTY_CONTEXT = Symbol('_')
|
|
4
|
-
|
|
5
|
-
export function createContext<T>(defaultContextValue: T) {
|
|
6
|
-
const contextStack: Array<T | typeof EMPTY_CONTEXT> = []
|
|
7
|
-
|
|
8
|
-
function use(): T | undefined {
|
|
9
|
-
if (contextStack.length === 0) {
|
|
10
|
-
return defaultContextValue
|
|
11
|
-
}
|
|
12
|
-
const currentContext = contextStack.at(-1)
|
|
13
|
-
return currentContext === EMPTY_CONTEXT ? defaultContextValue : currentContext
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function run<R>(ctxValue: T, cb: () => R | Promise<R>): R {
|
|
17
|
-
contextStack.push(ctxValue)
|
|
18
|
-
const result = cb()
|
|
19
|
-
const isResultPromise = isPromise(result)
|
|
20
|
-
if (isResultPromise) {
|
|
21
|
-
return (async () => {
|
|
22
|
-
try {
|
|
23
|
-
return await result
|
|
24
|
-
} finally {
|
|
25
|
-
contextStack.pop()
|
|
26
|
-
}
|
|
27
|
-
})() as R
|
|
28
|
-
} else {
|
|
29
|
-
contextStack.pop()
|
|
30
|
-
return result
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function wrap<X>(cb: () => X | Promise<X>): () => X | Promise<X> {
|
|
35
|
-
const capturedContext = use()
|
|
36
|
-
return () => {
|
|
37
|
-
contextStack.push(capturedContext!)
|
|
38
|
-
const result = cb()
|
|
39
|
-
const isResultPromise = isPromise(result)
|
|
40
|
-
if (isResultPromise) {
|
|
41
|
-
return (async () => {
|
|
42
|
-
try {
|
|
43
|
-
return await result
|
|
44
|
-
} finally {
|
|
45
|
-
contextStack.pop()
|
|
46
|
-
}
|
|
47
|
-
})()
|
|
48
|
-
} else {
|
|
49
|
-
contextStack.pop()
|
|
50
|
-
return result
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
run,
|
|
57
|
-
use,
|
|
58
|
-
wrap,
|
|
59
|
-
}
|
|
60
|
-
}
|
package/src/utils/scheduler.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
export const THRESHOLD = 0.2
|
|
2
|
-
export const THRESHOLD_ITEMS = 10
|
|
3
|
-
export const RESCHEDULE_COUNT = 0
|
|
4
|
-
|
|
5
|
-
export interface SchedulerOptions<T> {
|
|
6
|
-
readonly onResolveItem?: (item: T) => void
|
|
7
|
-
readonly onFinish: () => void
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function createScheduler<T>(options: SchedulerOptions<T>) {
|
|
11
|
-
const batches = new Set<T>()
|
|
12
|
-
const { onResolveItem, onFinish } = options
|
|
13
|
-
let frame = performance.now()
|
|
14
|
-
let scheduled = false
|
|
15
|
-
|
|
16
|
-
function schedule() {
|
|
17
|
-
const startFrame = performance.now()
|
|
18
|
-
const frameSizeDiffIn = startFrame - frame
|
|
19
|
-
const { size } = batches
|
|
20
|
-
if (frameSizeDiffIn < THRESHOLD && size > 0 && size < THRESHOLD_ITEMS) {
|
|
21
|
-
frame = startFrame
|
|
22
|
-
flush()
|
|
23
|
-
return
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (!scheduled) {
|
|
27
|
-
scheduled = true
|
|
28
|
-
Promise.resolve().then(() => {
|
|
29
|
-
scheduled = false
|
|
30
|
-
frame = performance.now()
|
|
31
|
-
flush()
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function flush() {
|
|
37
|
-
if (batches.size === 0) {
|
|
38
|
-
return
|
|
39
|
-
}
|
|
40
|
-
for (const value of batches) {
|
|
41
|
-
if (onResolveItem) {
|
|
42
|
-
onResolveItem(value)
|
|
43
|
-
}
|
|
44
|
-
batches.delete(value)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (batches.size > RESCHEDULE_COUNT) {
|
|
48
|
-
schedule()
|
|
49
|
-
return
|
|
50
|
-
}
|
|
51
|
-
onFinish()
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function addValue(value: T) {
|
|
55
|
-
batches.add(value)
|
|
56
|
-
schedule()
|
|
57
|
-
}
|
|
58
|
-
return addValue
|
|
59
|
-
}
|
package/src/utils/sub-memo.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { Subscribe } from '../subscriber'
|
|
2
|
-
import { subscriber } from '../subscriber'
|
|
3
|
-
import type { AnyFunction } from '../types'
|
|
4
|
-
|
|
5
|
-
interface CacheItem<T extends AnyFunction> {
|
|
6
|
-
count: number
|
|
7
|
-
returnType: Subscribe<T>
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const cache = new WeakMap<AnyFunction, CacheItem<AnyFunction>>()
|
|
11
|
-
let cacheCount = 0
|
|
12
|
-
|
|
13
|
-
export function getDebugCacheCreation() {
|
|
14
|
-
return cacheCount
|
|
15
|
-
}
|
|
16
|
-
function incrementDebugFunctionCreationCount() {
|
|
17
|
-
cacheCount++
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function subMemo<F extends AnyFunction>(anyFunction: F) {
|
|
21
|
-
cacheCount = 0
|
|
22
|
-
return {
|
|
23
|
-
call(): Subscribe<F> {
|
|
24
|
-
const item = cache.get(anyFunction)
|
|
25
|
-
if (item) {
|
|
26
|
-
item.count++
|
|
27
|
-
|
|
28
|
-
return item.returnType
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
incrementDebugFunctionCreationCount()
|
|
32
|
-
const returnType = subscriber(anyFunction)
|
|
33
|
-
const newItem = { count: 1, returnType }
|
|
34
|
-
cache.set(anyFunction, newItem)
|
|
35
|
-
return newItem.returnType
|
|
36
|
-
},
|
|
37
|
-
destroy() {
|
|
38
|
-
const item = cache.get(anyFunction)
|
|
39
|
-
|
|
40
|
-
if (item) {
|
|
41
|
-
item.count--
|
|
42
|
-
if (item.count === 0) {
|
|
43
|
-
item.returnType.destroy()
|
|
44
|
-
cache.delete(anyFunction)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
}
|
|
49
|
-
}
|
package/types/subscriber.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { AnyFunction, Callable, Listener } from './types';
|
|
2
|
-
import type { Emitter } from './utils/create-emitter';
|
|
3
|
-
interface SubscribeContext<T = unknown> {
|
|
4
|
-
addEmitter: (emitter: Emitter<T>) => void;
|
|
5
|
-
id: number;
|
|
6
|
-
sub: () => void;
|
|
7
|
-
}
|
|
8
|
-
interface SubscribeRaw<F extends AnyFunction, T extends ReturnType<F> = ReturnType<F>> {
|
|
9
|
-
(): T;
|
|
10
|
-
emitter: Emitter<T | undefined>;
|
|
11
|
-
destroy: () => void;
|
|
12
|
-
id: number;
|
|
13
|
-
listen: Listener<T>;
|
|
14
|
-
abort: () => void;
|
|
15
|
-
}
|
|
16
|
-
export type Subscribe<F extends AnyFunction, T extends ReturnType<F> = ReturnType<F>> = {
|
|
17
|
-
readonly [K in keyof SubscribeRaw<F, T>]: SubscribeRaw<F, T>[K];
|
|
18
|
-
} & Callable<T>;
|
|
19
|
-
export declare const context: {
|
|
20
|
-
run: <R>(ctxValue: SubscribeContext<unknown> | undefined, cb: () => R | Promise<R>) => R;
|
|
21
|
-
use: () => SubscribeContext<unknown> | undefined;
|
|
22
|
-
wrap: <X>(cb: () => X | Promise<X>) => () => X | Promise<X>;
|
|
23
|
-
};
|
|
24
|
-
export declare function subscriber<F extends AnyFunction, T extends ReturnType<F> = ReturnType<F>>(anyFunction: () => T): Subscribe<F, T>;
|
|
25
|
-
export {};
|
package/types/use.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { Subscribe } from '../subscriber';
|
|
2
|
-
import type { AnyFunction } from '../types';
|
|
3
|
-
export declare function getDebugCacheCreation(): number;
|
|
4
|
-
export declare function subMemo<F extends AnyFunction>(anyFunction: F): {
|
|
5
|
-
call(): Subscribe<F>;
|
|
6
|
-
destroy(): void;
|
|
7
|
-
};
|