@zeix/cause-effect 0.15.2 → 0.16.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/.ai-context.md +254 -0
- package/.cursorrules +54 -0
- package/.github/copilot-instructions.md +132 -0
- package/CLAUDE.md +319 -0
- package/README.md +136 -166
- package/eslint.config.js +1 -1
- package/index.dev.js +263 -262
- package/index.js +1 -1
- package/index.ts +24 -23
- package/package.json +1 -1
- package/src/computed.ts +54 -44
- package/src/diff.ts +24 -21
- package/src/effect.ts +15 -12
- package/src/errors.ts +8 -0
- package/src/signal.ts +6 -6
- package/src/state.ts +44 -48
- package/src/store.ts +236 -309
- package/src/system.ts +122 -0
- package/src/util.ts +3 -12
- package/test/batch.test.ts +18 -11
- package/test/benchmark.test.ts +4 -4
- package/test/computed.test.ts +508 -72
- package/test/effect.test.ts +61 -61
- package/test/match.test.ts +25 -25
- package/test/resolve.test.ts +16 -16
- package/test/signal.test.ts +9 -9
- package/test/state.test.ts +212 -25
- package/test/store.test.ts +1007 -1357
- package/test/util/dependency-graph.ts +1 -1
- package/types/index.d.ts +9 -9
- package/types/src/collection.d.ts +26 -0
- package/types/src/computed.d.ts +9 -9
- package/types/src/diff.d.ts +7 -7
- package/types/src/effect.d.ts +3 -3
- package/types/src/errors.d.ts +4 -1
- package/types/src/state.d.ts +5 -5
- package/types/src/store.d.ts +29 -44
- package/types/src/system.d.ts +44 -0
- package/types/src/util.d.ts +1 -2
- package/src/scheduler.ts +0 -172
package/src/store.ts
CHANGED
|
@@ -2,11 +2,10 @@ import { isComputed } from './computed'
|
|
|
2
2
|
import {
|
|
3
3
|
type ArrayToRecord,
|
|
4
4
|
diff,
|
|
5
|
+
type PartialRecord,
|
|
5
6
|
type UnknownArray,
|
|
6
7
|
type UnknownRecord,
|
|
7
|
-
type UnknownRecordOrArray,
|
|
8
8
|
} from './diff'
|
|
9
|
-
import { effect } from './effect'
|
|
10
9
|
import {
|
|
11
10
|
InvalidSignalValueError,
|
|
12
11
|
NullishSignalValueError,
|
|
@@ -14,15 +13,17 @@ import {
|
|
|
14
13
|
StoreKeyRangeError,
|
|
15
14
|
StoreKeyReadonlyError,
|
|
16
15
|
} from './errors'
|
|
16
|
+
import { isMutableSignal, type Signal } from './signal'
|
|
17
|
+
import { createState, isState, type State } from './state'
|
|
17
18
|
import {
|
|
18
19
|
batch,
|
|
19
20
|
type Cleanup,
|
|
21
|
+
createWatcher,
|
|
20
22
|
notify,
|
|
23
|
+
observe,
|
|
21
24
|
subscribe,
|
|
22
25
|
type Watcher,
|
|
23
|
-
} from './
|
|
24
|
-
import { isMutableSignal, type Signal } from './signal'
|
|
25
|
-
import { isState, type State, state } from './state'
|
|
26
|
+
} from './system'
|
|
26
27
|
import {
|
|
27
28
|
isFunction,
|
|
28
29
|
isObjectOfType,
|
|
@@ -37,51 +38,27 @@ import {
|
|
|
37
38
|
|
|
38
39
|
type ArrayItem<T> = T extends readonly (infer U extends {})[] ? U : never
|
|
39
40
|
|
|
40
|
-
type
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
type StoreChanges<T> = {
|
|
42
|
+
add: PartialRecord<T>
|
|
43
|
+
change: PartialRecord<T>
|
|
44
|
+
remove: PartialRecord<T>
|
|
45
|
+
sort: string[]
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
addEventListener<K extends keyof StoreEventMap<T>>(
|
|
50
|
-
type: K,
|
|
51
|
-
listener: (event: StoreEventMap<T>[K]) => void,
|
|
52
|
-
options?: boolean | AddEventListenerOptions,
|
|
53
|
-
): void
|
|
54
|
-
|
|
55
|
-
removeEventListener<K extends keyof StoreEventMap<T>>(
|
|
56
|
-
type: K,
|
|
57
|
-
listener: (event: StoreEventMap<T>[K]) => void,
|
|
58
|
-
options?: boolean | EventListenerOptions,
|
|
59
|
-
): void
|
|
60
|
-
|
|
61
|
-
dispatchEvent(event: Event): boolean
|
|
48
|
+
type StoreListeners<T> = {
|
|
49
|
+
[K in keyof StoreChanges<T>]: Set<(change: StoreChanges<T>[K]) => void>
|
|
62
50
|
}
|
|
63
51
|
|
|
64
|
-
interface BaseStore
|
|
65
|
-
extends StoreEventTarget<T> {
|
|
52
|
+
interface BaseStore {
|
|
66
53
|
readonly [Symbol.toStringTag]: 'Store'
|
|
67
|
-
|
|
68
|
-
set(value: T): void
|
|
69
|
-
update(fn: (value: T) => T): void
|
|
70
|
-
sort<
|
|
71
|
-
U = T extends UnknownArray ? ArrayItem<T> : T[Extract<keyof T, string>],
|
|
72
|
-
>(
|
|
73
|
-
compareFn?: (a: U, b: U) => number,
|
|
74
|
-
): void
|
|
75
|
-
readonly size: State<number>
|
|
54
|
+
readonly length: number
|
|
76
55
|
}
|
|
77
56
|
|
|
78
|
-
type RecordStore<T extends UnknownRecord> = BaseStore
|
|
57
|
+
type RecordStore<T extends UnknownRecord> = BaseStore & {
|
|
79
58
|
[K in keyof T]: T[K] extends readonly unknown[] | Record<string, unknown>
|
|
80
59
|
? Store<T[K]>
|
|
81
60
|
: State<T[K]>
|
|
82
61
|
} & {
|
|
83
|
-
add<K extends Extract<keyof T, string>>(key: K, value: T[K]): void
|
|
84
|
-
remove<K extends Extract<keyof T, string>>(key: K): void
|
|
85
62
|
[Symbol.iterator](): IterableIterator<
|
|
86
63
|
[
|
|
87
64
|
Extract<keyof T, string>,
|
|
@@ -92,49 +69,45 @@ type RecordStore<T extends UnknownRecord> = BaseStore<T> & {
|
|
|
92
69
|
: State<T[Extract<keyof T, string>]>,
|
|
93
70
|
]
|
|
94
71
|
>
|
|
72
|
+
add<K extends Extract<keyof T, string>>(key: K, value: T[K]): void
|
|
73
|
+
get(): T
|
|
74
|
+
set(value: T): void
|
|
75
|
+
update(fn: (value: T) => T): void
|
|
76
|
+
sort<U = T[Extract<keyof T, string>]>(
|
|
77
|
+
compareFn?: (a: U, b: U) => number,
|
|
78
|
+
): void
|
|
79
|
+
on<K extends keyof StoreChanges<T>>(
|
|
80
|
+
type: K,
|
|
81
|
+
listener: (change: StoreChanges<T>[K]) => void,
|
|
82
|
+
): Cleanup
|
|
83
|
+
remove<K extends Extract<keyof T, string>>(key: K): void
|
|
95
84
|
}
|
|
96
85
|
|
|
97
|
-
type ArrayStore<T extends UnknownArray> = BaseStore
|
|
98
|
-
|
|
86
|
+
type ArrayStore<T extends UnknownArray> = BaseStore & {
|
|
87
|
+
[Symbol.iterator](): IterableIterator<
|
|
88
|
+
ArrayItem<T> extends readonly unknown[] | Record<string, unknown>
|
|
89
|
+
? Store<ArrayItem<T>>
|
|
90
|
+
: State<ArrayItem<T>>
|
|
91
|
+
>
|
|
92
|
+
readonly [Symbol.isConcatSpreadable]: boolean
|
|
99
93
|
[n: number]: ArrayItem<T> extends
|
|
100
94
|
| readonly unknown[]
|
|
101
95
|
| Record<string, unknown>
|
|
102
96
|
? Store<ArrayItem<T>>
|
|
103
97
|
: State<ArrayItem<T>>
|
|
104
98
|
add(value: ArrayItem<T>): void
|
|
99
|
+
get(): T
|
|
100
|
+
set(value: T): void
|
|
101
|
+
update(fn: (value: T) => T): void
|
|
102
|
+
sort<U = ArrayItem<T>>(compareFn?: (a: U, b: U) => number): void
|
|
103
|
+
on<K extends keyof StoreChanges<T>>(
|
|
104
|
+
type: K,
|
|
105
|
+
listener: (change: StoreChanges<T>[K]) => void,
|
|
106
|
+
): Cleanup
|
|
105
107
|
remove(index: number): void
|
|
106
|
-
[Symbol.iterator](): IterableIterator<
|
|
107
|
-
ArrayItem<T> extends readonly unknown[] | Record<string, unknown>
|
|
108
|
-
? Store<ArrayItem<T>>
|
|
109
|
-
: State<ArrayItem<T>>
|
|
110
|
-
>
|
|
111
|
-
readonly [Symbol.isConcatSpreadable]: boolean
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
interface StoreAddEvent<T extends UnknownRecord | UnknownArray>
|
|
115
|
-
extends CustomEvent {
|
|
116
|
-
type: 'store-add'
|
|
117
|
-
detail: Partial<T>
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
interface StoreChangeEvent<T extends UnknownRecord | UnknownArray>
|
|
121
|
-
extends CustomEvent {
|
|
122
|
-
type: 'store-change'
|
|
123
|
-
detail: Partial<T>
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
interface StoreRemoveEvent<T extends UnknownRecord | UnknownArray>
|
|
127
|
-
extends CustomEvent {
|
|
128
|
-
type: 'store-remove'
|
|
129
|
-
detail: Partial<T>
|
|
130
108
|
}
|
|
131
109
|
|
|
132
|
-
|
|
133
|
-
type: 'store-sort'
|
|
134
|
-
detail: string[]
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
type Store<T> = T extends UnknownRecord
|
|
110
|
+
type Store<T extends UnknownRecord | UnknownArray> = T extends UnknownRecord
|
|
138
111
|
? RecordStore<T>
|
|
139
112
|
: T extends UnknownArray
|
|
140
113
|
? ArrayStore<T>
|
|
@@ -144,11 +117,6 @@ type Store<T> = T extends UnknownRecord
|
|
|
144
117
|
|
|
145
118
|
const TYPE_STORE = 'Store'
|
|
146
119
|
|
|
147
|
-
const STORE_EVENT_ADD = 'store-add'
|
|
148
|
-
const STORE_EVENT_CHANGE = 'store-change'
|
|
149
|
-
const STORE_EVENT_REMOVE = 'store-remove'
|
|
150
|
-
const STORE_EVENT_SORT = 'store-sort'
|
|
151
|
-
|
|
152
120
|
/* === Functions === */
|
|
153
121
|
|
|
154
122
|
/**
|
|
@@ -163,32 +131,39 @@ const STORE_EVENT_SORT = 'store-sort'
|
|
|
163
131
|
* @param {T} initialValue - initial object or array value of the store
|
|
164
132
|
* @returns {Store<T>} - new store with reactive properties that preserves the original type T
|
|
165
133
|
*/
|
|
166
|
-
const
|
|
134
|
+
const createStore = <T extends UnknownRecord | UnknownArray>(
|
|
167
135
|
initialValue: T,
|
|
168
136
|
): Store<T> => {
|
|
137
|
+
if (initialValue == null) throw new NullishSignalValueError('store')
|
|
138
|
+
|
|
169
139
|
const watchers = new Set<Watcher>()
|
|
170
|
-
const
|
|
140
|
+
const listeners: StoreListeners<T> = {
|
|
141
|
+
add: new Set<(change: PartialRecord<T>) => void>(),
|
|
142
|
+
change: new Set<(change: PartialRecord<T>) => void>(),
|
|
143
|
+
remove: new Set<(change: PartialRecord<T>) => void>(),
|
|
144
|
+
sort: new Set<(change: string[]) => void>(),
|
|
145
|
+
}
|
|
171
146
|
const signals = new Map<string, Signal<T[Extract<keyof T, string>] & {}>>()
|
|
172
|
-
const
|
|
147
|
+
const signalWatchers = new Map<string, Watcher>()
|
|
173
148
|
|
|
174
149
|
// Determine if this is an array-like store at creation time
|
|
175
150
|
const isArrayLike = Array.isArray(initialValue)
|
|
176
151
|
|
|
177
|
-
// Internal state
|
|
178
|
-
const size = state(0)
|
|
179
|
-
|
|
180
152
|
// Get current record
|
|
181
153
|
const current = () => {
|
|
182
154
|
const record: Record<string, unknown> = {}
|
|
183
|
-
for (const [key, signal] of signals)
|
|
184
|
-
record[key] = signal.get()
|
|
185
|
-
}
|
|
155
|
+
for (const [key, signal] of signals) record[key] = signal.get()
|
|
186
156
|
return record
|
|
187
157
|
}
|
|
188
158
|
|
|
189
|
-
// Emit
|
|
190
|
-
const emit = <
|
|
191
|
-
|
|
159
|
+
// Emit change notifications
|
|
160
|
+
const emit = <K extends keyof StoreChanges<T>>(
|
|
161
|
+
key: K,
|
|
162
|
+
changes: StoreChanges<T>[K],
|
|
163
|
+
) => {
|
|
164
|
+
Object.freeze(changes)
|
|
165
|
+
for (const listener of listeners[key]) listener(changes)
|
|
166
|
+
}
|
|
192
167
|
|
|
193
168
|
// Get sorted indexes
|
|
194
169
|
const getSortedIndexes = () =>
|
|
@@ -214,59 +189,47 @@ const store = <T extends UnknownRecord | UnknownArray>(
|
|
|
214
189
|
}
|
|
215
190
|
|
|
216
191
|
// Add nested signal and effect
|
|
217
|
-
const addProperty =
|
|
218
|
-
key:
|
|
219
|
-
value: T
|
|
192
|
+
const addProperty = (
|
|
193
|
+
key: string,
|
|
194
|
+
value: ArrayItem<T> | T[keyof T],
|
|
220
195
|
single = false,
|
|
221
196
|
): boolean => {
|
|
222
197
|
if (!isValidValue(key, value)) return false
|
|
223
198
|
const signal =
|
|
224
199
|
isState(value) || isStore(value)
|
|
225
200
|
? value
|
|
226
|
-
: isRecord(value)
|
|
227
|
-
?
|
|
228
|
-
:
|
|
229
|
-
? store(value)
|
|
230
|
-
: state(value)
|
|
201
|
+
: isRecord(value) || Array.isArray(value)
|
|
202
|
+
? createStore(value)
|
|
203
|
+
: createState(value)
|
|
231
204
|
// @ts-expect-error non-matching signal types
|
|
232
205
|
signals.set(key, signal)
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
cleanups.set(key, cleanup)
|
|
206
|
+
const watcher = createWatcher(() =>
|
|
207
|
+
observe(() => {
|
|
208
|
+
emit('change', { [key]: signal.get() } as PartialRecord<T>)
|
|
209
|
+
}, watcher),
|
|
210
|
+
)
|
|
211
|
+
watcher()
|
|
212
|
+
signalWatchers.set(key, watcher)
|
|
241
213
|
|
|
242
214
|
if (single) {
|
|
243
|
-
size.set(signals.size)
|
|
244
215
|
notify(watchers)
|
|
245
|
-
emit(
|
|
246
|
-
[key]: value,
|
|
247
|
-
} as unknown as Partial<T>)
|
|
216
|
+
emit('add', { [key]: value } as PartialRecord<T>)
|
|
248
217
|
}
|
|
249
218
|
return true
|
|
250
219
|
}
|
|
251
220
|
|
|
252
221
|
// Remove nested signal and effect
|
|
253
|
-
const removeProperty =
|
|
254
|
-
key: K,
|
|
255
|
-
single = false,
|
|
256
|
-
) => {
|
|
222
|
+
const removeProperty = (key: string, single = false) => {
|
|
257
223
|
const ok = signals.delete(key)
|
|
258
224
|
if (ok) {
|
|
259
|
-
const
|
|
260
|
-
if (
|
|
261
|
-
|
|
225
|
+
const watcher = signalWatchers.get(key)
|
|
226
|
+
if (watcher) watcher.cleanup()
|
|
227
|
+
signalWatchers.delete(key)
|
|
262
228
|
}
|
|
263
229
|
|
|
264
230
|
if (single) {
|
|
265
|
-
size.set(signals.size)
|
|
266
231
|
notify(watchers)
|
|
267
|
-
emit(
|
|
268
|
-
[key]: UNSET,
|
|
269
|
-
} as unknown as Partial<T>)
|
|
232
|
+
emit('remove', { [key]: UNSET } as PartialRecord<T>)
|
|
270
233
|
}
|
|
271
234
|
return ok
|
|
272
235
|
}
|
|
@@ -285,21 +248,16 @@ const store = <T extends UnknownRecord | UnknownArray>(
|
|
|
285
248
|
batch(() => {
|
|
286
249
|
// Additions
|
|
287
250
|
if (Object.keys(changes.add).length) {
|
|
288
|
-
for (const key in changes.add)
|
|
289
|
-
|
|
290
|
-
addProperty(
|
|
291
|
-
key as Extract<keyof T, string>,
|
|
292
|
-
value as T[Extract<keyof T, string>] & {},
|
|
293
|
-
)
|
|
294
|
-
}
|
|
251
|
+
for (const key in changes.add)
|
|
252
|
+
addProperty(key, changes.add[key] ?? UNSET)
|
|
295
253
|
|
|
296
254
|
// Queue initial additions event to allow listeners to be added first
|
|
297
255
|
if (initialRun) {
|
|
298
256
|
setTimeout(() => {
|
|
299
|
-
emit(
|
|
257
|
+
emit('add', changes.add)
|
|
300
258
|
}, 0)
|
|
301
259
|
} else {
|
|
302
|
-
emit
|
|
260
|
+
emit('add', changes.add)
|
|
303
261
|
}
|
|
304
262
|
}
|
|
305
263
|
|
|
@@ -309,206 +267,185 @@ const store = <T extends UnknownRecord | UnknownArray>(
|
|
|
309
267
|
const value = changes.change[key]
|
|
310
268
|
if (!isValidValue(key, value)) continue
|
|
311
269
|
const signal = signals.get(key as Extract<keyof T, string>)
|
|
312
|
-
if (isMutableSignal(signal))
|
|
313
|
-
signal.set(value as T[Extract<keyof T, string>] & {})
|
|
270
|
+
if (isMutableSignal(signal)) signal.set(value)
|
|
314
271
|
else
|
|
315
272
|
throw new StoreKeyReadonlyError(key, valueString(value))
|
|
316
273
|
}
|
|
317
|
-
emit(
|
|
274
|
+
emit('change', changes.change)
|
|
318
275
|
}
|
|
319
276
|
|
|
320
277
|
// Removals
|
|
321
278
|
if (Object.keys(changes.remove).length) {
|
|
322
|
-
for (const key in changes.remove)
|
|
323
|
-
|
|
324
|
-
emit(STORE_EVENT_REMOVE, changes.remove as Partial<T>)
|
|
279
|
+
for (const key in changes.remove) removeProperty(key)
|
|
280
|
+
emit('remove', changes.remove)
|
|
325
281
|
}
|
|
326
|
-
|
|
327
|
-
size.set(signals.size)
|
|
328
282
|
})
|
|
329
283
|
|
|
330
284
|
return changes.changed
|
|
331
285
|
}
|
|
332
286
|
|
|
333
|
-
// Initialize data
|
|
287
|
+
// Initialize data
|
|
334
288
|
reconcile({} as T, initialValue, true)
|
|
335
289
|
|
|
336
290
|
// Methods and Properties
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
291
|
+
const store: Record<PropertyKey, unknown> = {}
|
|
292
|
+
Object.defineProperties(store, {
|
|
293
|
+
[Symbol.toStringTag]: {
|
|
294
|
+
value: TYPE_STORE,
|
|
295
|
+
},
|
|
296
|
+
[Symbol.isConcatSpreadable]: {
|
|
297
|
+
value: isArrayLike,
|
|
298
|
+
},
|
|
299
|
+
[Symbol.iterator]: {
|
|
300
|
+
value: isArrayLike
|
|
301
|
+
? function* () {
|
|
302
|
+
const indexes = getSortedIndexes()
|
|
303
|
+
for (const index of indexes) {
|
|
304
|
+
const signal = signals.get(String(index))
|
|
305
|
+
if (signal) yield signal
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
: function* () {
|
|
309
|
+
for (const [key, signal] of signals) yield [key, signal]
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
add: {
|
|
313
|
+
value: isArrayLike
|
|
314
|
+
? (v: ArrayItem<T>): void => {
|
|
315
|
+
addProperty(String(signals.size), v, true)
|
|
316
|
+
}
|
|
317
|
+
: <K extends Extract<keyof T, string>>(k: K, v: T[K]): void => {
|
|
318
|
+
if (!signals.has(k)) addProperty(k, v, true)
|
|
319
|
+
else throw new StoreKeyExistsError(k, valueString(v))
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
get: {
|
|
323
|
+
value: (): T => {
|
|
324
|
+
subscribe(watchers)
|
|
325
|
+
return recordToArray(current()) as T
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
remove: {
|
|
329
|
+
value: isArrayLike
|
|
330
|
+
? (index: number): void => {
|
|
331
|
+
const currentArray = recordToArray(current()) as T
|
|
332
|
+
const currentLength = signals.size
|
|
333
|
+
if (
|
|
334
|
+
!Array.isArray(currentArray) ||
|
|
335
|
+
index <= -currentLength ||
|
|
336
|
+
index >= currentLength
|
|
337
|
+
)
|
|
338
|
+
throw new StoreKeyRangeError(index)
|
|
339
|
+
const newArray = [...currentArray]
|
|
340
|
+
newArray.splice(index, 1)
|
|
341
|
+
|
|
342
|
+
if (reconcile(currentArray, newArray as unknown as T))
|
|
343
|
+
notify(watchers)
|
|
344
|
+
}
|
|
345
|
+
: (k: string): void => {
|
|
346
|
+
if (signals.has(k)) removeProperty(k, true)
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
set: {
|
|
350
|
+
value: (v: T): void => {
|
|
351
|
+
if (reconcile(current() as T, v)) {
|
|
352
|
+
notify(watchers)
|
|
353
|
+
if (UNSET === v) watchers.clear()
|
|
343
354
|
}
|
|
344
|
-
|
|
345
|
-
if (!signals.has(k)) addProperty(k, v, true)
|
|
346
|
-
else throw new StoreKeyExistsError(k, valueString(v))
|
|
347
|
-
},
|
|
348
|
-
get: (): T => {
|
|
349
|
-
subscribe(watchers)
|
|
350
|
-
return recordToArray(current()) as T
|
|
355
|
+
},
|
|
351
356
|
},
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
357
|
+
update: {
|
|
358
|
+
value: (fn: (v: T) => T): void => {
|
|
359
|
+
const oldValue = current()
|
|
360
|
+
const newValue = fn(recordToArray(oldValue) as T)
|
|
361
|
+
if (reconcile(oldValue as T, newValue)) {
|
|
362
|
+
notify(watchers)
|
|
363
|
+
if (UNSET === newValue) watchers.clear()
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
sort: {
|
|
368
|
+
value: (
|
|
369
|
+
compareFn?: <
|
|
370
|
+
U = T extends UnknownArray
|
|
371
|
+
? ArrayItem<T>
|
|
372
|
+
: T[Extract<keyof T, string>],
|
|
373
|
+
>(
|
|
374
|
+
a: U,
|
|
375
|
+
b: U,
|
|
376
|
+
) => number,
|
|
377
|
+
): void => {
|
|
378
|
+
// Get all entries as [key, value] pairs
|
|
379
|
+
const entries = Array.from(signals.entries())
|
|
380
|
+
.map(([key, signal]) => [key, signal.get()])
|
|
381
|
+
.sort(
|
|
382
|
+
compareFn
|
|
383
|
+
? (a, b) => compareFn(a[1], b[1])
|
|
384
|
+
: (a, b) =>
|
|
385
|
+
String(a[1]).localeCompare(String(b[1])),
|
|
360
386
|
)
|
|
361
|
-
throw new StoreKeyRangeError(index)
|
|
362
|
-
const newArray = [...currentArray]
|
|
363
|
-
newArray.splice(index, 1)
|
|
364
387
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
388
|
+
// Create array of original keys in their new sorted order
|
|
389
|
+
const newOrder: string[] = entries.map(([key]) => String(key))
|
|
390
|
+
const newSignals = new Map<
|
|
391
|
+
string,
|
|
392
|
+
Signal<T[Extract<keyof T, string>] & {}>
|
|
393
|
+
>()
|
|
394
|
+
|
|
395
|
+
entries.forEach(([key], newIndex) => {
|
|
396
|
+
const oldKey = String(key)
|
|
397
|
+
const newKey = isArrayLike ? String(newIndex) : String(key)
|
|
398
|
+
const signal = signals.get(oldKey)
|
|
399
|
+
if (signal) newSignals.set(newKey, signal)
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
// Replace signals map
|
|
403
|
+
signals.clear()
|
|
404
|
+
newSignals.forEach((signal, key) => signals.set(key, signal))
|
|
373
405
|
notify(watchers)
|
|
374
|
-
|
|
375
|
-
}
|
|
406
|
+
emit('sort', newOrder)
|
|
407
|
+
},
|
|
376
408
|
},
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
409
|
+
on: {
|
|
410
|
+
value: <K extends keyof StoreChanges<T>>(
|
|
411
|
+
type: K,
|
|
412
|
+
listener: (change: StoreChanges<T>[K]) => void,
|
|
413
|
+
): Cleanup => {
|
|
414
|
+
listeners[type].add(listener)
|
|
415
|
+
return () => listeners[type].delete(listener)
|
|
416
|
+
},
|
|
384
417
|
},
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
>(
|
|
391
|
-
a: U,
|
|
392
|
-
b: U,
|
|
393
|
-
) => number,
|
|
394
|
-
): void => {
|
|
395
|
-
// Get all entries as [key, value] pairs
|
|
396
|
-
const entries = Array.from(signals.entries())
|
|
397
|
-
.map(
|
|
398
|
-
([key, signal]) =>
|
|
399
|
-
[key, signal.get()] as [
|
|
400
|
-
string,
|
|
401
|
-
T[Extract<keyof T, string>],
|
|
402
|
-
],
|
|
403
|
-
)
|
|
404
|
-
.sort(
|
|
405
|
-
compareFn
|
|
406
|
-
? (a, b) => compareFn(a[1], b[1])
|
|
407
|
-
: (a, b) => String(a[1]).localeCompare(String(b[1])),
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
// Create array of original keys in their new sorted order
|
|
411
|
-
const newOrder: string[] = entries.map(([key]) => String(key))
|
|
412
|
-
const newSignals = new Map<
|
|
413
|
-
string,
|
|
414
|
-
Signal<T[Extract<keyof T, string>] & {}>
|
|
415
|
-
>()
|
|
416
|
-
|
|
417
|
-
entries.forEach(([key], newIndex) => {
|
|
418
|
-
const oldKey = String(key)
|
|
419
|
-
const newKey = isArrayLike ? String(newIndex) : String(key)
|
|
420
|
-
|
|
421
|
-
const signal = signals.get(oldKey)
|
|
422
|
-
if (signal) newSignals.set(newKey, signal)
|
|
423
|
-
})
|
|
424
|
-
|
|
425
|
-
// Replace signals map
|
|
426
|
-
signals.clear()
|
|
427
|
-
newSignals.forEach((signal, key) => signals.set(key, signal))
|
|
428
|
-
notify(watchers)
|
|
429
|
-
emit(STORE_EVENT_SORT, newOrder)
|
|
418
|
+
length: {
|
|
419
|
+
get(): number {
|
|
420
|
+
subscribe(watchers)
|
|
421
|
+
return signals.size
|
|
422
|
+
},
|
|
430
423
|
},
|
|
431
|
-
|
|
432
|
-
removeEventListener: eventTarget.removeEventListener.bind(eventTarget),
|
|
433
|
-
dispatchEvent: eventTarget.dispatchEvent.bind(eventTarget),
|
|
434
|
-
size,
|
|
435
|
-
}
|
|
424
|
+
})
|
|
436
425
|
|
|
437
426
|
// Return proxy directly with integrated signal methods
|
|
438
|
-
return new Proxy(
|
|
439
|
-
get(
|
|
440
|
-
|
|
441
|
-
if (prop === Symbol.toStringTag) return TYPE_STORE
|
|
442
|
-
if (prop === Symbol.isConcatSpreadable) return isArrayLike
|
|
443
|
-
if (prop === Symbol.iterator)
|
|
444
|
-
return isArrayLike
|
|
445
|
-
? function* () {
|
|
446
|
-
const indexes = getSortedIndexes()
|
|
447
|
-
for (const index of indexes) {
|
|
448
|
-
const signal = signals.get(
|
|
449
|
-
String(index) as Extract<keyof T, string>,
|
|
450
|
-
)
|
|
451
|
-
if (signal) yield signal
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
: function* () {
|
|
455
|
-
for (const [key, signal] of signals)
|
|
456
|
-
yield [key, signal]
|
|
457
|
-
}
|
|
427
|
+
return new Proxy(store as Store<T>, {
|
|
428
|
+
get(target, prop) {
|
|
429
|
+
if (prop in target) return Reflect.get(target, prop)
|
|
458
430
|
if (isSymbol(prop)) return undefined
|
|
459
|
-
|
|
460
|
-
// Methods and Properties
|
|
461
|
-
if (prop in s) return s[prop]
|
|
462
|
-
if (prop === 'length' && isArrayLike) {
|
|
463
|
-
subscribe(watchers)
|
|
464
|
-
return size.get()
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// Signals
|
|
468
|
-
return signals.get(prop as Extract<keyof T, string>)
|
|
431
|
+
return signals.get(prop)
|
|
469
432
|
},
|
|
470
|
-
has(
|
|
471
|
-
|
|
472
|
-
return (
|
|
473
|
-
(stringProp &&
|
|
474
|
-
signals.has(stringProp as Extract<keyof T, string>)) ||
|
|
475
|
-
Object.keys(s).includes(stringProp) ||
|
|
476
|
-
prop === Symbol.toStringTag ||
|
|
477
|
-
prop === Symbol.iterator ||
|
|
478
|
-
prop === Symbol.isConcatSpreadable ||
|
|
479
|
-
(prop === 'length' && isArrayLike)
|
|
480
|
-
)
|
|
433
|
+
has(target, prop) {
|
|
434
|
+
if (prop in target) return true
|
|
435
|
+
return signals.has(String(prop))
|
|
481
436
|
},
|
|
482
|
-
ownKeys() {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
437
|
+
ownKeys(target) {
|
|
438
|
+
const staticKeys = Reflect.ownKeys(target)
|
|
439
|
+
const signalKeys = isArrayLike
|
|
440
|
+
? getSortedIndexes().map(key => String(key))
|
|
441
|
+
: Array.from(signals.keys())
|
|
442
|
+
return [...new Set([...signalKeys, ...staticKeys])]
|
|
488
443
|
},
|
|
489
|
-
getOwnPropertyDescriptor(
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
configurable: true,
|
|
493
|
-
writable: false,
|
|
494
|
-
value,
|
|
495
|
-
})
|
|
496
|
-
|
|
497
|
-
if (prop === 'length' && isArrayLike)
|
|
498
|
-
return {
|
|
499
|
-
enumerable: true,
|
|
500
|
-
configurable: true,
|
|
501
|
-
writable: false,
|
|
502
|
-
value: size.get(),
|
|
503
|
-
}
|
|
504
|
-
if (prop === Symbol.isConcatSpreadable)
|
|
505
|
-
return nonEnumerable(isArrayLike)
|
|
506
|
-
if (prop === Symbol.toStringTag) return nonEnumerable(TYPE_STORE)
|
|
507
|
-
if (isSymbol(prop)) return undefined
|
|
508
|
-
|
|
509
|
-
if (Object.keys(s).includes(prop)) return nonEnumerable(s[prop])
|
|
444
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
445
|
+
if (prop in target)
|
|
446
|
+
return Reflect.getOwnPropertyDescriptor(target, prop)
|
|
510
447
|
|
|
511
|
-
const signal = signals.get(prop
|
|
448
|
+
const signal = signals.get(String(prop))
|
|
512
449
|
return signal
|
|
513
450
|
? {
|
|
514
451
|
enumerable: true,
|
|
@@ -528,20 +465,10 @@ const store = <T extends UnknownRecord | UnknownArray>(
|
|
|
528
465
|
* @param {unknown} value - value to check
|
|
529
466
|
* @returns {boolean} - true if the value is a Store instance, false otherwise
|
|
530
467
|
*/
|
|
531
|
-
const isStore = <T extends
|
|
468
|
+
const isStore = <T extends UnknownRecord | UnknownArray>(
|
|
532
469
|
value: unknown,
|
|
533
470
|
): value is Store<T> => isObjectOfType(value, TYPE_STORE)
|
|
534
471
|
|
|
535
472
|
/* === Exports === */
|
|
536
473
|
|
|
537
|
-
export {
|
|
538
|
-
TYPE_STORE,
|
|
539
|
-
isStore,
|
|
540
|
-
store,
|
|
541
|
-
type Store,
|
|
542
|
-
type StoreAddEvent,
|
|
543
|
-
type StoreChangeEvent,
|
|
544
|
-
type StoreRemoveEvent,
|
|
545
|
-
type StoreSortEvent,
|
|
546
|
-
type StoreEventMap,
|
|
547
|
-
}
|
|
474
|
+
export { TYPE_STORE, isStore, createStore, type Store, type StoreChanges }
|