@zeix/cause-effect 0.15.0 → 0.15.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 +2 -10
- package/index.dev.js +50 -49
- package/index.js +1 -1
- package/index.ts +12 -2
- package/package.json +1 -1
- package/src/diff.ts +15 -3
- package/src/effect.ts +1 -2
- package/src/match.ts +13 -18
- package/src/resolve.ts +7 -17
- package/src/signal.ts +44 -30
- package/src/store.ts +62 -63
- package/src/util.ts +15 -17
- package/test/signal.test.ts +451 -0
- package/test/store.test.ts +27 -0
- package/types/index.d.ts +4 -4
- package/types/src/diff.d.ts +6 -3
- package/types/src/match.d.ts +3 -3
- package/types/src/resolve.d.ts +3 -3
- package/types/src/signal.d.ts +17 -13
- package/types/src/store.d.ts +20 -15
- package/types/src/util.d.ts +3 -4
package/src/store.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { diff, type UnknownRecord } from './diff'
|
|
1
|
+
import { diff, type UnknownRecord, type UnknownRecordOrArray } from './diff'
|
|
2
2
|
import { effect } from './effect'
|
|
3
3
|
import {
|
|
4
4
|
batch,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from './scheduler'
|
|
10
10
|
import { type Signal, toMutableSignal, UNSET } from './signal'
|
|
11
11
|
import { type State, state } from './state'
|
|
12
|
-
import { hasMethod, isObjectOfType } from './util'
|
|
12
|
+
import { hasMethod, isObjectOfType, validArrayIndexes } from './util'
|
|
13
13
|
|
|
14
14
|
/* === Constants === */
|
|
15
15
|
|
|
@@ -17,28 +17,28 @@ const TYPE_STORE = 'Store'
|
|
|
17
17
|
|
|
18
18
|
/* === Types === */
|
|
19
19
|
|
|
20
|
-
interface StoreAddEvent<T extends
|
|
20
|
+
interface StoreAddEvent<T extends UnknownRecordOrArray> extends CustomEvent {
|
|
21
21
|
type: 'store-add'
|
|
22
22
|
detail: Partial<T>
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
interface StoreChangeEvent<T extends
|
|
25
|
+
interface StoreChangeEvent<T extends UnknownRecordOrArray> extends CustomEvent {
|
|
26
26
|
type: 'store-change'
|
|
27
27
|
detail: Partial<T>
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
interface StoreRemoveEvent<T extends
|
|
30
|
+
interface StoreRemoveEvent<T extends UnknownRecordOrArray> extends CustomEvent {
|
|
31
31
|
type: 'store-remove'
|
|
32
32
|
detail: Partial<T>
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
type StoreEventMap<T extends
|
|
35
|
+
type StoreEventMap<T extends UnknownRecordOrArray> = {
|
|
36
36
|
'store-add': StoreAddEvent<T>
|
|
37
37
|
'store-change': StoreChangeEvent<T>
|
|
38
38
|
'store-remove': StoreRemoveEvent<T>
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
interface StoreEventTarget<T extends
|
|
41
|
+
interface StoreEventTarget<T extends UnknownRecordOrArray> extends EventTarget {
|
|
42
42
|
addEventListener<K extends keyof StoreEventMap<T>>(
|
|
43
43
|
type: K,
|
|
44
44
|
listener: (event: StoreEventMap<T>[K]) => void,
|
|
@@ -64,22 +64,17 @@ interface StoreEventTarget<T extends UnknownRecord> extends EventTarget {
|
|
|
64
64
|
dispatchEvent(event: Event): boolean
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
type Store<T extends
|
|
68
|
-
[K in keyof T
|
|
69
|
-
? Store<T[K]>
|
|
70
|
-
: State<T[K]>
|
|
67
|
+
type Store<T extends UnknownRecordOrArray = UnknownRecord> = {
|
|
68
|
+
[K in keyof T]: T[K] extends UnknownRecord ? Store<T[K]> : State<T[K]>
|
|
71
69
|
} & StoreEventTarget<T> & {
|
|
72
70
|
[Symbol.toStringTag]: 'Store'
|
|
73
|
-
[Symbol.iterator](): IterableIterator<[
|
|
71
|
+
[Symbol.iterator](): IterableIterator<[keyof T, Signal<T[keyof T]>]>
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
add<K extends keyof T & string>(key: K, value: T[K]): void
|
|
73
|
+
add<K extends keyof T>(key: K, value: T[K]): void
|
|
77
74
|
get(): T
|
|
78
|
-
remove<K extends keyof T
|
|
75
|
+
remove<K extends keyof T>(key: K): void
|
|
79
76
|
set(value: T): void
|
|
80
77
|
update(updater: (value: T) => T): void
|
|
81
|
-
|
|
82
|
-
// Interals signals
|
|
83
78
|
size: State<number>
|
|
84
79
|
}
|
|
85
80
|
|
|
@@ -88,27 +83,36 @@ type Store<T extends UnknownRecord = UnknownRecord> = {
|
|
|
88
83
|
/**
|
|
89
84
|
* Create a new store with deeply nested reactive properties
|
|
90
85
|
*
|
|
86
|
+
* Supports both objects and arrays as initial values. Arrays are converted
|
|
87
|
+
* to records internally for storage but maintain their array type through
|
|
88
|
+
* the .get() method, which automatically converts objects with consecutive
|
|
89
|
+
* numeric keys back to arrays.
|
|
90
|
+
*
|
|
91
91
|
* @since 0.15.0
|
|
92
|
-
* @param {T} initialValue - initial object value of the store
|
|
93
|
-
* @returns {Store<T>} - new store with reactive properties
|
|
92
|
+
* @param {T} initialValue - initial object or array value of the store
|
|
93
|
+
* @returns {Store<T>} - new store with reactive properties that preserves the original type T
|
|
94
94
|
*/
|
|
95
|
-
const store = <T extends
|
|
95
|
+
const store = <T extends UnknownRecordOrArray>(initialValue: T): Store<T> => {
|
|
96
96
|
const watchers: Set<Watcher> = new Set()
|
|
97
97
|
const eventTarget = new EventTarget()
|
|
98
|
-
const signals: Map<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
> = new Map()
|
|
102
|
-
const cleanups = new Map<keyof T & string, Cleanup>()
|
|
98
|
+
const signals: Map<keyof T, Store<T[keyof T]> | State<T[keyof T]>> =
|
|
99
|
+
new Map()
|
|
100
|
+
const cleanups = new Map<keyof T, Cleanup>()
|
|
103
101
|
|
|
104
102
|
// Internal state
|
|
105
103
|
const size = state(0)
|
|
106
104
|
|
|
107
|
-
// Get current record
|
|
108
|
-
const current = () => {
|
|
105
|
+
// Get current record or array
|
|
106
|
+
const current = (): T => {
|
|
107
|
+
const keys = Array.from(signals.keys())
|
|
108
|
+
const arrayIndexes = validArrayIndexes(keys)
|
|
109
|
+
if (arrayIndexes)
|
|
110
|
+
return arrayIndexes.map(index =>
|
|
111
|
+
signals.get(String(index))?.get(),
|
|
112
|
+
) as unknown as T
|
|
109
113
|
const record: Partial<T> = {}
|
|
110
|
-
for (const [key,
|
|
111
|
-
record[key] =
|
|
114
|
+
for (const [key, signal] of signals) {
|
|
115
|
+
record[key] = signal.get()
|
|
112
116
|
}
|
|
113
117
|
return record as T
|
|
114
118
|
}
|
|
@@ -118,26 +122,25 @@ const store = <T extends UnknownRecord>(initialValue: T): Store<T> => {
|
|
|
118
122
|
eventTarget.dispatchEvent(new CustomEvent(type, { detail }))
|
|
119
123
|
|
|
120
124
|
// Add nested signal and effect
|
|
121
|
-
const
|
|
122
|
-
key
|
|
123
|
-
value
|
|
124
|
-
|
|
125
|
-
const signal = toMutableSignal<T[keyof T & string]>(value)
|
|
126
|
-
signals.set(key, signal)
|
|
125
|
+
const addProperty = <K extends keyof T>(key: K, value: T[K]) => {
|
|
126
|
+
const stringKey = String(key)
|
|
127
|
+
const signal = toMutableSignal(value)
|
|
128
|
+
signals.set(stringKey, signal as Store<T[keyof T]> | State<T[keyof T]>)
|
|
127
129
|
const cleanup = effect(() => {
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
130
|
-
emit('store-change', { [key]:
|
|
130
|
+
const currentValue = signal.get()
|
|
131
|
+
if (currentValue != null)
|
|
132
|
+
emit('store-change', { [key]: currentValue } as Partial<T>)
|
|
131
133
|
})
|
|
132
|
-
cleanups.set(
|
|
134
|
+
cleanups.set(stringKey, cleanup)
|
|
133
135
|
}
|
|
134
136
|
|
|
135
137
|
// Remove nested signal and effect
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
const removeProperty = <K extends keyof T>(key: K) => {
|
|
139
|
+
const stringKey = String(key)
|
|
140
|
+
signals.delete(stringKey)
|
|
141
|
+
const cleanup = cleanups.get(stringKey)
|
|
139
142
|
if (cleanup) cleanup()
|
|
140
|
-
cleanups.delete(
|
|
143
|
+
cleanups.delete(stringKey)
|
|
141
144
|
}
|
|
142
145
|
|
|
143
146
|
// Reconcile data and dispatch events
|
|
@@ -148,18 +151,18 @@ const store = <T extends UnknownRecord>(initialValue: T): Store<T> => {
|
|
|
148
151
|
if (Object.keys(changes.add).length) {
|
|
149
152
|
for (const key in changes.add) {
|
|
150
153
|
const value = changes.add[key]
|
|
151
|
-
if (value != null)
|
|
154
|
+
if (value != null) addProperty(key, value as T[keyof T])
|
|
152
155
|
}
|
|
153
156
|
emit('store-add', changes.add)
|
|
154
157
|
}
|
|
155
158
|
if (Object.keys(changes.change).length) {
|
|
156
159
|
for (const key in changes.change) {
|
|
157
|
-
const signal = signals.get(key
|
|
160
|
+
const signal = signals.get(key)
|
|
158
161
|
const value = changes.change[key]
|
|
159
162
|
if (
|
|
160
163
|
signal &&
|
|
161
164
|
value != null &&
|
|
162
|
-
hasMethod<Signal<T[keyof T
|
|
165
|
+
hasMethod<Signal<T[keyof T]>>(signal, 'set')
|
|
163
166
|
)
|
|
164
167
|
signal.set(value)
|
|
165
168
|
}
|
|
@@ -167,7 +170,7 @@ const store = <T extends UnknownRecord>(initialValue: T): Store<T> => {
|
|
|
167
170
|
}
|
|
168
171
|
if (Object.keys(changes.remove).length) {
|
|
169
172
|
for (const key in changes.remove) {
|
|
170
|
-
|
|
173
|
+
removeProperty(key)
|
|
171
174
|
}
|
|
172
175
|
emit('store-remove', changes.remove)
|
|
173
176
|
}
|
|
@@ -184,8 +187,8 @@ const store = <T extends UnknownRecord>(initialValue: T): Store<T> => {
|
|
|
184
187
|
// Queue initial additions event to allow listeners to be added first
|
|
185
188
|
setTimeout(() => {
|
|
186
189
|
const initialAdditionsEvent = new CustomEvent('store-add', {
|
|
187
|
-
detail: initialValue
|
|
188
|
-
})
|
|
190
|
+
detail: initialValue,
|
|
191
|
+
})
|
|
189
192
|
eventTarget.dispatchEvent(initialAdditionsEvent)
|
|
190
193
|
}, 0)
|
|
191
194
|
|
|
@@ -204,17 +207,12 @@ const store = <T extends UnknownRecord>(initialValue: T): Store<T> => {
|
|
|
204
207
|
// Return proxy directly with integrated signal methods
|
|
205
208
|
return new Proxy({} as Store<T>, {
|
|
206
209
|
get(_target, prop) {
|
|
207
|
-
const key = String(prop)
|
|
208
|
-
|
|
209
210
|
// Handle signal methods and size property
|
|
210
211
|
switch (prop) {
|
|
211
212
|
case 'add':
|
|
212
|
-
return <K extends keyof T
|
|
213
|
-
k: K,
|
|
214
|
-
v: T[K],
|
|
215
|
-
): void => {
|
|
213
|
+
return <K extends keyof T>(k: K, v: T[K]): void => {
|
|
216
214
|
if (!signals.has(k)) {
|
|
217
|
-
|
|
215
|
+
addProperty(k, v)
|
|
218
216
|
notify(watchers)
|
|
219
217
|
emit('store-add', {
|
|
220
218
|
[k]: v,
|
|
@@ -228,9 +226,9 @@ const store = <T extends UnknownRecord>(initialValue: T): Store<T> => {
|
|
|
228
226
|
return current()
|
|
229
227
|
}
|
|
230
228
|
case 'remove':
|
|
231
|
-
return <K extends keyof T
|
|
229
|
+
return <K extends keyof T>(k: K): void => {
|
|
232
230
|
if (signals.has(k)) {
|
|
233
|
-
|
|
231
|
+
removeProperty(k)
|
|
234
232
|
notify(watchers)
|
|
235
233
|
emit('store-remove', { [k]: UNSET } as Partial<T>)
|
|
236
234
|
size.set(signals.size)
|
|
@@ -267,13 +265,13 @@ const store = <T extends UnknownRecord>(initialValue: T): Store<T> => {
|
|
|
267
265
|
if (prop === Symbol.iterator) {
|
|
268
266
|
return function* () {
|
|
269
267
|
for (const [key, signal] of signals) {
|
|
270
|
-
yield [key, signal
|
|
268
|
+
yield [key, signal]
|
|
271
269
|
}
|
|
272
270
|
}
|
|
273
271
|
}
|
|
274
272
|
|
|
275
273
|
// Handle data properties - return signals
|
|
276
|
-
return signals.get(
|
|
274
|
+
return signals.get(String(prop))
|
|
277
275
|
},
|
|
278
276
|
has(_target, prop) {
|
|
279
277
|
const key = String(prop)
|
|
@@ -285,7 +283,7 @@ const store = <T extends UnknownRecord>(initialValue: T): Store<T> => {
|
|
|
285
283
|
)
|
|
286
284
|
},
|
|
287
285
|
ownKeys() {
|
|
288
|
-
return Array.from(signals.keys())
|
|
286
|
+
return Array.from(signals.keys()).map(key => String(key))
|
|
289
287
|
},
|
|
290
288
|
getOwnPropertyDescriptor(_target, prop) {
|
|
291
289
|
const signal = signals.get(String(prop))
|
|
@@ -308,8 +306,9 @@ const store = <T extends UnknownRecord>(initialValue: T): Store<T> => {
|
|
|
308
306
|
* @param {unknown} value - value to check
|
|
309
307
|
* @returns {boolean} - true if the value is a Store instance, false otherwise
|
|
310
308
|
*/
|
|
311
|
-
const isStore = <T extends
|
|
312
|
-
|
|
309
|
+
const isStore = <T extends UnknownRecordOrArray>(
|
|
310
|
+
value: unknown,
|
|
311
|
+
): value is Store<T> => isObjectOfType(value, TYPE_STORE)
|
|
313
312
|
|
|
314
313
|
/* === Exports === */
|
|
315
314
|
|
package/src/util.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/* === Utility Functions === */
|
|
2
2
|
|
|
3
|
-
const isNumber = /*#__PURE__*/ (value: unknown): value is number =>
|
|
4
|
-
typeof value === 'number'
|
|
5
|
-
|
|
6
3
|
const isString = /*#__PURE__*/ (value: unknown): value is string =>
|
|
7
4
|
typeof value === 'string'
|
|
8
5
|
|
|
6
|
+
const isNumber = /*#__PURE__*/ (value: unknown): value is number =>
|
|
7
|
+
typeof value === 'number'
|
|
8
|
+
|
|
9
9
|
const isFunction = /*#__PURE__*/ <T>(
|
|
10
10
|
fn: unknown,
|
|
11
11
|
): fn is (...args: unknown[]) => T => typeof fn === 'function'
|
|
@@ -24,17 +24,16 @@ const isRecord = /*#__PURE__*/ <T extends Record<string, unknown>>(
|
|
|
24
24
|
value: unknown,
|
|
25
25
|
): value is T => isObjectOfType(value, 'Object')
|
|
26
26
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return record
|
|
27
|
+
const validArrayIndexes = /*#__PURE__*/ (
|
|
28
|
+
keys: Array<PropertyKey>,
|
|
29
|
+
): number[] | null => {
|
|
30
|
+
if (!keys.length) return null
|
|
31
|
+
const indexes = keys.map(k =>
|
|
32
|
+
isString(k) ? parseInt(k, 10) : isNumber(k) ? k : NaN,
|
|
33
|
+
)
|
|
34
|
+
return indexes.every(index => Number.isFinite(index) && index >= 0)
|
|
35
|
+
? indexes.sort((a, b) => a - b)
|
|
36
|
+
: null
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
const hasMethod = /*#__PURE__*/ <
|
|
@@ -61,14 +60,13 @@ class CircularDependencyError extends Error {
|
|
|
61
60
|
/* === Exports === */
|
|
62
61
|
|
|
63
62
|
export {
|
|
64
|
-
isNumber,
|
|
65
63
|
isString,
|
|
64
|
+
isNumber,
|
|
66
65
|
isFunction,
|
|
67
66
|
isAsyncFunction,
|
|
68
67
|
isObjectOfType,
|
|
69
68
|
isRecord,
|
|
70
|
-
|
|
71
|
-
arrayToRecord,
|
|
69
|
+
validArrayIndexes,
|
|
72
70
|
hasMethod,
|
|
73
71
|
isAbortError,
|
|
74
72
|
toError,
|