@jbrowse/mobx-state-tree 5.11.1 → 5.12.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.
@@ -764,10 +764,11 @@ function getMembers(target) {
764
764
  }
765
765
  return;
766
766
  }
767
- if (descriptor.value._isFlowAction === true) {
767
+ const value = descriptor.value;
768
+ if (value?._isFlowAction === true) {
768
769
  reflected.flowActions.push(key);
769
770
  }
770
- if (descriptor.value._isMSTAction === true) {
771
+ if (value?._isMSTAction === true) {
771
772
  reflected.actions.push(key);
772
773
  }
773
774
  else if (isObservableProp(target, key)) {
@@ -1116,6 +1117,21 @@ class ScalarNode extends BaseNode {
1116
1117
  ScalarNode.prototype.die = action(ScalarNode.prototype.die);
1117
1118
 
1118
1119
  let nextNodeId = 1;
1120
+ var ObservableInstanceLifecycle;
1121
+ (function (ObservableInstanceLifecycle) {
1122
+ // the actual observable instance has not been created yet
1123
+ ObservableInstanceLifecycle[ObservableInstanceLifecycle["UNINITIALIZED"] = 0] = "UNINITIALIZED";
1124
+ // the actual observable instance is being created
1125
+ ObservableInstanceLifecycle[ObservableInstanceLifecycle["CREATING"] = 1] = "CREATING";
1126
+ // the actual observable instance has been created
1127
+ ObservableInstanceLifecycle[ObservableInstanceLifecycle["CREATED"] = 2] = "CREATED";
1128
+ })(ObservableInstanceLifecycle || (ObservableInstanceLifecycle = {}));
1129
+ var InternalEvents;
1130
+ (function (InternalEvents) {
1131
+ InternalEvents["Dispose"] = "dispose";
1132
+ InternalEvents["Patch"] = "patch";
1133
+ InternalEvents["Snapshot"] = "snapshot";
1134
+ })(InternalEvents || (InternalEvents = {}));
1119
1135
  const snapshotReactionOptions = {
1120
1136
  onError(e) {
1121
1137
  throw e;
@@ -1147,7 +1163,7 @@ class ObjectNode extends BaseNode {
1147
1163
  _autoUnbox = true; // unboxing is disabled when reading child nodes
1148
1164
  _isRunningAction = false; // only relevant for root
1149
1165
  _hasSnapshotReaction = false;
1150
- _observableInstanceState = 0 /* ObservableInstanceLifecycle.UNINITIALIZED */;
1166
+ _observableInstanceState = ObservableInstanceLifecycle.UNINITIALIZED;
1151
1167
  _childNodes;
1152
1168
  _initialSnapshot;
1153
1169
  _cachedInitialSnapshot;
@@ -1192,7 +1208,7 @@ class ObjectNode extends BaseNode {
1192
1208
  }
1193
1209
  createObservableInstanceIfNeeded(fireHooks = true) {
1194
1210
  if (this._observableInstanceState ===
1195
- 0 /* ObservableInstanceLifecycle.UNINITIALIZED */) {
1211
+ ObservableInstanceLifecycle.UNINITIALIZED) {
1196
1212
  this.createObservableInstance(fireHooks);
1197
1213
  }
1198
1214
  }
@@ -1203,7 +1219,7 @@ class ObjectNode extends BaseNode {
1203
1219
  throw fail("assertion failed: the creation of the observable instance must be done on the initializing phase");
1204
1220
  }
1205
1221
  }
1206
- this._observableInstanceState = 1 /* ObservableInstanceLifecycle.CREATING */;
1222
+ this._observableInstanceState = ObservableInstanceLifecycle.CREATING;
1207
1223
  // make sure the parent chain is created as well
1208
1224
  // array with parent chain from parent to child
1209
1225
  const parentChain = [];
@@ -1213,7 +1229,7 @@ class ObjectNode extends BaseNode {
1213
1229
  // this is done to avoid traversing the whole tree to the root when using
1214
1230
  // the same reference again
1215
1231
  while (parent?._observableInstanceState ===
1216
- 0 /* ObservableInstanceLifecycle.UNINITIALIZED */) {
1232
+ ObservableInstanceLifecycle.UNINITIALIZED) {
1217
1233
  parentChain.unshift(parent);
1218
1234
  parent = parent.parent;
1219
1235
  }
@@ -1238,7 +1254,7 @@ class ObjectNode extends BaseNode {
1238
1254
  finally {
1239
1255
  this._isRunningAction = false;
1240
1256
  }
1241
- this._observableInstanceState = 2 /* ObservableInstanceLifecycle.CREATED */;
1257
+ this._observableInstanceState = ObservableInstanceLifecycle.CREATED;
1242
1258
  this._snapshotComputed.trackAndCompute();
1243
1259
  if (this.isRoot) {
1244
1260
  this._addSnapshotReaction();
@@ -1352,7 +1368,7 @@ class ObjectNode extends BaseNode {
1352
1368
  if (!this.isAlive) {
1353
1369
  return this._snapshotUponDeath;
1354
1370
  }
1355
- return this._observableInstanceState === 2 /* ObservableInstanceLifecycle.CREATED */
1371
+ return this._observableInstanceState === ObservableInstanceLifecycle.CREATED
1356
1372
  ? this._getActualSnapshot()
1357
1373
  : this._getCachedInitialSnapshot();
1358
1374
  }
@@ -1415,7 +1431,7 @@ class ObjectNode extends BaseNode {
1415
1431
  this._autoUnbox = false;
1416
1432
  try {
1417
1433
  return this._observableInstanceState ===
1418
- 2 /* ObservableInstanceLifecycle.CREATED */
1434
+ ObservableInstanceLifecycle.CREATED
1419
1435
  ? this.type.getChildNode(this, subpath)
1420
1436
  : this._childNodes[subpath];
1421
1437
  }
@@ -1428,7 +1444,7 @@ class ObjectNode extends BaseNode {
1428
1444
  this._autoUnbox = false;
1429
1445
  try {
1430
1446
  return this._observableInstanceState ===
1431
- 2 /* ObservableInstanceLifecycle.CREATED */
1447
+ ObservableInstanceLifecycle.CREATED
1432
1448
  ? this.type.getChildren(this)
1433
1449
  : convertChildNodesToArray(this._childNodes);
1434
1450
  }
@@ -1514,7 +1530,7 @@ class ObjectNode extends BaseNode {
1514
1530
  }
1515
1531
  aboutToDie() {
1516
1532
  if (this._observableInstanceState ===
1517
- 0 /* ObservableInstanceLifecycle.UNINITIALIZED */) {
1533
+ ObservableInstanceLifecycle.UNINITIALIZED) {
1518
1534
  return;
1519
1535
  }
1520
1536
  this.getChildren().forEach(node => {
@@ -1523,8 +1539,8 @@ class ObjectNode extends BaseNode {
1523
1539
  // beforeDestroy should run before the disposers since else we could end up in a situation where
1524
1540
  // a disposer added with addDisposer at this stage (beforeDestroy) is actually never released
1525
1541
  this.baseAboutToDie();
1526
- this._internalEventsEmit("dispose" /* InternalEvents.Dispose */);
1527
- this._internalEventsClear("dispose" /* InternalEvents.Dispose */);
1542
+ this._internalEventsEmit(InternalEvents.Dispose);
1543
+ this._internalEventsClear(InternalEvents.Dispose);
1528
1544
  }
1529
1545
  finalizeDeath() {
1530
1546
  // invariant: not called directly but from "die"
@@ -1540,42 +1556,42 @@ class ObjectNode extends BaseNode {
1540
1556
  }
1541
1557
  onSnapshot(onChange) {
1542
1558
  this._addSnapshotReaction();
1543
- return this._internalEventsRegister("snapshot" /* InternalEvents.Snapshot */, onChange);
1559
+ return this._internalEventsRegister(InternalEvents.Snapshot, onChange);
1544
1560
  }
1545
1561
  emitSnapshot(snapshot) {
1546
- this._internalEventsEmit("snapshot" /* InternalEvents.Snapshot */, snapshot);
1562
+ this._internalEventsEmit(InternalEvents.Snapshot, snapshot);
1547
1563
  }
1548
1564
  onPatch(handler) {
1549
- return this._internalEventsRegister("patch" /* InternalEvents.Patch */, handler);
1565
+ return this._internalEventsRegister(InternalEvents.Patch, handler);
1550
1566
  }
1551
1567
  emitPatch(basePatch, source) {
1552
- if (this._internalEventsHasSubscribers("patch" /* InternalEvents.Patch */)) {
1568
+ if (this._internalEventsHasSubscribers(InternalEvents.Patch)) {
1553
1569
  const localizedPatch = {
1554
1570
  ...basePatch,
1555
- path: `${source.path.substr(this.path.length)}/${basePatch.path}` // calculate the relative path of the patch
1571
+ path: `${source.path.slice(this.path.length)}/${basePatch.path}` // calculate the relative path of the patch
1556
1572
  };
1557
1573
  const [patch, reversePatch] = splitPatch(localizedPatch);
1558
- this._internalEventsEmit("patch" /* InternalEvents.Patch */, patch, reversePatch);
1574
+ this._internalEventsEmit(InternalEvents.Patch, patch, reversePatch);
1559
1575
  }
1560
1576
  if (this.parent) {
1561
1577
  this.parent.emitPatch(basePatch, source);
1562
1578
  }
1563
1579
  }
1564
1580
  hasDisposer(disposer) {
1565
- return this._internalEventsHas("dispose" /* InternalEvents.Dispose */, disposer);
1581
+ return this._internalEventsHas(InternalEvents.Dispose, disposer);
1566
1582
  }
1567
1583
  addDisposer(disposer) {
1568
1584
  if (!this.hasDisposer(disposer)) {
1569
- this._internalEventsRegister("dispose" /* InternalEvents.Dispose */, disposer, true);
1585
+ this._internalEventsRegister(InternalEvents.Dispose, disposer, true);
1570
1586
  return;
1571
1587
  }
1572
1588
  throw fail("cannot add a disposer when it is already registered for execution");
1573
1589
  }
1574
1590
  removeDisposer(disposer) {
1575
- if (!this._internalEventsHas("dispose" /* InternalEvents.Dispose */, disposer)) {
1591
+ if (!this._internalEventsHas(InternalEvents.Dispose, disposer)) {
1576
1592
  throw fail("cannot remove a disposer which was never registered for execution");
1577
1593
  }
1578
- this._internalEventsUnregister("dispose" /* InternalEvents.Dispose */, disposer);
1594
+ this._internalEventsUnregister(InternalEvents.Dispose, disposer);
1579
1595
  }
1580
1596
  removeMiddleware(middleware) {
1581
1597
  if (this.middlewares) {
@@ -1651,9 +1667,6 @@ ObjectNode.prototype.createObservableInstance = action(ObjectNode.prototype.crea
1651
1667
  ObjectNode.prototype.detach = action(ObjectNode.prototype.detach);
1652
1668
  ObjectNode.prototype.die = action(ObjectNode.prototype.die);
1653
1669
 
1654
- // Cache for validation results to avoid re-validating the same object against the same type
1655
- // Uses WeakMap so cached objects can be garbage collected
1656
- const validationCache = new WeakMap();
1657
1670
  /**
1658
1671
  * @internal
1659
1672
  * @hidden
@@ -1729,27 +1742,7 @@ class BaseType {
1729
1742
  : typeCheckFailure(context, value);
1730
1743
  // it is tempting to compare snapshots, but in that case we should always clone on assignments...
1731
1744
  }
1732
- // check cache for object values (only at root level to avoid context mismatches)
1733
- if (typeof value === "object" && value !== null && context.length === 1) {
1734
- const typeCache = validationCache.get(value);
1735
- if (typeCache) {
1736
- const cached = typeCache.get(this);
1737
- if (cached !== undefined) {
1738
- return cached;
1739
- }
1740
- }
1741
- }
1742
- const result = this.isValidSnapshot(value, context);
1743
- // cache result for object values (only at root level)
1744
- if (typeof value === "object" && value !== null && context.length === 1) {
1745
- let typeCache = validationCache.get(value);
1746
- if (!typeCache) {
1747
- typeCache = new WeakMap();
1748
- validationCache.set(value, typeCache);
1749
- }
1750
- typeCache.set(this, result);
1751
- }
1752
- return result;
1745
+ return this.isValidSnapshot(value, context);
1753
1746
  }
1754
1747
  is(thing) {
1755
1748
  return this.validate(thing, [{ path: "", type: this }]).length === 0;
@@ -2565,22 +2558,47 @@ function isActionContextThisOrChildOf(actionContext, parentOrThis) {
2565
2558
  }
2566
2559
 
2567
2560
  const MAX_STRINGIFY_DEPTH = 3;
2561
+ const MAX_STRINGIFY_ARRAY_ITEMS = 10;
2562
+ const MAX_STRINGIFY_OBJECT_KEYS = 20;
2563
+ const MAX_STRINGIFY_STRING_LENGTH = 100;
2564
+ // Recursively cap depth, array length, object key count, and string length so a
2565
+ // huge offending snapshot degrades into readable, bounded context instead of a
2566
+ // wall of text that then gets sliced mid-structure. The depth cap also bounds
2567
+ // recursion on circular structures.
2568
+ function truncateForStringify(value, depth) {
2569
+ if (typeof value === "string") {
2570
+ return value.length > MAX_STRINGIFY_STRING_LENGTH
2571
+ ? `${value.substring(0, MAX_STRINGIFY_STRING_LENGTH)}… (${value.length - MAX_STRINGIFY_STRING_LENGTH} more characters)`
2572
+ : value;
2573
+ }
2574
+ if (value === null || typeof value !== "object") {
2575
+ return value;
2576
+ }
2577
+ if (depth >= MAX_STRINGIFY_DEPTH) {
2578
+ return Array.isArray(value) ? "[…]" : "{…}";
2579
+ }
2580
+ if (Array.isArray(value)) {
2581
+ const items = value
2582
+ .slice(0, MAX_STRINGIFY_ARRAY_ITEMS)
2583
+ .map(item => truncateForStringify(item, depth + 1));
2584
+ if (value.length > MAX_STRINGIFY_ARRAY_ITEMS) {
2585
+ items.push(`… ${value.length - MAX_STRINGIFY_ARRAY_ITEMS} more items`);
2586
+ }
2587
+ return items;
2588
+ }
2589
+ const keys = Object.keys(value);
2590
+ const result = {};
2591
+ for (const key of keys.slice(0, MAX_STRINGIFY_OBJECT_KEYS)) {
2592
+ result[key] = truncateForStringify(value[key], depth + 1);
2593
+ }
2594
+ if (keys.length > MAX_STRINGIFY_OBJECT_KEYS) {
2595
+ result["…"] = `${keys.length - MAX_STRINGIFY_OBJECT_KEYS} more keys`;
2596
+ }
2597
+ return result;
2598
+ }
2568
2599
  function safeStringify(value) {
2569
2600
  try {
2570
- const ancestors = [];
2571
- return JSON.stringify(value, function (_key, val) {
2572
- if (val !== null && typeof val === "object") {
2573
- while (ancestors.length > 0 &&
2574
- ancestors[ancestors.length - 1] !== this) {
2575
- ancestors.pop();
2576
- }
2577
- if (ancestors.length >= MAX_STRINGIFY_DEPTH) {
2578
- return Array.isArray(val) ? "[…]" : "{…}";
2579
- }
2580
- ancestors.push(val);
2581
- }
2582
- return val;
2583
- });
2601
+ return JSON.stringify(truncateForStringify(value, 0));
2584
2602
  }
2585
2603
  catch (e) {
2586
2604
  // istanbul ignore next
@@ -3011,31 +3029,6 @@ function convertChildNodesToArray(childNodes) {
3011
3029
  return result;
3012
3030
  }
3013
3031
 
3014
- // based on: https://github.com/mobxjs/mobx-utils/blob/master/src/async-action.ts
3015
- /*
3016
- All contents of this file are deprecated.
3017
-
3018
- The term `process` has been replaced with `flow` to avoid conflicts with the
3019
- global `process` object.
3020
-
3021
- Refer to `flow.ts` for any further changes to this implementation.
3022
- */
3023
- const DEPRECATION_MESSAGE = "See https://github.com/mobxjs/mobx-state-tree/issues/399 for more information. " +
3024
- "Note that the middleware event types starting with `process` now start with `flow`.";
3025
- /**
3026
- * @hidden
3027
- *
3028
- * @deprecated has been renamed to `flow()`.
3029
- * See https://github.com/mobxjs/mobx-state-tree/issues/399 for more information.
3030
- * Note that the middleware event types starting with `process` now start with `flow`.
3031
- *
3032
- * @returns {Promise}
3033
- */
3034
- function process$1(asyncAction) {
3035
- deprecated("process", `\`process()\` has been renamed to \`flow()\`. ${DEPRECATION_MESSAGE}`);
3036
- return flow(asyncAction);
3037
- }
3038
-
3039
3032
  const plainObjectString = Object.toString();
3040
3033
  /**
3041
3034
  * @internal
@@ -3224,8 +3217,6 @@ function addHiddenWritableProp(object, propName, value) {
3224
3217
  */
3225
3218
  class EventHandler {
3226
3219
  handlers = [];
3227
- emitting = false;
3228
- pendingUnregisters = null;
3229
3220
  get hasSubscribers() {
3230
3221
  return this.handlers.length > 0;
3231
3222
  }
@@ -3244,14 +3235,6 @@ class EventHandler {
3244
3235
  return this.handlers.includes(fn);
3245
3236
  }
3246
3237
  unregister(fn) {
3247
- if (this.emitting) {
3248
- // defer unregistration until emit is done
3249
- if (!this.pendingUnregisters) {
3250
- this.pendingUnregisters = [];
3251
- }
3252
- this.pendingUnregisters.push(fn);
3253
- return;
3254
- }
3255
3238
  const index = this.handlers.indexOf(fn);
3256
3239
  if (index >= 0) {
3257
3240
  this.handlers.splice(index, 1);
@@ -3261,26 +3244,10 @@ class EventHandler {
3261
3244
  this.handlers.length = 0;
3262
3245
  }
3263
3246
  emit(...args) {
3264
- // use emitting flag to defer unregistrations instead of copying array
3265
- this.emitting = true;
3266
- try {
3267
- for (const f of this.handlers) {
3268
- f(...args);
3269
- }
3270
- }
3271
- finally {
3272
- this.emitting = false;
3273
- // process any deferred unregistrations
3274
- if (this.pendingUnregisters) {
3275
- for (const fn of this.pendingUnregisters) {
3276
- const index = this.handlers.indexOf(fn);
3277
- if (index >= 0) {
3278
- this.handlers.splice(index, 1);
3279
- }
3280
- }
3281
- this.pendingUnregisters = null;
3282
- }
3283
- }
3247
+ // iterate a copy so (un)registrations during emit don't disturb this pass
3248
+ // and reentrant emits stay correct
3249
+ const handlers = this.handlers.slice();
3250
+ handlers.forEach(f => f(...args));
3284
3251
  }
3285
3252
  }
3286
3253
  /**
@@ -3326,25 +3293,6 @@ class EventHandlers {
3326
3293
  }
3327
3294
  }
3328
3295
  }
3329
- /**
3330
- * @internal
3331
- * @hidden
3332
- */
3333
- const deprecated = function (id, message) {
3334
- // skip if running production
3335
- if (!devMode()) {
3336
- return;
3337
- }
3338
- // warn if hasn't been warned before
3339
- if (deprecated.ids && !deprecated.ids.hasOwnProperty(id)) {
3340
- warnError(`Deprecation warning: ${message}`);
3341
- }
3342
- // mark as warned to avoid duplicate warn message
3343
- if (deprecated.ids) {
3344
- deprecated.ids[id] = true;
3345
- }
3346
- };
3347
- deprecated.ids = {};
3348
3296
  /**
3349
3297
  * @internal
3350
3298
  * @hidden
@@ -4297,9 +4245,9 @@ class ArrayType extends ComplexType {
4297
4245
  return node.storedValue.slice();
4298
4246
  }
4299
4247
  getChildNode(node, key) {
4300
- const index = Number(key);
4301
- if (index < node.storedValue.length) {
4302
- return node.storedValue[index];
4248
+ const child = node.storedValue[Number(key)];
4249
+ if (child) {
4250
+ return child;
4303
4251
  }
4304
4252
  throw fail(`Not a child: ${key}`);
4305
4253
  }
@@ -4452,6 +4400,23 @@ function array(subtype) {
4452
4400
  }
4453
4401
  function reconcileArrayChildren(parent, childType, oldNodes, newValues, newPaths) {
4454
4402
  let nothingChanged = true;
4403
+ // When the element type is a plain model with an identifier, index the old
4404
+ // nodes by id so a moved / replaced child is matched in O(1) instead of the
4405
+ // linear scan in the reorder branch below. This turns a full array
4406
+ // replacement (the common "load new data" case) from O(n^2) into O(n). Types
4407
+ // whose id extraction needs type-specific preprocessing (union,
4408
+ // snapshotProcessor, late, ...) are intentionally excluded: areSame must run
4409
+ // `is()` before their id check, so they stay on the scan path.
4410
+ let idIndex;
4411
+ if (childType instanceof ModelType && childType.identifierAttribute) {
4412
+ const byId = new Map();
4413
+ for (const n of oldNodes) {
4414
+ if (n instanceof ObjectNode && n.identifier !== null) {
4415
+ byId.set(n.identifier, n);
4416
+ }
4417
+ }
4418
+ idIndex = { attr: childType.identifierAttribute, byId };
4419
+ }
4455
4420
  for (let i = 0;; i++) {
4456
4421
  const hasNewNode = i <= newValues.length - 1;
4457
4422
  const oldNode = oldNodes[i];
@@ -4466,7 +4431,7 @@ function reconcileArrayChildren(parent, childType, oldNodes, newValues, newPaths
4466
4431
  // both are empty, end
4467
4432
  break;
4468
4433
  }
4469
- else if (!hasNewNode) {
4434
+ else if (oldNode && !hasNewNode) {
4470
4435
  // new one does not exists
4471
4436
  nothingChanged = false;
4472
4437
  oldNodes.splice(i, 1);
@@ -4495,13 +4460,26 @@ function reconcileArrayChildren(parent, childType, oldNodes, newValues, newPaths
4495
4460
  oldNodes[i] = valueAsNode(childType, parent, newPath, newValue, oldNode);
4496
4461
  }
4497
4462
  else {
4498
- // nothing to do, try to reorder
4463
+ // nothing to do, try to reorder: find a candidate old node to reuse
4499
4464
  let oldMatch = undefined;
4500
- // find a possible candidate to reuse
4501
- for (let j = i; j < oldNodes.length; j++) {
4502
- if (areSame(oldNodes[j], newValue)) {
4465
+ if (idIndex && isPlainObject(newValue)) {
4466
+ // for an identified element type a plain-object snapshot can only match
4467
+ // by id (it is never a live node, and snapshot-reference equality still
4468
+ // implies the same id), so a miss means "nothing to reuse" without
4469
+ // scanning. areSame still verifies the single candidate.
4470
+ const candidate = idIndex.byId.get(normalizeIdentifier(newValue[idIndex.attr]));
4471
+ const j = candidate ? oldNodes.indexOf(candidate, i) : -1;
4472
+ if (j >= i && areSame(oldNodes[j], newValue)) {
4503
4473
  oldMatch = oldNodes.splice(j, 1)[0];
4504
- break;
4474
+ }
4475
+ }
4476
+ else {
4477
+ // find a possible candidate to reuse
4478
+ for (let j = i; j < oldNodes.length; j++) {
4479
+ if (areSame(oldNodes[j], newValue)) {
4480
+ oldMatch = oldNodes.splice(j, 1)[0];
4481
+ break;
4482
+ }
4505
4483
  }
4506
4484
  }
4507
4485
  nothingChanged = false;
@@ -4661,6 +4639,9 @@ class ModelType extends ComplexType {
4661
4639
  preProcessor;
4662
4640
  postProcessor;
4663
4641
  propertyNames;
4642
+ // member/property name collisions are a property of the type, so we only need
4643
+ // to check the first instance we finalize (see finalizeNewInstance)
4644
+ duplicateKeysChecked = false;
4664
4645
  constructor(opts) {
4665
4646
  super(opts.name || defaultObjectOptions.name);
4666
4647
  Object.assign(this, defaultObjectOptions, opts);
@@ -4850,6 +4831,21 @@ class ModelType extends ComplexType {
4850
4831
  _interceptReads(instance, name, node.unbox);
4851
4832
  });
4852
4833
  this.initializers.reduce((self, fn) => fn(self), instance);
4834
+ // views, actions and volatile share the instance namespace with properties,
4835
+ // so a view/action reusing a property name silently clobbers that property's
4836
+ // observable value. It's a type-level mistake (identical for every instance),
4837
+ // so we check just the first one we finalize: a healthy property stays a
4838
+ // plain observable, a shadowing getter becomes a computed, and a shadowing
4839
+ // function/value stops being observable at all. The flag is only set on
4840
+ // success so a broken type keeps throwing on every create.
4841
+ if (!this.duplicateKeysChecked) {
4842
+ this.forAllProps(name => {
4843
+ if (isComputedProp(instance, name) || !isObservableProp(instance, name)) {
4844
+ throw fail(`${name} property is declared twice`);
4845
+ }
4846
+ });
4847
+ this.duplicateKeysChecked = true;
4848
+ }
4853
4849
  intercept(instance, this.willChange);
4854
4850
  observe(instance, this.didChange);
4855
4851
  }
@@ -4904,14 +4900,7 @@ class ModelType extends ComplexType {
4904
4900
  getSnapshot(node, applyPostProcess = true) {
4905
4901
  const res = {};
4906
4902
  this.forAllProps((name, type) => {
4907
- try {
4908
- // TODO: FIXME, make sure the observable ref is used!
4909
- const atom = getAtom(node.storedValue, name);
4910
- atom.reportObserved();
4911
- }
4912
- catch (_e) {
4913
- throw fail(`${name} property is declared twice`);
4914
- }
4903
+ getAtom(node.storedValue, name).reportObserved();
4915
4904
  const snapshot = this.getChildNode(node, name).snapshot;
4916
4905
  // strip-default optionals omit their key when equal to the default
4917
4906
  if (!shouldStripChildFromSnapshot(type, snapshot)) {
@@ -5523,22 +5512,14 @@ class Union extends BaseType {
5523
5512
  if (this._dispatcher) {
5524
5513
  return this._dispatcher(value);
5525
5514
  }
5526
- // fast path: when type checking is disabled, try quick structural matching first
5515
+ // fast path: when type checking is disabled, try quick structural matching
5516
+ // first. This skips full recursive validation of every property value, a
5517
+ // meaningful win for wide model members (e.g. jbrowse config schemas).
5527
5518
  if (!isTypeCheckingEnabled()) {
5528
5519
  const quickMatch = this.tryQuickMatch(value, reconcileCurrentType);
5529
5520
  if (quickMatch) {
5530
5521
  return quickMatch;
5531
5522
  }
5532
- // for plain object snapshots that didn't match via quick path, try all types
5533
- // with quick matching before falling back to full validation
5534
- // (state tree nodes must go through full validation for type identity checks)
5535
- if (isPlainObject(value) && !isStateTreeNode(value)) {
5536
- for (const type of this._types) {
5537
- if (this.snapshotLooksLikeType(value, type)) {
5538
- return type;
5539
- }
5540
- }
5541
- }
5542
5523
  }
5543
5524
  // find the most accomodating type
5544
5525
  // if we are using reconciliation try the current node type first (fix for #1045)
@@ -7006,5 +6987,5 @@ const types = {
7006
6987
  resilient
7007
6988
  };
7008
6989
 
7009
- export { addDisposer, addMiddleware, applyAction, applyPatch, applySnapshot, cast, castFlowReturn, castToReferenceSnapshot, castToSnapshot, clone, createActionTrackingMiddleware, createActionTrackingMiddleware2, decorate, destroy, detach, escapeJsonPath, flow, getChildType, getEnv, getIdentifier, getLivelinessChecking, getMembers, getNodeId, getParent, getParentOfType, getPath, getPathParts, getPropertyMembers, getRelativePath, getRoot, getRunningActionContext, getSnapshot, getType, getUnionSubtypes, hasParent, hasParentOfType, isActionContextChildOf, isActionContextThisOrChildOf, isAlive, isArrayType, isFrozenType, isIdentifierType, isLateType, isLiteralType, isMapType, isModelType, isOptionalType, isPrimitiveType, isProtected, isReferenceType, isRefinementType, isRoot, isStateTreeNode, isType, isUnionType, isValidReference, joinJsonPath, onAction, onPatch, onSnapshot, process$1 as process, protect, recordActions, recordPatches, resolveIdentifier, resolvePath, setDevMode, setLivelinessChecking, setLivelynessChecking, setTypeChecking, splitJsonPath, types as t, toGenerator, toGeneratorFunction, tryReference, tryResolve, typecheck, types, unescapeJsonPath, unprotect, walk };
6990
+ export { addDisposer, addMiddleware, applyAction, applyPatch, applySnapshot, cast, castFlowReturn, castToReferenceSnapshot, castToSnapshot, clone, createActionTrackingMiddleware, createActionTrackingMiddleware2, decorate, destroy, detach, escapeJsonPath, flow, getChildType, getEnv, getIdentifier, getLivelinessChecking, getMembers, getNodeId, getParent, getParentOfType, getPath, getPathParts, getPropertyMembers, getRelativePath, getRoot, getRunningActionContext, getSnapshot, getType, getUnionSubtypes, hasParent, hasParentOfType, isActionContextChildOf, isActionContextThisOrChildOf, isAlive, isArrayType, isFrozenType, isIdentifierType, isLateType, isLiteralType, isMapType, isModelType, isOptionalType, isPrimitiveType, isProtected, isReferenceType, isRefinementType, isRoot, isStateTreeNode, isType, isUnionType, isValidReference, joinJsonPath, onAction, onPatch, onSnapshot, protect, recordActions, recordPatches, resolveIdentifier, resolvePath, setDevMode, setLivelinessChecking, setLivelynessChecking, setTypeChecking, splitJsonPath, types as t, toGenerator, toGeneratorFunction, tryReference, tryResolve, typecheck, types, unescapeJsonPath, unprotect, walk };
7010
6991
  //# sourceMappingURL=mobx-state-tree.mjs.map