@planet-matrix/mobius-model 0.1.3 → 0.3.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.
- package/CHANGELOG.md +54 -0
- package/README.md +21 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +12 -6
- package/dist/reactor/index.d.ts +3 -0
- package/dist/reactor/index.d.ts.map +1 -0
- package/dist/reactor/reactor-core/flags.d.ts.map +1 -0
- package/dist/reactor/reactor-core/index.d.ts.map +1 -0
- package/dist/reactor/reactor-core/primitive.d.ts +276 -0
- package/dist/reactor/reactor-core/primitive.d.ts.map +1 -0
- package/dist/{signal/signal-core → reactor/reactor-core}/reactive-system.d.ts +102 -22
- package/dist/reactor/reactor-core/reactive-system.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/branch.d.ts +19 -0
- package/dist/reactor/reactor-operators/branch.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/convert.d.ts +30 -0
- package/dist/reactor/reactor-operators/convert.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/create.d.ts +26 -0
- package/dist/reactor/reactor-operators/create.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/filter.d.ts +269 -0
- package/dist/reactor/reactor-operators/filter.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/index.d.ts +8 -0
- package/dist/reactor/reactor-operators/index.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/join.d.ts +48 -0
- package/dist/reactor/reactor-operators/join.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/map.d.ts +165 -0
- package/dist/reactor/reactor-operators/map.d.ts.map +1 -0
- package/dist/reactor/reactor-operators/utility.d.ts +48 -0
- package/dist/reactor/reactor-operators/utility.d.ts.map +1 -0
- package/package.json +9 -12
- package/src/index.ts +1 -1
- package/src/reactor/README.md +18 -0
- package/src/reactor/index.ts +2 -0
- package/src/reactor/reactor-core/primitive.ts +1046 -0
- package/src/{signal/signal-core → reactor/reactor-core}/reactive-system.ts +392 -93
- package/src/reactor/reactor-operators/branch.ts +66 -0
- package/src/reactor/reactor-operators/convert.ts +70 -0
- package/src/reactor/reactor-operators/create.ts +66 -0
- package/src/reactor/reactor-operators/filter.ts +988 -0
- package/src/reactor/reactor-operators/index.ts +7 -0
- package/src/reactor/reactor-operators/join.ts +174 -0
- package/src/reactor/reactor-operators/map.ts +599 -0
- package/src/reactor/reactor-operators/utility.ts +102 -0
- package/tests/unit/{signal/computed.spec.ts → reactor/alien-signals-computed.spec.ts} +15 -10
- package/tests/unit/reactor/alien-signals-effect-scope.spec.ts +86 -0
- package/tests/unit/reactor/alien-signals-effect.spec.ts +395 -0
- package/tests/unit/reactor/alien-signals-topology.spec.ts +361 -0
- package/tests/unit/reactor/alien-signals-trigger.spec.ts +75 -0
- package/tests/unit/reactor/alien-signals-untrack.spec.ts +91 -0
- package/tests/unit/reactor/preact-signal.spec.ts +73 -0
- package/tests/unit/reactor/reactor-core.spec.ts +219 -0
- package/tests/unit/reactor/reactor-operators-branch.spec.ts +33 -0
- package/tests/unit/reactor/reactor-operators-convert.spec.ts +31 -0
- package/tests/unit/reactor/reactor-operators-create.spec.ts +47 -0
- package/tests/unit/reactor/reactor-operators-filter.spec.ts +604 -0
- package/tests/unit/reactor/reactor-operators-join.spec.ts +94 -0
- package/tests/unit/reactor/reactor-operators-map.spec.ts +327 -0
- package/tests/unit/reactor/reactor-operators-utility.spec.ts +55 -0
- package/dist/signal/index.d.ts +0 -3
- package/dist/signal/index.d.ts.map +0 -1
- package/dist/signal/signal-core/flags.d.ts.map +0 -1
- package/dist/signal/signal-core/index.d.ts.map +0 -1
- package/dist/signal/signal-core/primitive.d.ts +0 -67
- package/dist/signal/signal-core/primitive.d.ts.map +0 -1
- package/dist/signal/signal-core/reactive-system.d.ts.map +0 -1
- package/dist/signal/signal-operators/index.d.ts +0 -4
- package/dist/signal/signal-operators/index.d.ts.map +0 -1
- package/src/signal/index.ts +0 -2
- package/src/signal/signal-core/README.md +0 -4
- package/src/signal/signal-core/primitive.ts +0 -275
- package/src/signal/signal-operators/index.ts +0 -19
- package/tests/unit/signal/effect.spec.ts +0 -108
- /package/dist/{signal/signal-core → reactor/reactor-core}/flags.d.ts +0 -0
- /package/dist/{signal/signal-core → reactor/reactor-core}/index.d.ts +0 -0
- /package/src/{signal/signal-core → reactor/reactor-core}/flags.ts +0 -0
- /package/src/{signal/signal-core → reactor/reactor-core}/index.ts +0 -0
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
import type { AnyValueReactor, Signal, ValueOfValueReactor, ValueReactor } from "../reactor-core/index.ts"
|
|
2
|
+
|
|
3
|
+
import { castValueInitializer } from "./utility.ts"
|
|
4
|
+
import { effect, signal } from "../reactor-core/index.ts"
|
|
5
|
+
|
|
6
|
+
export interface TapOptions<R extends AnyValueReactor> {
|
|
7
|
+
target: R;
|
|
8
|
+
tapper?: ((value: ValueOfValueReactor<R>) => void) | undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Taps into the value changes of a ValueReactor without modifying its value.
|
|
12
|
+
*/
|
|
13
|
+
export const tap = <R extends AnyValueReactor>(
|
|
14
|
+
options: TapOptions<R>
|
|
15
|
+
): R => {
|
|
16
|
+
const { target, tapper = (value: ValueOfValueReactor<R>): void => console.log(value) } = options;
|
|
17
|
+
|
|
18
|
+
effect(() => {
|
|
19
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
20
|
+
const value = target.get() as ValueOfValueReactor<R>
|
|
21
|
+
tapper(value)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
return target
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface WithHistoryOptions<V> {
|
|
28
|
+
target: ValueReactor<V>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Creates a signal that maintains a history of values from a ValueReactor.
|
|
32
|
+
* The history is stored in an array, with each new value appended to the end.
|
|
33
|
+
*
|
|
34
|
+
* Note: This implementation does not limit the size of the history array. Use
|
|
35
|
+
* with caution to avoid excessive memory usage.
|
|
36
|
+
*/
|
|
37
|
+
export const withHistory = <V>(
|
|
38
|
+
options: WithHistoryOptions<V>
|
|
39
|
+
): Signal<V[]> => {
|
|
40
|
+
const { target } = options;
|
|
41
|
+
|
|
42
|
+
const result = signal(castValueInitializer<V[]>(), {
|
|
43
|
+
onDispose: () => {
|
|
44
|
+
eTarget.dispose()
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const history: V[] = []
|
|
49
|
+
const eTarget = effect(() => {
|
|
50
|
+
history.push(target.get())
|
|
51
|
+
result.set([...history])
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return result
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface BufferByCountOptions<V> {
|
|
58
|
+
target: ValueReactor<V>;
|
|
59
|
+
count: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Buffers values from a ValueReactor and emits them as an array
|
|
63
|
+
* when the number of buffered values reaches the specified count.
|
|
64
|
+
*/
|
|
65
|
+
export const bufferByCount = <V>(
|
|
66
|
+
options: BufferByCountOptions<V>
|
|
67
|
+
): Signal<V[]> => {
|
|
68
|
+
const { target, count } = options;
|
|
69
|
+
|
|
70
|
+
if (count <= 0) {
|
|
71
|
+
throw new Error("bufferByCount operator requires count to be greater than 0")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const result = signal<V[]>(() => [], {
|
|
75
|
+
onDispose: () => {
|
|
76
|
+
eTarget.dispose()
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const bufferedValues: V[] = []
|
|
81
|
+
const eTarget = effect(() => {
|
|
82
|
+
bufferedValues.push(target.get())
|
|
83
|
+
if (bufferedValues.length >= count) {
|
|
84
|
+
result.set([...bufferedValues])
|
|
85
|
+
bufferedValues.length = 0
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
return result
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface BufferByTimeOptions<V> {
|
|
93
|
+
target: ValueReactor<V>;
|
|
94
|
+
time: number;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Buffers values from a ValueReactor and emits them as an array
|
|
98
|
+
* at specified time intervals.
|
|
99
|
+
*/
|
|
100
|
+
export const bufferByTime = <V>(
|
|
101
|
+
options: BufferByTimeOptions<V>
|
|
102
|
+
): Signal<V[]> => {
|
|
103
|
+
const { target, time } = options;
|
|
104
|
+
|
|
105
|
+
if (time <= 0) {
|
|
106
|
+
throw new Error("bufferByTime operator requires timeInMilliseconds to be greater than 0")
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const result = signal<V[]>(() => [], {
|
|
110
|
+
onDispose: () => {
|
|
111
|
+
eTarget.dispose()
|
|
112
|
+
clearInterval(intervalId)
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const bufferedValues: V[] = []
|
|
117
|
+
const eTarget = effect(() => {
|
|
118
|
+
bufferedValues.push(target.get())
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const intervalId = setInterval(() => {
|
|
122
|
+
result.set([...bufferedValues])
|
|
123
|
+
bufferedValues.length = 0
|
|
124
|
+
}, time)
|
|
125
|
+
|
|
126
|
+
return result
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface BufferByTriggerOptions<VTarget, VTrigger> {
|
|
130
|
+
target: ValueReactor<VTarget>;
|
|
131
|
+
trigger: ValueReactor<VTrigger>;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Buffers values from a ValueReactor and emits them as an array when the trigger ValueReactor changes.
|
|
135
|
+
*/
|
|
136
|
+
export const bufferByTrigger = <VTarget, VTrigger>(
|
|
137
|
+
options: BufferByTriggerOptions<VTarget, VTrigger>
|
|
138
|
+
): Signal<VTarget[]> => {
|
|
139
|
+
const { target, trigger } = options;
|
|
140
|
+
|
|
141
|
+
const result = signal(castValueInitializer<VTarget[]>(), {
|
|
142
|
+
onDispose: () => {
|
|
143
|
+
eTarget.dispose()
|
|
144
|
+
eTrigger.dispose()
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
const bufferedValues: VTarget[] = []
|
|
149
|
+
const eTarget = effect(() => {
|
|
150
|
+
bufferedValues.push(target.get())
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const eTrigger = effect(() => {
|
|
154
|
+
trigger.get()
|
|
155
|
+
result.set([...bufferedValues])
|
|
156
|
+
bufferedValues.length = 0
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
return result
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface BufferByToggleOptions<VTarget, VOpen, VClose> {
|
|
163
|
+
target: ValueReactor<VTarget>;
|
|
164
|
+
open: ValueReactor<VOpen>;
|
|
165
|
+
close: ValueReactor<VClose>;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Buffers values from a ValueReactor and emits them as an array
|
|
169
|
+
* when the open ValueReactor emits a value, and stops buffering
|
|
170
|
+
* when the close ValueReactor emits a value.
|
|
171
|
+
*/
|
|
172
|
+
export const bufferByToggle = <VTarget, VOpen, VClose>(
|
|
173
|
+
options: BufferByToggleOptions<VTarget, VOpen, VClose>
|
|
174
|
+
): Signal<VTarget[]> => {
|
|
175
|
+
const { target, open, close } = options;
|
|
176
|
+
|
|
177
|
+
const result = signal(castValueInitializer<VTarget[]>(), {
|
|
178
|
+
onDispose: () => {
|
|
179
|
+
eTarget.dispose()
|
|
180
|
+
eOpen.dispose()
|
|
181
|
+
eClose.dispose()
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
const bufferedValues: VTarget[] = []
|
|
186
|
+
let buffering = false
|
|
187
|
+
|
|
188
|
+
const eOpen = effect(() => {
|
|
189
|
+
open.get()
|
|
190
|
+
buffering = true
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
const eTarget = effect(() => {
|
|
194
|
+
const value = target.get()
|
|
195
|
+
if (buffering === true) {
|
|
196
|
+
bufferedValues.push(value)
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
const eClose = effect(() => {
|
|
201
|
+
close.get()
|
|
202
|
+
if (buffering === true) {
|
|
203
|
+
result.set([...bufferedValues])
|
|
204
|
+
bufferedValues.length = 0
|
|
205
|
+
buffering = false
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
return result
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface BufferByDynamicOptions<VTarget, VDynamic> {
|
|
213
|
+
target: ValueReactor<VTarget>;
|
|
214
|
+
dynamic: (value: VTarget) => ValueReactor<VDynamic>;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Buffers values from a ValueReactor and emits them as an array
|
|
218
|
+
* when the ValueReactor returned by the `dynamic` function emits a value.
|
|
219
|
+
* The `dynamic` function is called each time the buffer is emitted
|
|
220
|
+
* to get a new ValueReactor for the next buffer.
|
|
221
|
+
*/
|
|
222
|
+
export const bufferByDynamic = <VTarget, VDynamic>(
|
|
223
|
+
options: BufferByDynamicOptions<VTarget, VDynamic>
|
|
224
|
+
): Signal<VTarget[]> => {
|
|
225
|
+
const { target, dynamic } = options;
|
|
226
|
+
|
|
227
|
+
const result = signal(castValueInitializer<VTarget[]>(), {
|
|
228
|
+
onDispose: () => {
|
|
229
|
+
eTarget.dispose()
|
|
230
|
+
eDynamic.dispose()
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
const bufferedValues: VTarget[] = []
|
|
235
|
+
const eTarget = effect(() => {
|
|
236
|
+
const value = target.get()
|
|
237
|
+
bufferedValues.push(value)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
let trigger: ValueReactor<VDynamic> | undefined = undefined
|
|
241
|
+
const eDynamic = effect(() => {
|
|
242
|
+
trigger?.dispose()
|
|
243
|
+
const targetValue = target.getWithoutTrack()
|
|
244
|
+
trigger = dynamic(targetValue)
|
|
245
|
+
trigger.get()
|
|
246
|
+
result.set([...bufferedValues])
|
|
247
|
+
bufferedValues.length = 0
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
return result
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export interface GroupToArrayByOptions<V, K> {
|
|
254
|
+
target: ValueReactor<V>;
|
|
255
|
+
keyGetter: (value: V) => K;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Groups values from a ValueReactor into an array of arrays based on a key
|
|
259
|
+
* extracted by the keyGetter function.
|
|
260
|
+
*/
|
|
261
|
+
export const groupToArrayBy = <V, K>(
|
|
262
|
+
options: GroupToArrayByOptions<V, K>
|
|
263
|
+
): Signal<V[][]> => {
|
|
264
|
+
const { target, keyGetter } = options;
|
|
265
|
+
|
|
266
|
+
const result = signal<V[][]>(() => [], {
|
|
267
|
+
onDispose: () => {
|
|
268
|
+
eTarget.dispose()
|
|
269
|
+
}
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
const groups = new Map<K, V[]>()
|
|
273
|
+
const eTarget = effect(() => {
|
|
274
|
+
const value = target.get()
|
|
275
|
+
const key = keyGetter(value)
|
|
276
|
+
if (groups.has(key) === false) {
|
|
277
|
+
groups.set(key, [])
|
|
278
|
+
result.set([...groups.values()])
|
|
279
|
+
}
|
|
280
|
+
const groupedArray = groups.get(key)!
|
|
281
|
+
groupedArray.push(value)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
return result
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export interface GroupToObjectByOptions<V, K extends string | number | symbol> {
|
|
288
|
+
target: ValueReactor<V>;
|
|
289
|
+
keyGetter: (value: V) => K;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Groups values from a ValueReactor into an object where each key maps to an array
|
|
293
|
+
* of values corresponding to that key.
|
|
294
|
+
*/
|
|
295
|
+
export const groupToObjectBy = <V, K extends string | number | symbol>(
|
|
296
|
+
options: GroupToObjectByOptions<V, K>
|
|
297
|
+
): Signal<Record<K, V[]>> => {
|
|
298
|
+
const { target, keyGetter } = options;
|
|
299
|
+
|
|
300
|
+
const result = signal<Record<K, V[]>>(() => {
|
|
301
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
302
|
+
return {} as Record<K, V[]>
|
|
303
|
+
}, {
|
|
304
|
+
onDispose: () => {
|
|
305
|
+
eTarget.dispose()
|
|
306
|
+
}
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
const groups = new Map<K, V[]>()
|
|
310
|
+
const eTarget = effect(() => {
|
|
311
|
+
const value = target.get()
|
|
312
|
+
const key = keyGetter(value)
|
|
313
|
+
if (groups.has(key) === false) {
|
|
314
|
+
groups.set(key, [])
|
|
315
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
316
|
+
result.set(Object.fromEntries(groups.entries()) as Record<K, V[]>)
|
|
317
|
+
}
|
|
318
|
+
const groupedArray = groups.get(key)!
|
|
319
|
+
groupedArray.push(value)
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
return result
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Groups values from a ValueReactor into an array of Signals based on a key
|
|
327
|
+
* extracted by the keyGetter function.
|
|
328
|
+
*/
|
|
329
|
+
export const groupToSignalArrayBy = <V, K>(
|
|
330
|
+
target: ValueReactor<V>, keyGetter: (value: V) => K
|
|
331
|
+
): Signal<Array<Signal<V[]>>> => {
|
|
332
|
+
const result = signal<Array<Signal<V[]>>>(() => [], {
|
|
333
|
+
onDispose: () => {
|
|
334
|
+
eTarget.dispose()
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
const groups = new Map<K, Signal<V[]>>()
|
|
339
|
+
const eTarget = effect(() => {
|
|
340
|
+
const value = target.get()
|
|
341
|
+
const key = keyGetter(value)
|
|
342
|
+
if (groups.has(key) === false) {
|
|
343
|
+
const newGroup = signal<V[]>(() => [])
|
|
344
|
+
groups.set(key, newGroup)
|
|
345
|
+
result.set([...groups.values()])
|
|
346
|
+
}
|
|
347
|
+
const groupedSignal = groups.get(key)!
|
|
348
|
+
const currentArray = groupedSignal.get()
|
|
349
|
+
groupedSignal.set([...currentArray, value])
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
return result
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export interface GroupToSignalObjectByOptions<V, K extends string | number | symbol> {
|
|
356
|
+
target: ValueReactor<V>;
|
|
357
|
+
keyGetter: (value: V) => K;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Groups values from a ValueReactor into a Signal of an object where each key maps to a Signal
|
|
361
|
+
* of an array of values corresponding to that key.
|
|
362
|
+
*/
|
|
363
|
+
export const groupToSignalObjectBy = <V, K extends string | number | symbol>(
|
|
364
|
+
options: GroupToSignalObjectByOptions<V, K>
|
|
365
|
+
): Signal<Record<K, Signal<V[]>>> => {
|
|
366
|
+
const { target, keyGetter } = options;
|
|
367
|
+
|
|
368
|
+
const result = signal<Record<K, Signal<V[]>>>(() => {
|
|
369
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
370
|
+
return {} as Record<K, Signal<V[]>>
|
|
371
|
+
}, {
|
|
372
|
+
onDispose: () => {
|
|
373
|
+
eTarget.dispose()
|
|
374
|
+
}
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
const groups = new Map<K, Signal<V[]>>()
|
|
378
|
+
const eTarget = effect(() => {
|
|
379
|
+
const value = target.get()
|
|
380
|
+
const key = keyGetter(value)
|
|
381
|
+
if (groups.has(key) === false) {
|
|
382
|
+
const newGroup = signal<V[]>(() => [])
|
|
383
|
+
groups.set(key, newGroup)
|
|
384
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
385
|
+
result.set(Object.fromEntries(groups.entries()) as Record<K, Signal<V[]>>)
|
|
386
|
+
}
|
|
387
|
+
const groupedSignal = groups.get(key)!
|
|
388
|
+
const currentArray = groupedSignal.get()
|
|
389
|
+
groupedSignal.set([...currentArray, value])
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
return result
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export interface MapOptions<VTarget, VMapped> {
|
|
396
|
+
target: ValueReactor<VTarget>;
|
|
397
|
+
mapper: (value: VTarget) => VMapped;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Maps values from a ValueReactor using the provided mapper function and
|
|
401
|
+
* emits the mapped values as a new Signal.
|
|
402
|
+
*/
|
|
403
|
+
export const map = <VTarget, VMapped>(
|
|
404
|
+
options: MapOptions<VTarget, VMapped>
|
|
405
|
+
): Signal<VMapped> => {
|
|
406
|
+
const { target, mapper } = options;
|
|
407
|
+
|
|
408
|
+
const result = signal(castValueInitializer<VMapped>(), {
|
|
409
|
+
onDispose: () => {
|
|
410
|
+
eTarget.dispose()
|
|
411
|
+
}
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
const eTarget = effect(() => {
|
|
415
|
+
const value = target.get()
|
|
416
|
+
const newValue = mapper(value)
|
|
417
|
+
result.set(newValue)
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
return result
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export interface ContextualMapInitializingMapperContext<VTarget> {
|
|
424
|
+
isInitializingRun: true;
|
|
425
|
+
targetValue: VTarget
|
|
426
|
+
}
|
|
427
|
+
export interface ContextualMapUpdatingMapperContext<VTarget, VMapped> {
|
|
428
|
+
isInitializingRun: false;
|
|
429
|
+
targetValue: VTarget
|
|
430
|
+
previousMappedValue: VMapped
|
|
431
|
+
}
|
|
432
|
+
export type ContextualMapMapperContextOf<VTarget, VMapped> =
|
|
433
|
+
| ContextualMapInitializingMapperContext<VTarget>
|
|
434
|
+
| ContextualMapUpdatingMapperContext<VTarget, VMapped>;
|
|
435
|
+
|
|
436
|
+
export interface ContextualMapOptions<VTarget, VMapped> {
|
|
437
|
+
target: ValueReactor<VTarget>;
|
|
438
|
+
mapper: (context: ContextualMapMapperContextOf<VTarget, VMapped>) => VMapped;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Maps values from a ValueReactor using the provided contextual mapper function and
|
|
442
|
+
* emits the mapped values as a new Signal.
|
|
443
|
+
*/
|
|
444
|
+
export const contextualMap = <VTarget, VMapped>(
|
|
445
|
+
options: ContextualMapOptions<VTarget, VMapped>
|
|
446
|
+
): Signal<VMapped> => {
|
|
447
|
+
const { target, mapper } = options;
|
|
448
|
+
|
|
449
|
+
const result = signal(castValueInitializer<VMapped>(), {
|
|
450
|
+
onDispose: () => {
|
|
451
|
+
eTarget.dispose()
|
|
452
|
+
}
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
const eTarget = effect((context) => {
|
|
456
|
+
const targetValue = target.get()
|
|
457
|
+
const { isInitializingRun } = context
|
|
458
|
+
if (isInitializingRun === true) {
|
|
459
|
+
const newValue = mapper({
|
|
460
|
+
isInitializingRun,
|
|
461
|
+
targetValue
|
|
462
|
+
})
|
|
463
|
+
result.set(newValue)
|
|
464
|
+
} else {
|
|
465
|
+
const previousMappedValue = result.getWithoutTrack()
|
|
466
|
+
const newValue = mapper({
|
|
467
|
+
isInitializingRun,
|
|
468
|
+
targetValue,
|
|
469
|
+
previousMappedValue
|
|
470
|
+
})
|
|
471
|
+
result.set(newValue)
|
|
472
|
+
}
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
return result
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
export interface MergeMapOptions<VTarget, VMapped> {
|
|
479
|
+
target: ValueReactor<VTarget>;
|
|
480
|
+
mapper: (value: VTarget) => ValueReactor<VMapped>;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Maps each value from a ValueReactor<T> to a ValueReactor<R> using the provided `mapper`,
|
|
484
|
+
* subscribes to every mapped reactor, and merges their emitted values into a single `Signal<R>`.
|
|
485
|
+
*/
|
|
486
|
+
export const mergeMap = <VTarget, VMapped>(
|
|
487
|
+
options: MergeMapOptions<VTarget, VMapped>
|
|
488
|
+
): Signal<VMapped> => {
|
|
489
|
+
const { target, mapper } = options;
|
|
490
|
+
|
|
491
|
+
const result = signal(castValueInitializer<VMapped>(), {
|
|
492
|
+
onDispose: () => {
|
|
493
|
+
eTarget.dispose()
|
|
494
|
+
}
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
const eTarget = effect(() => {
|
|
498
|
+
const value = target.get()
|
|
499
|
+
const currentMappedReactor = mapper(value);
|
|
500
|
+
|
|
501
|
+
effect(() => {
|
|
502
|
+
const mappedValue = currentMappedReactor.get()
|
|
503
|
+
result.set(mappedValue)
|
|
504
|
+
})
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
return result
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export interface SwitchMapOptions<VTarget, VMapped> {
|
|
511
|
+
target: ValueReactor<VTarget>;
|
|
512
|
+
mapper: (value: VTarget) => ValueReactor<VMapped>;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Maps each value from a ValueReactor<T> to a ValueReactor<R> using the provided `mapper`,
|
|
516
|
+
* subscribes only to the latest mapped reactor, and emits its values as a `Signal<R>`.
|
|
517
|
+
*/
|
|
518
|
+
export const switchMap = <VTarget, VMapped>(
|
|
519
|
+
options: SwitchMapOptions<VTarget, VMapped>
|
|
520
|
+
): Signal<VMapped> => {
|
|
521
|
+
const { target, mapper } = options;
|
|
522
|
+
|
|
523
|
+
const result = signal(castValueInitializer<VMapped>(), {
|
|
524
|
+
onDispose: () => {
|
|
525
|
+
eTarget.dispose()
|
|
526
|
+
}
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
let lastMappedReactor: ValueReactor<VMapped> | undefined = undefined
|
|
530
|
+
|
|
531
|
+
const eTarget = effect(() => {
|
|
532
|
+
const value = target.get()
|
|
533
|
+
lastMappedReactor?.dispose()
|
|
534
|
+
lastMappedReactor = mapper(value);
|
|
535
|
+
effect(() => {
|
|
536
|
+
const mappedValue = lastMappedReactor!.get()
|
|
537
|
+
result.set(mappedValue)
|
|
538
|
+
})
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
return result
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
export interface ScanOptions<VTarget, VAccumulated> {
|
|
545
|
+
target: ValueReactor<VTarget>;
|
|
546
|
+
accumulator: (acc: VAccumulated, value: VTarget) => VAccumulated;
|
|
547
|
+
seed: VAccumulated;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Accumulates values from a ValueReactor using the provided accumulator function
|
|
551
|
+
* and emits the accumulated value as a Signal.
|
|
552
|
+
*/
|
|
553
|
+
export const scan = <VTarget, VAccumulated>(
|
|
554
|
+
options: ScanOptions<VTarget, VAccumulated>
|
|
555
|
+
): Signal<VAccumulated> => {
|
|
556
|
+
const { target, accumulator, seed } = options;
|
|
557
|
+
|
|
558
|
+
const result = signal(() => seed, {
|
|
559
|
+
onDispose: () => {
|
|
560
|
+
eTarget.dispose()
|
|
561
|
+
}
|
|
562
|
+
})
|
|
563
|
+
|
|
564
|
+
const eTarget = effect(() => {
|
|
565
|
+
const value = target.get()
|
|
566
|
+
const newValue = accumulator(result.getWithoutTrack(), value)
|
|
567
|
+
result.set(newValue)
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
return result
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export interface PairwiseOptions<V> {
|
|
574
|
+
target: ValueReactor<V>;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Emits the previous and current values from a ValueReactor as a tuple.
|
|
578
|
+
* The first emitted tuple will have `undefined` as the previous value.
|
|
579
|
+
*/
|
|
580
|
+
export const pairwise = <V>(
|
|
581
|
+
options: PairwiseOptions<V>
|
|
582
|
+
): Signal<[V | undefined, V]> => {
|
|
583
|
+
const { target } = options;
|
|
584
|
+
|
|
585
|
+
const result = signal<[V | undefined, V]>(() => [undefined, target.get()], {
|
|
586
|
+
onDispose: () => {
|
|
587
|
+
eTarget.dispose()
|
|
588
|
+
}
|
|
589
|
+
})
|
|
590
|
+
|
|
591
|
+
let previousValue: V | undefined = undefined
|
|
592
|
+
const eTarget = effect(() => {
|
|
593
|
+
const currentValue = target.get()
|
|
594
|
+
result.set([previousValue, currentValue])
|
|
595
|
+
previousValue = currentValue
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
return result
|
|
599
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { ValueReactor } from "../reactor-core/index.ts"
|
|
2
|
+
|
|
3
|
+
import { effect } from "../reactor-core/index.ts"
|
|
4
|
+
|
|
5
|
+
export type ValueInitializer<V> = () => V;
|
|
6
|
+
/**
|
|
7
|
+
* This initializer can be used to create a Signal with an undefined initial value which
|
|
8
|
+
* is casted to the desired type T. So the real value getter function will not be called
|
|
9
|
+
* until the Signal is actually used.
|
|
10
|
+
*
|
|
11
|
+
* Every signal should always have a valid value (or value getter which returns a valid value).
|
|
12
|
+
* When using the castValueInitializer, the real value should be set as soon as possible.
|
|
13
|
+
*/
|
|
14
|
+
export const castValueInitializer = <V>(): ValueInitializer<V> => {
|
|
15
|
+
return (): V => {
|
|
16
|
+
// oxlint-disable-next-line no-unsafe-return no-unsafe-type-assertion
|
|
17
|
+
return undefined as unknown as V
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type Unsubscribe = () => void
|
|
22
|
+
export interface SubscribeOptions<V> {
|
|
23
|
+
target: ValueReactor<V>;
|
|
24
|
+
subscriber: (value: V) => void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Subscribes to a ValueReactor and invokes the subscriber when the reactor's value
|
|
28
|
+
* changes.
|
|
29
|
+
*
|
|
30
|
+
* The initial value is skipped; the subscriber is called only on subsequent updates.
|
|
31
|
+
*/
|
|
32
|
+
export const subscribe = <V>(
|
|
33
|
+
options: SubscribeOptions<V>
|
|
34
|
+
): Unsubscribe => {
|
|
35
|
+
const { target, subscriber } = options
|
|
36
|
+
|
|
37
|
+
const e = effect((context) => {
|
|
38
|
+
const { isInitializingRun } = context
|
|
39
|
+
const value = target.get()
|
|
40
|
+
if (isInitializingRun === false) {
|
|
41
|
+
subscriber(value)
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
const unsubscribe = (): void => {
|
|
45
|
+
e.dispose()
|
|
46
|
+
}
|
|
47
|
+
return unsubscribe
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// oxlint-disable-next-line no-explicit-any
|
|
51
|
+
export type AnyValueReactorArray = Array<ValueReactor<any>>;
|
|
52
|
+
export type GetValuesInArray<T extends AnyValueReactorArray> = {
|
|
53
|
+
[K in keyof T]: T[K] extends ValueReactor<infer U> ? U : never
|
|
54
|
+
}
|
|
55
|
+
export interface GetValuesInArrayOptions<T extends AnyValueReactorArray> {
|
|
56
|
+
target: T;
|
|
57
|
+
withoutTrack?: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Gets the current values from an array of ValueReactors.
|
|
61
|
+
*/
|
|
62
|
+
export const getValuesInArray = <T extends AnyValueReactorArray>(
|
|
63
|
+
options: GetValuesInArrayOptions<T>
|
|
64
|
+
): GetValuesInArray<T> => {
|
|
65
|
+
const valueReactors = options.target;
|
|
66
|
+
const withoutTrack = options?.withoutTrack ?? false;
|
|
67
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
68
|
+
const values = valueReactors.map((vr) => {
|
|
69
|
+
// oxlint-disable-next-line no-unsafe-return
|
|
70
|
+
return withoutTrack ? vr.getWithoutTrack() : vr.get()
|
|
71
|
+
}) as unknown as GetValuesInArray<T>
|
|
72
|
+
return values
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// oxlint-disable-next-line no-explicit-any
|
|
76
|
+
export type AnyValueReactorObject = Record<string, ValueReactor<any>>;
|
|
77
|
+
export type GetValuesInObject<T extends AnyValueReactorObject> = {
|
|
78
|
+
[K in keyof T]: T[K] extends ValueReactor<infer U> ? U : never
|
|
79
|
+
}
|
|
80
|
+
export interface GetValueInObjectOptions<T extends AnyValueReactorObject> {
|
|
81
|
+
target: T;
|
|
82
|
+
withoutTrack?: boolean;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Gets the current values from an object of ValueReactors.
|
|
86
|
+
*/
|
|
87
|
+
export const getValuesInObject = <T extends AnyValueReactorObject>(
|
|
88
|
+
options: GetValueInObjectOptions<T>
|
|
89
|
+
): GetValuesInObject<T> => {
|
|
90
|
+
const valueReactors = options.target;
|
|
91
|
+
const withoutTrack = options?.withoutTrack ?? false;
|
|
92
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
93
|
+
const values = {} as GetValuesInObject<T>
|
|
94
|
+
for (const key of Object.keys(valueReactors)) {
|
|
95
|
+
const valueReactor = valueReactors[key]!
|
|
96
|
+
// oxlint-disable-next-line no-unsafe-assignment
|
|
97
|
+
const value = withoutTrack ? valueReactor.getWithoutTrack() : valueReactor.get()
|
|
98
|
+
// oxlint-disable-next-line no-unsafe-assignment no-unsafe-member-access no-unsafe-type-assertion
|
|
99
|
+
values[key as unknown as keyof GetValuesInObject<T>] = value
|
|
100
|
+
}
|
|
101
|
+
return values
|
|
102
|
+
}
|