@jbrowse/mobx-state-tree 5.10.8 → 5.11.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.
@@ -4903,7 +4903,7 @@ class ModelType extends ComplexType {
4903
4903
  }
4904
4904
  getSnapshot(node, applyPostProcess = true) {
4905
4905
  const res = {};
4906
- this.forAllProps((name, _type) => {
4906
+ this.forAllProps((name, type) => {
4907
4907
  try {
4908
4908
  // TODO: FIXME, make sure the observable ref is used!
4909
4909
  const atom = getAtom(node.storedValue, name);
@@ -4912,7 +4912,11 @@ class ModelType extends ComplexType {
4912
4912
  catch (_e) {
4913
4913
  throw fail(`${name} property is declared twice`);
4914
4914
  }
4915
- res[name] = this.getChildNode(node, name).snapshot;
4915
+ const snapshot = this.getChildNode(node, name).snapshot;
4916
+ // strip-default optionals omit their key when equal to the default
4917
+ if (!shouldStripChildFromSnapshot(type, snapshot)) {
4918
+ res[name] = snapshot;
4919
+ }
4916
4920
  });
4917
4921
  if (applyPostProcess) {
4918
4922
  return this.applySnapshotPostProcessor(res);
@@ -4922,7 +4926,13 @@ class ModelType extends ComplexType {
4922
4926
  processInitialSnapshot(childNodes) {
4923
4927
  const processed = {};
4924
4928
  Object.keys(childNodes).forEach(key => {
4925
- processed[key] = childNodes[key].getSnapshot();
4929
+ const snapshot = childNodes[key].getSnapshot();
4930
+ // strip-default optionals omit their key when equal to the default; this
4931
+ // mirrors getSnapshot for nodes serialized before they become observable
4932
+ // instances (e.g. array/map children that were never accessed)
4933
+ if (!shouldStripChildFromSnapshot(this.properties[key], snapshot)) {
4934
+ processed[key] = snapshot;
4935
+ }
4926
4936
  });
4927
4937
  return this.applySnapshotPostProcessor(processed);
4928
4938
  }
@@ -5876,6 +5886,84 @@ const undefinedAsOptionalValues = [undefined];
5876
5886
  function isOptionalType(type) {
5877
5887
  return isType(type) && (type.flags & TypeFlags.Optional) > 0;
5878
5888
  }
5889
+ /**
5890
+ * Compare a child snapshot to a stripped-default's reference snapshot. Mirrors
5891
+ * the legacy hand-rolled comparison: identity for primitives, structural for
5892
+ * objects/arrays.
5893
+ */
5894
+ function defaultSnapshotEquals(a, b) {
5895
+ if (a === b) {
5896
+ return true;
5897
+ }
5898
+ if (typeof a === "object" &&
5899
+ a !== null &&
5900
+ typeof b === "object" &&
5901
+ b !== null) {
5902
+ // Cheap structural short-circuit before the full stringify compare: a
5903
+ // value of a different size can't equal the default, so the common strip
5904
+ // case (non-empty value vs an empty `[]`/`{}` default) avoids stringifying
5905
+ // a potentially large snapshot on every getSnapshot.
5906
+ if (Array.isArray(a) !== Array.isArray(b)) {
5907
+ return false;
5908
+ }
5909
+ const aSize = Array.isArray(a) ? a.length : Object.keys(a).length;
5910
+ const bSize = Array.isArray(b) ? b.length : Object.keys(b).length;
5911
+ if (aSize !== bSize) {
5912
+ return false;
5913
+ }
5914
+ return JSON.stringify(a) === JSON.stringify(b);
5915
+ }
5916
+ return false;
5917
+ }
5918
+ /**
5919
+ * An optional type that additionally omits its key from a parent model's
5920
+ * snapshot when the value equals the (snapshotted) default. The comparison is
5921
+ * against the default *snapshot* — i.e. the subtype is instantiated with the
5922
+ * default once and its post-processed snapshot is cached — so a default whose
5923
+ * normalized form gains fields (e.g. a fileLocation gaining `locationType`)
5924
+ * still strips correctly.
5925
+ *
5926
+ * @hidden
5927
+ * @internal
5928
+ */
5929
+ class StripDefaultValue extends OptionalValue {
5930
+ _defaultSnapshot;
5931
+ shouldStripFromSnapshot(snapshot) {
5932
+ if (!this._defaultSnapshot) {
5933
+ // instantiate the subtype detached with the default and read the node's
5934
+ // snapshot, which normalizes (fills model defaults, applies the subtype's
5935
+ // own postProcess). Cached on the (singleton) type after first use.
5936
+ const node = this.getSubTypes().instantiate(null, "", undefined, this.getDefaultInstanceOrSnapshot());
5937
+ this._defaultSnapshot = { value: node.snapshot };
5938
+ }
5939
+ return defaultSnapshotEquals(snapshot, this._defaultSnapshot.value);
5940
+ }
5941
+ }
5942
+ /**
5943
+ * Whether `type` is a strip-default optional whose current child `snapshot`
5944
+ * equals its default and should therefore be omitted from the parent model's
5945
+ * snapshot. Used by `ModelType.getSnapshot`.
5946
+ *
5947
+ * @hidden
5948
+ * @internal
5949
+ */
5950
+ function shouldStripChildFromSnapshot(type, snapshot) {
5951
+ return (type instanceof StripDefaultValue && type.shouldStripFromSnapshot(snapshot));
5952
+ }
5953
+ /**
5954
+ * `types.stripDefault` - Like `types.optional`, but the property is omitted from
5955
+ * a parent model's snapshot entirely when its value equals the default (instead
5956
+ * of being serialized with the default value). Lets a model produce minimal
5957
+ * snapshots without a bespoke `postProcessSnapshot`.
5958
+ *
5959
+ * @param type
5960
+ * @param defaultValueOrFunction
5961
+ * @returns
5962
+ */
5963
+ function stripDefault(type, defaultValueOrFunction) {
5964
+ checkOptionalPreconditions(type, defaultValueOrFunction);
5965
+ return new StripDefaultValue(type, defaultValueOrFunction, undefinedAsOptionalValues);
5966
+ }
5879
5967
 
5880
5968
  const optionalUndefinedType = optional(undefinedType, undefined);
5881
5969
  const optionalNullType = optional(nullType, null);
@@ -6893,6 +6981,7 @@ const types = {
6893
6981
  safeReference,
6894
6982
  union,
6895
6983
  optional,
6984
+ stripDefault,
6896
6985
  literal,
6897
6986
  maybe,
6898
6987
  maybeNull,