mobx 7.0.4 → 7.0.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # mobx
2
2
 
3
+ ## 7.0.6
4
+
5
+ ### Patch Changes
6
+
7
+ - [`4b1c8d3ea2b3de1a2ae852fd5631e33d28a82b15`](https://github.com/mobxjs/mobx/commit/4b1c8d3ea2b3de1a2ae852fd5631e33d28a82b15) [#4721](https://github.com/mobxjs/mobx/pull/4721) Thanks [@giaBaoJS](https://github.com/giaBaoJS)! - fix: assigning `length` on an observable array now respects `intercept`. A cancelled or modified splice used to be overridden by a direct write to the backing array, which truncated it without notifying observers and made the next mutation throw "Modification exception". A non-integer `length` now throws "Out of range" before touching the array.
8
+
9
+ - [`60be47cab0926d12f22449ec6c809fb5ec6601c4`](https://github.com/mobxjs/mobx/commit/60be47cab0926d12f22449ec6c809fb5ec6601c4) [#4716](https://github.com/mobxjs/mobx/pull/4716) Thanks [@AcEKaycgR](https://github.com/AcEKaycgR)! - fix: `observable.box(array)` no longer warns under `observableRequiresReaction` when a spy is active. The `create` spy event now reports `newValue` as the value itself rather than a string, matching the declared `IBoxDidChange` type. Previously the constructor stringified the value, and stringifying an observable array reads its `length`, which counted as an observable read outside a reactive context. Spy listeners that relied on `newValue` being a string for `create` events will now receive the raw value.
10
+
11
+ - [`3551aeb051ff922717b03c4fbc04a6b98f7f3b3e`](https://github.com/mobxjs/mobx/commit/3551aeb051ff922717b03c4fbc04a6b98f7f3b3e) [#4717](https://github.com/mobxjs/mobx/pull/4717) Thanks [@breken-ai](https://github.com/breken-ai)! - Remove the `signal` abort listener when a reaction disposes itself (`r.dispose()` or a resolved `when`), so long-lived `AbortSignal`s no longer accumulate listeners that keep disposed reactions in memory
12
+
13
+ - [`7e4e27dbfbf0b56770a11e9cba913e5d49c0c6f8`](https://github.com/mobxjs/mobx/commit/7e4e27dbfbf0b56770a11e9cba913e5d49c0c6f8) [#4718](https://github.com/mobxjs/mobx/pull/4718) Thanks [@breken-ai](https://github.com/breken-ai)! - `observe`/`spy` `add` events of an observable Set now report the value that was actually stored (e.g. the observable copy of a plain object), matching ObservableMap and observable arrays, so `set.has(change.newValue)` holds
14
+
15
+ ## 7.0.5
16
+
17
+ ### Patch Changes
18
+
19
+ - [`105d66d73529812a8eddc0847891e7f0c574e129`](https://github.com/mobxjs/mobx/commit/105d66d73529812a8eddc0847891e7f0c574e129) [#4713](https://github.com/mobxjs/mobx/pull/4713) Thanks [@xaviergonz](https://github.com/xaviergonz)! - perf: reduce memory used by large, mostly unobserved state. `intercept` / `observe` handler arrays are allocated at their exact size, an observable's internal observers `Set` is released once its last observer leaves, observable objects created with the same `deep` / `autoBind` options share one default annotation instead of allocating their own, and `endBatch()` skips the unobservation pass when nothing is pending.
20
+
3
21
  ## 7.0.4
4
22
 
5
23
  ### Patch Changes
@@ -35,6 +35,7 @@ export declare class Reaction implements IDerivation, IReactionPublic {
35
35
  runId_: number;
36
36
  unboundDepsCount_: number;
37
37
  private flags_;
38
+ private removeAbortListener_;
38
39
  constructor(name_: string | undefined, onInvalidate_: () => void, errorHandler_?: ((error: any, derivation: IDerivation) => void) | undefined, requiresObservable_?: any);
39
40
  get isDisposed(): boolean;
40
41
  set isDisposed(newValue: boolean);
@@ -820,6 +820,19 @@ function createAutoAnnotation(options) {
820
820
  extend_
821
821
  };
822
822
  }
823
+ // The auto annotation only depends on `deep` and `autoBind`, so share one instance per combination
824
+ // instead of allocating one for every object created with options
825
+ const autoAnnotations = [autoAnnotation];
826
+ function getAutoAnnotation(options) {
827
+ var _autoAnnotations$idx;
828
+ const deep = options.deep !== false;
829
+ const autoBind = !!options.autoBind;
830
+ const idx = (deep ? 0 : 1) | (autoBind ? 2 : 0);
831
+ return (_autoAnnotations$idx = autoAnnotations[idx]) != null ? _autoAnnotations$idx : autoAnnotations[idx] = createAutoAnnotation({
832
+ deep,
833
+ autoBind
834
+ });
835
+ }
823
836
  function make_$1(adm, key, descriptor, source) {
824
837
  var _this$options_3, _this$options_4;
825
838
  // getter -> computed
@@ -936,7 +949,7 @@ function getEnhancerFromOptions(options) {
936
949
  }
937
950
  function getAnnotationFromOptions(options) {
938
951
  var _options$defaultDecor;
939
- return options ? (_options$defaultDecor = options.defaultDecorator) != null ? _options$defaultDecor : createAutoAnnotation(options) : undefined;
952
+ return options ? (_options$defaultDecor = options.defaultDecorator) != null ? _options$defaultDecor : getAutoAnnotation(options) : undefined;
940
953
  }
941
954
  function getEnhancerFromAnnotation(annotation) {
942
955
  var _annotation$options_$, _annotation$options_;
@@ -1182,14 +1195,13 @@ class ObservableValue extends Atom {
1182
1195
  this.equals_ = equals_;
1183
1196
  this.value_ = enhancer_(value, undefined, name_);
1184
1197
  if (notifySpy && isSpyEnabled()) {
1185
- var _this$value_;
1186
1198
  // only notify spy if this is a stand-alone observable
1187
1199
  spyReport({
1188
1200
  type: CREATE,
1189
1201
  object: this,
1190
1202
  observableKind: "value",
1191
1203
  debugObjectName: this.name_,
1192
- newValue: "" + ((_this$value_ = this.value_) == null ? void 0 : _this$value_.toString())
1204
+ newValue: this.value_
1193
1205
  });
1194
1206
  }
1195
1207
  }
@@ -1988,34 +2000,41 @@ function endBatch() {
1988
2000
  // the nested call instead of recursing; the outer loop re-reads list.length on
1989
2001
  // every iteration, so it picks up anything the nested dispose() pushes onto the
1990
2002
  // same pendingUnobservations array.
1991
- if (!globalState.isRunningUnobservations) {
1992
- globalState.isRunningUnobservations = true;
1993
- try {
1994
- const list = globalState.pendingUnobservations;
1995
- for (let i = 0; i < list.length; i++) {
1996
- const observable = list[i];
1997
- observable.isPendingUnobservation = false;
1998
- if (!observable.observers_ || observable.observers_.size === 0) {
1999
- if (observable.isBeingObserved) {
2000
- // if this observable had reactive observers, trigger the hooks
2001
- observable.isBeingObserved = false;
2002
- observable.onBUO();
2003
- }
2004
- if (observable instanceof ComputedValue) {
2005
- // computed values are automatically teared down when the last observer leaves
2006
- // this process happens recursively, this computed might be the last observabe of another, etc..
2007
- observable.suspend_();
2008
- }
2009
- }
2003
+ if (!globalState.isRunningUnobservations && globalState.pendingUnobservations.length > 0) {
2004
+ runPendingUnobservations();
2005
+ }
2006
+ }
2007
+ }
2008
+ // Only called when there is something to unobserve, so the common endBatch() skips the
2009
+ // try/finally and the pendingUnobservations reallocation entirely
2010
+ function runPendingUnobservations() {
2011
+ globalState.isRunningUnobservations = true;
2012
+ try {
2013
+ const list = globalState.pendingUnobservations;
2014
+ for (let i = 0; i < list.length; i++) {
2015
+ const observable = list[i];
2016
+ observable.isPendingUnobservation = false;
2017
+ if (!observable.observers_ || observable.observers_.size === 0) {
2018
+ // release the empty Set so unobserved atoms don't keep paying for it
2019
+ observable.observers_ = null;
2020
+ if (observable.isBeingObserved) {
2021
+ // if this observable had reactive observers, trigger the hooks
2022
+ observable.isBeingObserved = false;
2023
+ observable.onBUO();
2024
+ }
2025
+ if (observable instanceof ComputedValue) {
2026
+ // computed values are automatically teared down when the last observer leaves
2027
+ // this process happens recursively, this computed might be the last observabe of another, etc..
2028
+ observable.suspend_();
2010
2029
  }
2011
- globalState.pendingUnobservations = [];
2012
- } finally {
2013
- // Always release the guard, even if an onBUO handler (user code) threw,
2014
- // otherwise every future endBatch() would see isRunningUnobservations
2015
- // stuck true and silently stop draining pendingUnobservations forever.
2016
- globalState.isRunningUnobservations = false;
2017
2030
  }
2018
2031
  }
2032
+ globalState.pendingUnobservations = [];
2033
+ } finally {
2034
+ // Always release the guard, even if an onBUO handler (user code) threw,
2035
+ // otherwise every future endBatch() would see isRunningUnobservations
2036
+ // stuck true and silently stop draining pendingUnobservations forever.
2037
+ globalState.isRunningUnobservations = false;
2019
2038
  }
2020
2039
  }
2021
2040
  /**
@@ -2145,6 +2164,7 @@ class Reaction {
2145
2164
  this.runId_ = 0;
2146
2165
  this.unboundDepsCount_ = 0;
2147
2166
  this.flags_ = 0b00000;
2167
+ this.removeAbortListener_ = undefined;
2148
2168
  this.name_ = name_;
2149
2169
  this.onInvalidate_ = onInvalidate_;
2150
2170
  this.errorHandler_ = errorHandler_;
@@ -2281,7 +2301,10 @@ class Reaction {
2281
2301
  }
2282
2302
  dispose() {
2283
2303
  if (!this.isDisposed) {
2304
+ var _this$removeAbortList;
2284
2305
  this.isDisposed = true;
2306
+ // also covers self-disposal (`r.dispose()`, `when`), which bypasses the disposer
2307
+ (_this$removeAbortList = this.removeAbortListener_) == null || _this$removeAbortList.call(this);
2285
2308
  if (!this.isRunning) {
2286
2309
  // if disposed while running, clean up later. Maybe not optimal, but rare case
2287
2310
  startBatch();
@@ -2291,11 +2314,11 @@ class Reaction {
2291
2314
  }
2292
2315
  }
2293
2316
  getDisposer_(abortSignal) {
2294
- const dispose = () => {
2295
- this.dispose();
2296
- abortSignal == null || abortSignal.removeEventListener == null || abortSignal.removeEventListener("abort", dispose);
2297
- };
2298
- abortSignal == null || abortSignal.addEventListener == null || abortSignal.addEventListener("abort", dispose);
2317
+ const dispose = () => this.dispose();
2318
+ if (abortSignal != null && abortSignal.addEventListener && !this.isDisposed) {
2319
+ abortSignal.addEventListener("abort", dispose);
2320
+ this.removeAbortListener_ = () => abortSignal.removeEventListener == null ? void 0 : abortSignal.removeEventListener("abort", dispose);
2321
+ }
2299
2322
  dispose[$mobx] = this;
2300
2323
  if ("dispose" in Symbol && typeof Symbol.dispose === "symbol") {
2301
2324
  dispose[Symbol.dispose] = dispose;
@@ -3294,8 +3317,13 @@ function hasInterceptors(interceptable) {
3294
3317
  return interceptable.interceptors_ !== undefined && interceptable.interceptors_.length > 0;
3295
3318
  }
3296
3319
  function registerInterceptor(interceptable, handler) {
3297
- const interceptors = interceptable.interceptors_ || (interceptable.interceptors_ = []);
3298
- interceptors.push(handler);
3320
+ let interceptors = interceptable.interceptors_;
3321
+ if (interceptors) {
3322
+ interceptors.push(handler);
3323
+ } else {
3324
+ // Allocate at the exact size: pushing onto an empty array over-allocates its backing store
3325
+ interceptors = interceptable.interceptors_ = [handler];
3326
+ }
3299
3327
  return once(() => {
3300
3328
  const idx = interceptors.indexOf(handler);
3301
3329
  if (idx !== -1) {
@@ -3327,8 +3355,13 @@ function hasListeners(listenable) {
3327
3355
  return listenable.changeListeners_ !== undefined && listenable.changeListeners_.length > 0;
3328
3356
  }
3329
3357
  function registerListener(listenable, handler) {
3330
- const listeners = listenable.changeListeners_ || (listenable.changeListeners_ = []);
3331
- listeners.push(handler);
3358
+ let listeners = listenable.changeListeners_;
3359
+ if (listeners) {
3360
+ listeners.push(handler);
3361
+ } else {
3362
+ // Allocate at the exact size: pushing onto an empty array over-allocates its backing store
3363
+ listeners = listenable.changeListeners_ = [handler];
3364
+ }
3332
3365
  return once(() => {
3333
3366
  const idx = listeners.indexOf(handler);
3334
3367
  if (idx !== -1) {
@@ -3442,8 +3475,7 @@ const arrayTraps = {
3442
3475
  const adm = target[$mobx];
3443
3476
  if (name === "length") {
3444
3477
  adm.setArrayLength_(value);
3445
- }
3446
- if (typeof name === "symbol" || isNaN(name)) {
3478
+ } else if (typeof name === "symbol" || isNaN(name)) {
3447
3479
  target[name] = value;
3448
3480
  } else {
3449
3481
  // numeric string
@@ -3488,7 +3520,7 @@ class ObservableArrayAdministration {
3488
3520
  return this.values_.length;
3489
3521
  }
3490
3522
  setArrayLength_(newLength) {
3491
- if (typeof newLength !== "number" || isNaN(newLength) || newLength < 0) {
3523
+ if (!Number.isInteger(newLength) || newLength < 0) {
3492
3524
  die(40, newLength);
3493
3525
  }
3494
3526
  let currentLength = this.values_.length;
@@ -4270,7 +4302,8 @@ class ObservableSet {
4270
4302
  }
4271
4303
  if (!this.has(value)) {
4272
4304
  transaction(() => {
4273
- this.data_.add(this.enhancer_(value, undefined));
4305
+ value = this.enhancer_(value, undefined); // report the stored value, like map and array
4306
+ this.data_.add(value);
4274
4307
  this.atom_.reportChanged();
4275
4308
  });
4276
4309
  const notifySpy = isSpyEnabled();