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