@oscarpalmer/mora 0.22.0 → 0.24.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.
Files changed (44) hide show
  1. package/dist/batch.js +8 -9
  2. package/dist/constants.js +36 -0
  3. package/dist/effect.js +7 -8
  4. package/dist/helpers/is.js +9 -19
  5. package/dist/helpers/proxy.js +12 -7
  6. package/dist/helpers/value.js +7 -8
  7. package/dist/index.js +1 -1
  8. package/dist/models.js +0 -0
  9. package/dist/mora.full.js +248 -148
  10. package/dist/subscription.js +7 -5
  11. package/dist/value/array.js +27 -29
  12. package/dist/value/computed.js +18 -21
  13. package/dist/value/reactive.js +2 -1
  14. package/dist/value/signal.js +2 -2
  15. package/dist/value/store.js +12 -3
  16. package/package.json +9 -11
  17. package/src/batch.ts +11 -13
  18. package/src/constants.ts +53 -0
  19. package/src/effect.ts +12 -17
  20. package/src/helpers/is.ts +45 -22
  21. package/src/helpers/proxy.ts +62 -39
  22. package/src/helpers/value.ts +19 -14
  23. package/src/index.ts +1 -0
  24. package/src/models.ts +66 -0
  25. package/src/subscription.ts +14 -16
  26. package/src/value/array.ts +92 -55
  27. package/src/value/computed.ts +29 -39
  28. package/src/value/reactive.ts +11 -25
  29. package/src/value/signal.ts +9 -3
  30. package/src/value/store.ts +55 -13
  31. package/types/batch.d.ts +3 -5
  32. package/types/constants.d.ts +16 -0
  33. package/types/effect.d.ts +2 -1
  34. package/types/helpers/is.d.ts +23 -10
  35. package/types/helpers/proxy.d.ts +4 -3
  36. package/types/helpers/value.d.ts +1 -1
  37. package/types/index.d.ts +1 -0
  38. package/types/models.d.ts +56 -0
  39. package/types/subscription.d.ts +3 -6
  40. package/types/value/array.d.ts +48 -7
  41. package/types/value/computed.d.ts +2 -11
  42. package/types/value/reactive.d.ts +8 -17
  43. package/types/value/signal.d.ts +7 -1
  44. package/types/value/store.d.ts +38 -7
package/dist/mora.full.js CHANGED
@@ -1,78 +1,124 @@
1
+ const ACTIVE = {};
2
+ const ARRAY_THRESHOLD = 100;
3
+ const ARRAY_OFFSET = 25;
4
+ const ARRAY_PEEK = 10;
5
+ const BATCH = {
6
+ depth: 0,
7
+ handlers: new Set(),
8
+ };
9
+ const METHODS_AFFECTING_LENGTH = new Set([
10
+ 'pop',
11
+ 'push',
12
+ 'shift',
13
+ 'unshift',
14
+ ]);
15
+ const METHODS_UPDATE = new Set([
16
+ ...METHODS_AFFECTING_LENGTH,
17
+ 'copyWithin',
18
+ 'fill',
19
+ 'reverse',
20
+ 'sort',
21
+ 'splice',
22
+ ]);
23
+ const NAME_ARRAY = 'array';
24
+ const NAME_COMPUTED = 'computed';
25
+ const NAME_EFFECT = 'effect';
26
+ const NAME_MORA = '$mora';
27
+ const NAME_SIGNAL = 'signal';
28
+ const NAME_STORE = 'store';
29
+ const NAMES = new Set([
30
+ NAME_ARRAY,
31
+ NAME_COMPUTED,
32
+ NAME_SIGNAL,
33
+ NAME_STORE,
34
+ ]);
35
+ const PROPERTY_LENGTH = 'length';
36
+
37
+ class Effect {
38
+ constructor(callback) {
39
+ Object.defineProperty(this, NAME_MORA, {
40
+ value: NAME_EFFECT,
41
+ });
42
+ this.state = {
43
+ callback,
44
+ };
45
+ runEffect(this);
46
+ }
47
+ }
48
+ function runEffect(effect) {
49
+ const previousEffect = ACTIVE.effect;
50
+ ACTIVE.effect = effect;
51
+ try {
52
+ effect.state.callback();
53
+ }
54
+ finally {
55
+ ACTIVE.effect = previousEffect;
56
+ }
57
+ }
58
+ /**
59
+ * Create an effect
60
+ * @param callback Callback for handling signal effects
61
+ * @returns Effect
62
+ */
63
+ function effect(callback) {
64
+ return typeof callback === 'function'
65
+ ? new Effect(callback)
66
+ : undefined;
67
+ }
68
+
1
69
  /**
2
70
  * Is the value a reactive array?
71
+ * @param value Value to check
72
+ * @returns True if value is a {@link ReactiveArray}
3
73
  */
4
74
  function isArray(value) {
5
- return isMora(value, arrayName);
75
+ return isMora(value, NAME_ARRAY);
6
76
  }
7
77
  /**
8
78
  * Is the value a computed signal?
79
+ * @param value Value to check
80
+ * @returns True if value is a {@link Computed}
9
81
  */
10
82
  function isComputed(value) {
11
- return isMora(value, computedName);
83
+ return isMora(value, NAME_COMPUTED);
12
84
  }
13
85
  /**
14
86
  * Is the value an effect?
87
+ * @param value Value to check
88
+ * @returns True if value is an {@link Effect}
15
89
  */
16
90
  function isEffect(value) {
17
- return isMora(value, effectName);
91
+ return isMora(value, NAME_EFFECT);
18
92
  }
19
93
  function isMora(value, name) {
20
94
  return (typeof value === 'object' &&
21
95
  value != null &&
22
- '$mora' in value &&
96
+ NAME_MORA in value &&
23
97
  (typeof name === 'string'
24
- ? value.$mora === name
25
- : name.has(value.$mora)));
98
+ ? value[NAME_MORA] === name
99
+ : name.has(value[NAME_MORA])));
26
100
  }
101
+ /**
102
+ * Is the value reactive?
103
+ * @param value Value to check
104
+ * @returns True if value is a {@link Reactive}
105
+ */
27
106
  function isReactive(value) {
28
- return isMora(value, reactiveNames);
107
+ return isMora(value, NAMES);
29
108
  }
30
109
  /**
31
110
  * Is the value a signal?
111
+ * @param value Value to check
112
+ * @returns True if value is a {@link Signal}
32
113
  */
33
114
  function isSignal(value) {
34
- return isMora(value, signalName);
35
- }
36
- const arrayName = 'array';
37
- const computedName = 'computed';
38
- const effectName = 'effect';
39
- const signalName = 'signal';
40
- const storeName = 'store';
41
- const reactiveNames = new Set([arrayName, computedName, signalName, storeName]);
42
-
43
- class Effect {
44
- constructor(callback) {
45
- Object.defineProperty(this, '$mora', {
46
- value: effectName,
47
- });
48
- this.state = {
49
- callback,
50
- };
51
- runEffect(this);
52
- }
53
- }
54
- function runEffect(effect) {
55
- const previousEffect = activeEffect;
56
- activeEffect = effect;
57
- try {
58
- effect.state.callback();
59
- }
60
- finally {
61
- activeEffect = previousEffect;
62
- }
63
- }
64
- /**
65
- * Create an effect
66
- */
67
- function effect(callback) {
68
- return new Effect(callback);
115
+ return isMora(value, NAME_SIGNAL);
69
116
  }
70
- let activeEffect;
71
117
 
72
118
  function flushHandlers() {
73
- while (batchDepth === 0 && batchedHandlers.size > 0) {
74
- const handlers = [...batchedHandlers];
75
- batchedHandlers.clear();
119
+ while (BATCH.depth === 0 && BATCH.handlers.size > 0) {
120
+ const handlers = [...BATCH.handlers];
121
+ BATCH.handlers.clear();
76
122
  for (const handler of handlers) {
77
123
  if (isEffect(handler)) {
78
124
  runEffect(handler);
@@ -84,31 +130,35 @@ function flushHandlers() {
84
130
  }
85
131
  }
86
132
  /**
87
- * Start batching effects _(use `stopBatch` to flush and run batched effects)_
133
+ * Start batching effects
134
+ *
135
+ * _(Use {@link stopBatch} to flush and run batched effects)_
88
136
  */
89
137
  function startBatch() {
90
- batchDepth += 1;
138
+ BATCH.depth += 1;
91
139
  }
92
140
  /**
93
141
  * Stop batching effects and flush _(run)_ them
94
142
  */
95
143
  function stopBatch() {
96
- if (batchDepth > 0) {
97
- batchDepth -= 1;
144
+ if (BATCH.depth > 0) {
145
+ BATCH.depth -= 1;
98
146
  }
99
147
  flushHandlers();
100
148
  }
101
- const batchedHandlers = new Set();
102
- let batchDepth = 0;
103
149
 
104
150
  class Subscription {
105
- state;
106
151
  callback;
152
+ state;
107
153
  constructor(state, callback) {
108
154
  this.state = state;
109
155
  this.callback = callback;
110
156
  callback(state.value);
111
157
  }
158
+ destroy() {
159
+ this.callback = noop;
160
+ this.state = undefined;
161
+ }
112
162
  }
113
163
  function noop() { }
114
164
  function subscribe(state, callback) {
@@ -121,12 +171,8 @@ function subscribe(state, callback) {
121
171
  };
122
172
  }
123
173
  function unsubscribe(state, callback) {
124
- const subscription = state.subscriptions.get(callback);
174
+ state.subscriptions.get(callback)?.destroy();
125
175
  state.subscriptions.delete(callback);
126
- if (subscription != null) {
127
- subscription.callback = noop;
128
- subscription.state = undefined;
129
- }
130
176
  }
131
177
 
132
178
  class Reactive {
@@ -142,83 +188,89 @@ class Reactive {
142
188
  if (typeof options === 'object' && typeof options.equal === 'function') {
143
189
  this.state.equal = options.equal;
144
190
  }
145
- Object.defineProperty(this, '$mora', {
191
+ Object.defineProperty(this, NAME_MORA, {
146
192
  value: name,
147
193
  });
148
194
  }
149
195
  /**
150
196
  * Get the value _(without reactivity)_
197
+ * @return Current value
151
198
  */
152
199
  peek() {
153
200
  return this.state.value;
154
201
  }
155
202
  /**
156
203
  * Subscribe to changes
204
+ * @param callback Callback for changes
205
+ * @return Unsubscribe callback
157
206
  */
158
207
  subscribe(callback) {
159
208
  return subscribe(this.state, callback);
160
209
  }
161
210
  /**
162
211
  * JSON representation of the value
212
+ * @return JSON value
163
213
  */
164
214
  toJSON() {
165
215
  return this.get();
166
216
  }
167
217
  /**
168
218
  * String representation of the value
219
+ * @return Value as string
169
220
  */
170
221
  toString() {
171
222
  return String(this.get());
172
223
  }
173
224
  /**
174
225
  * Unsubscribe from changes
226
+ * @param callback Callback to unsubscribe
175
227
  */
176
228
  unsubscribe(callback) {
177
229
  unsubscribe(this.state, callback);
178
230
  }
179
231
  }
180
232
 
181
- let activeComputed;
182
233
  class Computed extends Reactive {
183
234
  effect = {
184
235
  dirty: true,
185
236
  instance: undefined,
186
237
  };
187
238
  constructor(callback, options) {
188
- super(computedName, undefined, options);
239
+ super(NAME_COMPUTED, undefined, options);
189
240
  this.effect.instance = effect(() => {
190
- if (this.effect.dirty) {
191
- const previousComputed = activeComputed;
192
- activeComputed = this;
193
- const value = callback();
194
- activeComputed = previousComputed;
195
- if (!this.state.equal(this.state.value, value)) {
196
- this.state.value = value;
197
- for (const computed of this.state.computeds) {
198
- computed.effect.dirty = true;
199
- }
200
- for (const effect of this.state.effects) {
201
- batchedHandlers.add(effect);
202
- }
203
- for (const [, subscription] of this.state.subscriptions) {
204
- subscription.callback(value);
205
- }
241
+ if (!this.effect.dirty) {
242
+ return;
243
+ }
244
+ const previousComputed = ACTIVE.computed;
245
+ ACTIVE.computed = this;
246
+ const value = callback();
247
+ ACTIVE.computed = previousComputed;
248
+ if (!this.state.equal(this.state.value, value)) {
249
+ this.state.value = value;
250
+ for (const computed of this.state.computeds) {
251
+ computed.effect.dirty = true;
252
+ }
253
+ for (const effect of this.state.effects) {
254
+ BATCH.handlers.add(effect);
255
+ }
256
+ for (const [, subscription] of this.state.subscriptions) {
257
+ subscription.callback(value);
206
258
  }
207
- this.effect.dirty = false;
208
259
  }
260
+ this.effect.dirty = false;
209
261
  });
210
262
  }
211
263
  /**
212
264
  * @inheritdoc
213
265
  */
214
266
  get() {
215
- if (activeComputed != null && this !== activeComputed) {
216
- this.state.computeds.add(activeComputed);
267
+ if (ACTIVE.computed != null && this !== ACTIVE.computed) {
268
+ this.state.computeds.add(ACTIVE.computed);
217
269
  }
218
- if (activeEffect != null && activeEffect !== this.effect.instance) {
219
- this.state.effects.add(activeEffect);
270
+ if (ACTIVE.effect != null && ACTIVE.effect !== this.effect.instance) {
271
+ this.state.effects.add(ACTIVE.effect);
220
272
  }
221
- if (this.effect.dirty && batchDepth === 0) {
273
+ if (this.effect.dirty && BATCH.depth === 0) {
222
274
  runEffect(this.effect.instance);
223
275
  }
224
276
  return this.state.value;
@@ -236,12 +288,12 @@ function emitValue(state) {
236
288
  computed.effect.dirty = true;
237
289
  }
238
290
  for (const effect of state.effects) {
239
- batchedHandlers.add(effect);
291
+ BATCH.handlers.add(effect);
240
292
  }
241
293
  for (const [, subscription] of state.subscriptions) {
242
- batchedHandlers.add(subscription);
294
+ BATCH.handlers.add(subscription);
243
295
  }
244
- if (batchDepth === 0) {
296
+ if (BATCH.depth === 0) {
245
297
  flushHandlers();
246
298
  }
247
299
  }
@@ -251,9 +303,9 @@ function equalArrays(state, first, second) {
251
303
  return false;
252
304
  }
253
305
  let offset = 0;
254
- if (length >= 100) {
255
- offset = Math.round(length / 10);
256
- offset = offset > 25 ? 25 : offset;
306
+ if (length >= ARRAY_THRESHOLD) {
307
+ offset = Math.round(length / ARRAY_PEEK);
308
+ offset = offset > ARRAY_OFFSET ? ARRAY_OFFSET : offset;
257
309
  for (let index = 0; index < offset; index += 1) {
258
310
  if (!state.equal(first[index], second[index])) {
259
311
  return false;
@@ -269,24 +321,29 @@ function equalArrays(state, first, second) {
269
321
  return true;
270
322
  }
271
323
  function getValue(state) {
272
- if (activeComputed != null) {
273
- state.computeds.add(activeComputed);
324
+ if (ACTIVE.computed != null) {
325
+ state.computeds.add(ACTIVE.computed);
274
326
  }
275
- if (activeEffect != null) {
276
- state.effects.add(activeEffect);
327
+ if (ACTIVE.effect != null) {
328
+ state.effects.add(ACTIVE.effect);
277
329
  }
278
330
  return state.value;
279
331
  }
280
332
 
333
+ function emityProxyValues(state, mapped) {
334
+ const values = [...mapped.values()];
335
+ const { length } = values;
336
+ for (let index = 0; index < length; index += 1) {
337
+ values[index].effect.dirty = true;
338
+ }
339
+ emitValue(state);
340
+ }
281
341
  function getReactiveValueInProxy(reactive, mapped, key, isArray) {
282
342
  let item = mapped.get(key);
283
343
  if (item == null) {
284
- item = computed(() => {
285
- const value = reactive.get();
286
- return isArray
287
- ? value.at(key)
288
- : value[key];
289
- });
344
+ item = computed(() => isArray
345
+ ? reactive.get().at(key)
346
+ : reactive.get()[key]);
290
347
  mapped.set(key, item);
291
348
  }
292
349
  return item;
@@ -312,11 +369,12 @@ function setProxyValue(proxy, value) {
312
369
  }
313
370
  stopBatch();
314
371
  }
315
- function setValueInProxy(target, property, value, state, isArray, length) {
372
+ function setValueInProxy(parameters) {
373
+ const { isArray, length, property, state, target, value } = parameters;
316
374
  if (isArray) {
317
375
  const isIndex = !Number.isNaN(Number(property));
318
- const isLength = property === 'length';
319
- if (!isIndex && !isLength) {
376
+ const isLength = property === PROPERTY_LENGTH;
377
+ if (!(isIndex || isLength)) {
320
378
  return Reflect.set(target, property, value);
321
379
  }
322
380
  }
@@ -333,7 +391,7 @@ function setValueInProxy(target, property, value, state, isArray, length) {
333
391
 
334
392
  class Signal extends Reactive {
335
393
  constructor(value, options) {
336
- super(signalName, value, options);
394
+ super(NAME_SIGNAL, value, options);
337
395
  }
338
396
  /**
339
397
  * @inheritdoc
@@ -343,6 +401,7 @@ class Signal extends Reactive {
343
401
  }
344
402
  /**
345
403
  * Set the value
404
+ * @param value New value
346
405
  */
347
406
  set(value) {
348
407
  if (!this.state.equal(this.state.value, value)) {
@@ -352,6 +411,7 @@ class Signal extends Reactive {
352
411
  }
353
412
  /**
354
413
  * Update the value _(based on the current value)_
414
+ * @param callback Callback to update the value
355
415
  */
356
416
  update(callback) {
357
417
  this.set(callback(this.state.value));
@@ -359,6 +419,9 @@ class Signal extends Reactive {
359
419
  }
360
420
  /**
361
421
  * Create a reactive value
422
+ * @param value Initial value
423
+ * @param options Optional reactivity options
424
+ * @returns Reactive value
362
425
  */
363
426
  function signal(value, options) {
364
427
  return new Signal(value, options);
@@ -384,11 +447,18 @@ class ReactiveArray extends Reactive {
384
447
  }
385
448
  }
386
449
  constructor(value, options) {
387
- super(arrayName, new Proxy(value, {
388
- get: (target, property) => updateMethods.has(property)
450
+ super(NAME_ARRAY, new Proxy(value, {
451
+ get: (target, property) => METHODS_UPDATE.has(property)
389
452
  ? updateArray(property, target, this.state, this.#size)
390
453
  : Reflect.get(target, property),
391
- set: (target, property, value) => setValueInProxy(target, property, value, this.state, true, this.#size),
454
+ set: (target, property, value) => setValueInProxy({
455
+ property,
456
+ target,
457
+ value,
458
+ isArray: true,
459
+ state: this.state,
460
+ length: this.#size,
461
+ }),
392
462
  }), options);
393
463
  this.#size.set(value.length);
394
464
  }
@@ -398,44 +468,56 @@ class ReactiveArray extends Reactive {
398
468
  clear() {
399
469
  this.length = 0;
400
470
  }
401
- get(first) {
402
- if (typeof first === 'number') {
403
- return getReactiveValueInProxy(this, this.#indiced, first, true).get();
404
- }
405
- if (first === 'length') {
406
- return this.length;
407
- }
408
- return getValue(this.state);
409
- }
410
471
  /**
411
472
  * Create a computed, filtered array
473
+ * @param callback Callback to evaluate each item
474
+ * @return Computed array of filtered items
412
475
  */
413
476
  filter(callback) {
414
477
  return computed(() => this.get().filter(callback));
415
478
  }
479
+ get(first) {
480
+ if (typeof first === 'number') {
481
+ return getReactiveValueInProxy(this, this.#indiced, first, true).get();
482
+ }
483
+ return first === PROPERTY_LENGTH ? this.length : getValue(this.state);
484
+ }
416
485
  /**
417
486
  * Create a computed, mapped array
487
+ * @param callback Callback to transform each item
488
+ * @return Computed array of mapped items
418
489
  */
419
490
  map(callback) {
420
491
  return computed(() => this.get().map(callback));
421
492
  }
493
+ /**
494
+ * Notify dependents of changes
495
+ *
496
+ * _This bypasses equality checks and will immediately notify dependents.
497
+ * Use this only if you're modifying nested data that would be ignored by equality checks._
498
+ */
499
+ notify() {
500
+ emityProxyValues(this.state, this.#indiced);
501
+ }
422
502
  peek(value) {
423
- if (value === true) {
503
+ if (value === 'length') {
424
504
  return this.#size.peek();
425
505
  }
426
- if (typeof value === 'number') {
427
- return this.state.value.at(value);
428
- }
429
- return [...this.state.value];
506
+ return typeof value === 'number'
507
+ ? this.state.value.at(value)
508
+ : [...this.state.value];
430
509
  }
431
510
  /**
432
511
  * Remove and return the last item of the array
512
+ * @returns Removed item, or `undefined` if the array is empty
433
513
  */
434
514
  pop() {
435
515
  return this.state.value.pop();
436
516
  }
437
517
  /**
438
518
  * Add items to the end of the array
519
+ * @param items Items to add
520
+ * @returns New array length
439
521
  */
440
522
  push(...items) {
441
523
  return this.state.value.push(...items);
@@ -447,18 +529,23 @@ class ReactiveArray extends Reactive {
447
529
  else if (first === 'length') {
448
530
  this.length = second;
449
531
  }
450
- else if (typeof first === 'number') {
451
- this.state.value[first] = second;
532
+ else if (typeof first === 'number' && !Number.isNaN(first)) {
533
+ setAtIndex(this.state.value, first, second);
452
534
  }
453
535
  }
454
536
  /**
455
537
  * Remove and return the first item of the array
538
+ * @returns Removed item, or `undefined` if the array is empty
456
539
  */
457
540
  shift() {
458
541
  return this.state.value.shift();
459
542
  }
460
543
  /**
461
544
  * Remove and return items from the array _(and optionally add new items)_
545
+ * @param from Index to start removing items from
546
+ * @param to Index to stop removing items at _(defaults to the end of the array)_
547
+ * @param items Optional items to add
548
+ * @returns Removed items
462
549
  */
463
550
  splice(from, to, ...items) {
464
551
  return this.state.value.splice(from, to ?? this.state.value.length, ...items);
@@ -471,12 +558,15 @@ class ReactiveArray extends Reactive {
471
558
  }
472
559
  /**
473
560
  * Add items to the beginning of the array
561
+ * @param items Items to add
562
+ * @returns New array length
474
563
  */
475
564
  unshift(...items) {
476
565
  return this.state.value.unshift(...items);
477
566
  }
478
567
  /**
479
568
  * Update the value _(based on the current value)_
569
+ * @param callback Callback to update the value
480
570
  */
481
571
  update(callback) {
482
572
  const updated = callback(this.state.value);
@@ -487,12 +577,15 @@ class ReactiveArray extends Reactive {
487
577
  }
488
578
  /**
489
579
  * Create a reactive array
580
+ * @param value Initial array of items
581
+ * @param options Optional reactivity options
582
+ * @returns Reactive array
490
583
  */
491
584
  function array(value, options) {
492
585
  return new ReactiveArray(Array.isArray(value) ? value : [], options);
493
586
  }
494
587
  function updateArray(type, array, state, length) {
495
- const affectsLength = lengthAffectingMethods.has(type);
588
+ const affectsLength = METHODS_AFFECTING_LENGTH.has(type);
496
589
  const previousArray = affectsLength ? [] : [...array];
497
590
  const previousLength = array.length;
498
591
  return (...args) => {
@@ -506,40 +599,34 @@ function updateArray(type, array, state, length) {
506
599
  return result;
507
600
  };
508
601
  }
509
- const lengthAffectingMethods = new Set([
510
- 'pop',
511
- 'push',
512
- 'shift',
513
- 'unshift',
514
- ]);
515
- const updateMethods = new Set([
516
- ...lengthAffectingMethods,
517
- 'copyWithin',
518
- 'fill',
519
- 'reverse',
520
- 'sort',
521
- 'splice',
522
- ]);
602
+ function setAtIndex(array, index, value) {
603
+ const actual = index < 0 ? array.length + index : index;
604
+ if (actual > -1) {
605
+ array[actual] = value;
606
+ }
607
+ }
523
608
 
524
609
  function isKey(value) {
525
- return typeof value === "number" || typeof value === "string";
610
+ return typeof value === "number" || typeof value === "string";
526
611
  }
527
612
  function isPlainObject(value) {
528
- if (value === null || typeof value !== "object") {
529
- return false;
530
- }
531
- if (Symbol.toStringTag in value || Symbol.iterator in value) {
532
- return false;
533
- }
534
- const prototype = Object.getPrototypeOf(value);
535
- return prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null;
613
+ if (value === null || typeof value !== "object") return false;
614
+ if (Symbol.toStringTag in value || Symbol.iterator in value) return false;
615
+ const prototype = Object.getPrototypeOf(value);
616
+ return prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null;
536
617
  }
537
618
 
538
619
  class Store extends Reactive {
539
620
  #keyed = new Map();
540
621
  constructor(value, options) {
541
- super(storeName, new Proxy(value, {
542
- set: (target, property, value) => setValueInProxy(target, property, value, this.state, false),
622
+ super(NAME_STORE, new Proxy(value, {
623
+ set: (target, property, value) => setValueInProxy({
624
+ target,
625
+ property,
626
+ value,
627
+ isArray: false,
628
+ state: this.state,
629
+ }),
543
630
  }), options);
544
631
  }
545
632
  get(key) {
@@ -547,6 +634,15 @@ class Store extends Reactive {
547
634
  ? getReactiveValueInProxy(this, this.#keyed, key, false).get()
548
635
  : getValue(this.state);
549
636
  }
637
+ /**
638
+ * Notify dependents of changes
639
+ *
640
+ * _This bypasses equality checks and will immediately notify dependents.
641
+ * Use this only if you're modifying nested data that would be ignored by equality checks._
642
+ */
643
+ notify() {
644
+ emityProxyValues(this.state, this.#keyed);
645
+ }
550
646
  peek(key) {
551
647
  return isKey(key) ? this.state.value[key] : { ...this.state.value };
552
648
  }
@@ -566,6 +662,7 @@ class Store extends Reactive {
566
662
  }
567
663
  /**
568
664
  * Update the value _(based on the current value)_
665
+ * @param callback Callback to update the value
569
666
  */
570
667
  update(callback) {
571
668
  const updated = callback({ ...this.state.value });
@@ -576,6 +673,9 @@ class Store extends Reactive {
576
673
  }
577
674
  /**
578
675
  * Create a reactive store
676
+ * @param value Initial object value
677
+ * @param options Optional reactivity options
678
+ * @returns Reactive store
579
679
  */
580
680
  function store(value, options) {
581
681
  return new Store((isPlainObject(value) ? value : {}), options);