@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/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 './scheduler'
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 StoreEventMap<T extends UnknownRecord | UnknownArray> = {
41
- 'store-add': StoreAddEvent<T>
42
- 'store-change': StoreChangeEvent<T>
43
- 'store-remove': StoreRemoveEvent<T>
44
- 'store-sort': StoreSortEvent
41
+ type StoreChanges<T> = {
42
+ add: PartialRecord<T>
43
+ change: PartialRecord<T>
44
+ remove: PartialRecord<T>
45
+ sort: string[]
45
46
  }
46
47
 
47
- interface StoreEventTarget<T extends UnknownRecord | UnknownArray>
48
- extends EventTarget {
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<T extends UnknownRecord | UnknownArray>
65
- extends StoreEventTarget<T> {
52
+ interface BaseStore {
66
53
  readonly [Symbol.toStringTag]: 'Store'
67
- get(): T
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<T> & {
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<T> & {
98
- readonly length: number
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
- interface StoreSortEvent extends CustomEvent {
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 store = <T extends UnknownRecord | UnknownArray>(
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 eventTarget = new EventTarget()
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 cleanups = new Map<string, Cleanup>()
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 event
190
- const emit = <R>(type: keyof StoreEventMap<T>, detail: R) =>
191
- eventTarget.dispatchEvent(new CustomEvent(type, { detail }))
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 = <K extends Extract<keyof T, string>>(
218
- key: K,
219
- value: T[K] | ArrayItem<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
- ? store(value)
228
- : Array.isArray(value)
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 cleanup = effect(() => {
234
- const currentValue = signal.get()
235
- if (currentValue != null)
236
- emit(STORE_EVENT_CHANGE, {
237
- [key]: currentValue,
238
- } as unknown as Partial<T>)
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(STORE_EVENT_ADD, {
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 = <K extends Extract<keyof T, string>>(
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 cleanup = cleanups.get(key)
260
- if (cleanup) cleanup()
261
- cleanups.delete(key)
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(STORE_EVENT_REMOVE, {
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
- const value = changes.add[key] ?? UNSET
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(STORE_EVENT_ADD, changes.add as Partial<T>)
257
+ emit('add', changes.add)
300
258
  }, 0)
301
259
  } else {
302
- emit<Partial<T>>(STORE_EVENT_ADD, changes.add as Partial<T>)
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(STORE_EVENT_CHANGE, changes.change as Partial<T>)
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
- removeProperty(key as Extract<keyof T, string>)
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 - convert arrays to records for internal storage
287
+ // Initialize data
334
288
  reconcile({} as T, initialValue, true)
335
289
 
336
290
  // Methods and Properties
337
- const s: Record<string, unknown> = {
338
- add: isArrayLike
339
- ? (v: ArrayItem<T>): void => {
340
- const nextIndex = signals.size
341
- const key = String(nextIndex) as Extract<keyof T, string>
342
- addProperty(key, v, true)
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
- : <K extends Extract<keyof T, string>>(k: K, v: T[K]): void => {
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
- remove: isArrayLike
353
- ? (index: number): void => {
354
- const currentArray = recordToArray(current()) as T
355
- const currentLength = signals.size
356
- if (
357
- !Array.isArray(currentArray) ||
358
- index <= -currentLength ||
359
- index >= currentLength
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
- if (reconcile(currentArray, newArray as unknown as T))
366
- notify(watchers)
367
- }
368
- : <K extends Extract<keyof T, string>>(k: K): void => {
369
- if (signals.has(k)) removeProperty(k, true)
370
- },
371
- set: (v: T): void => {
372
- if (reconcile(current() as T, v)) {
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
- if (UNSET === v) watchers.clear()
375
- }
406
+ emit('sort', newOrder)
407
+ },
376
408
  },
377
- update: (fn: (v: T) => T): void => {
378
- const oldValue = current()
379
- const newValue = fn(recordToArray(oldValue) as T)
380
- if (reconcile(oldValue as T, newValue)) {
381
- notify(watchers)
382
- if (UNSET === newValue) watchers.clear()
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
- sort: (
386
- compareFn?: <
387
- U = T extends UnknownArray
388
- ? ArrayItem<T>
389
- : T[Extract<keyof T, string>],
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
- addEventListener: eventTarget.addEventListener.bind(eventTarget),
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({} as Store<T>, {
439
- get(_target, prop) {
440
- // Symbols
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(_target, prop) {
471
- const stringProp = String(prop)
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
- return isArrayLike
484
- ? getSortedIndexes()
485
- .map(key => String(key))
486
- .concat(['length'])
487
- : Array.from(signals.keys()).map(key => String(key))
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(_target, prop) {
490
- const nonEnumerable = <T>(value: T) => ({
491
- enumerable: false,
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 as Extract<keyof T, string>)
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 UnknownRecordOrArray>(
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 }