r-state-tree 0.6.3 → 0.8.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.
@@ -58,6 +58,152 @@ const modelType = {
58
58
  type: "model"
59
59
  /* model */
60
60
  };
61
+ const computedType = {
62
+ type: "computed"
63
+ /* computed */
64
+ };
65
+ function makeDecorator(type) {
66
+ return function(value, context) {
67
+ context.metadata[context.name] = type;
68
+ return value;
69
+ };
70
+ }
71
+ function makeChildDecorator(typeObj) {
72
+ return function(valueOrChildType, context) {
73
+ if (context !== void 0) {
74
+ return makeDecorator(typeObj)(valueOrChildType, context);
75
+ }
76
+ const childCtor = valueOrChildType;
77
+ const typeWithCtor = typeObj(childCtor);
78
+ const decorator = function(value, context2) {
79
+ return makeDecorator(typeWithCtor)(value, context2);
80
+ };
81
+ decorator.type = typeWithCtor?.type;
82
+ decorator.childType = childCtor;
83
+ return decorator;
84
+ };
85
+ }
86
+ const child = makeChildDecorator(childType);
87
+ const modelRefDecorator = makeChildDecorator(modelRefType);
88
+ function modelRef(childCtor, context) {
89
+ if (context !== void 0) {
90
+ throw new Error(
91
+ "r-state-tree: @modelRef requires a model constructor, for example `@modelRef(User)`"
92
+ );
93
+ }
94
+ return modelRefDecorator(childCtor);
95
+ }
96
+ const model = makeDecorator(modelType);
97
+ const id = makeDecorator(idType);
98
+ const state = makeDecorator(stateType);
99
+ function hasConfigLike(value) {
100
+ if (!value) return false;
101
+ const t = typeof value;
102
+ if (t !== "object" && t !== "function") return false;
103
+ return "type" in value;
104
+ }
105
+ function normalizeEntry(entry) {
106
+ if (!entry) return void 0;
107
+ if (hasConfigLike(entry)) return entry;
108
+ if (typeof entry === "function") {
109
+ if (entry === id) return idType;
110
+ if (entry === state) return stateType;
111
+ if (entry === model) return modelType;
112
+ if (entry === child) return childType;
113
+ if (entry === modelRef) {
114
+ throw new Error(
115
+ "r-state-tree: modelRef requires a model constructor, for example `modelRef(User)`"
116
+ );
117
+ }
118
+ return void 0;
119
+ }
120
+ return void 0;
121
+ }
122
+ function getConfigType(entry) {
123
+ const normalized = normalizeEntry(entry);
124
+ return normalized && hasConfigLike(normalized) ? normalized.type : void 0;
125
+ }
126
+ function getConfigChildType(entry) {
127
+ const normalized = normalizeEntry(entry);
128
+ if (!normalized || !hasConfigLike(normalized)) return void 0;
129
+ if (typeof normalized === "function") {
130
+ return normalized.childType;
131
+ }
132
+ return normalized.childType;
133
+ }
134
+ function getParentConstructor(Ctor) {
135
+ return Ctor?.prototype ? Object.getPrototypeOf(Ctor.prototype)?.constructor : void 0;
136
+ }
137
+ function getCtorChain(ctor) {
138
+ const chain = [];
139
+ let node = ctor;
140
+ while (node) {
141
+ chain.push(node);
142
+ const parent = getParentConstructor(node);
143
+ if (!parent || parent === Object || parent === Function) {
144
+ break;
145
+ }
146
+ node = parent;
147
+ }
148
+ chain.reverse();
149
+ return chain;
150
+ }
151
+ function getMetadataSymbol() {
152
+ const sym = Symbol.metadata;
153
+ return typeof sym === "symbol" ? sym : void 0;
154
+ }
155
+ function getOwnMetadata(ctor) {
156
+ const metadataSymbol = getMetadataSymbol();
157
+ if (!metadataSymbol) return void 0;
158
+ return Object.prototype.hasOwnProperty.call(ctor, metadataSymbol) ? ctor[metadataSymbol] : void 0;
159
+ }
160
+ function getOwnStaticTypes(ctor) {
161
+ return Object.prototype.hasOwnProperty.call(ctor, "types") ? ctor.types : void 0;
162
+ }
163
+ function mergeInto(target, source2) {
164
+ if (!source2 || typeof source2 !== "object") return;
165
+ for (const key of Reflect.ownKeys(source2)) {
166
+ const value = source2[key];
167
+ const normalized = normalizeEntry(value);
168
+ if (normalized) {
169
+ target[key] = normalized;
170
+ }
171
+ }
172
+ }
173
+ const configurationCache = /* @__PURE__ */ new WeakMap();
174
+ function refsEqual(a, b) {
175
+ if (a.length !== b.length) return false;
176
+ for (let i = 0; i < a.length; i++) {
177
+ if (a[i].ctor !== b[i].ctor) return false;
178
+ if (a[i].typesRef !== b[i].typesRef) return false;
179
+ if (a[i].metaRef !== b[i].metaRef) return false;
180
+ }
181
+ return true;
182
+ }
183
+ function getConfigurationForCtor(ctor) {
184
+ if (!ctor) return {};
185
+ const chain = getCtorChain(ctor);
186
+ const refs = chain.map((c) => ({
187
+ ctor: c,
188
+ typesRef: getOwnStaticTypes(c),
189
+ metaRef: getOwnMetadata(c)
190
+ }));
191
+ const cached = configurationCache.get(ctor);
192
+ if (cached && refsEqual(cached.refs, refs)) {
193
+ return cached.merged;
194
+ }
195
+ const merged = {};
196
+ for (let i = 0; i < refs.length; i++) {
197
+ const { typesRef, metaRef } = refs[i];
198
+ mergeInto(merged, metaRef);
199
+ mergeInto(merged, typesRef);
200
+ }
201
+ configurationCache.set(ctor, { refs, merged });
202
+ return merged;
203
+ }
204
+ function getConfigurationValue(ctor, key) {
205
+ return getConfigurationForCtor(ctor)[key];
206
+ }
61
207
  function isNonPrimitive(val) {
62
208
  return val != null && (typeof val === "object" || typeof val === "function");
63
209
  }
@@ -74,10 +220,12 @@ function getPropertyType(key, obj) {
74
220
  if (plain) {
75
221
  return "observable";
76
222
  }
77
- const metadata = obj.constructor[Symbol.metadata];
78
- const config = metadata?.[key];
223
+ const config = getConfigurationValue(
224
+ obj.constructor,
225
+ key
226
+ );
79
227
  if (config) {
80
- switch (config.type) {
228
+ switch (getConfigType(config)) {
81
229
  case ObservableCfgTypes.computed:
82
230
  return "computed";
83
231
  case ModelCfgTypes.state:
@@ -532,9 +680,10 @@ function reaction(fn, callback) {
532
680
  if (Object.is(currentValue, nextValue)) {
533
681
  return;
534
682
  }
683
+ const previousValue = currentValue;
535
684
  currentValue = nextValue;
536
685
  untracked(() => {
537
- callback(nextValue);
686
+ callback(nextValue, previousValue);
538
687
  });
539
688
  });
540
689
  }
@@ -616,11 +765,12 @@ function signal(value) {
616
765
  }
617
766
  function computed(value, context) {
618
767
  if (context && typeof context === "object" && "kind" in context) {
619
- context.metadata[context.name] = { type: "computed" };
768
+ context.metadata[context.name] = computedType;
620
769
  return value;
621
770
  }
622
771
  return internalCreateComputed(value).c;
623
772
  }
773
+ computed.type = computedType.type;
624
774
  function source(obj) {
625
775
  return getSource(obj);
626
776
  }
@@ -1470,9 +1620,22 @@ function createFilterMethod(method) {
1470
1620
  function(callback, thisArg) {
1471
1621
  const adm = getAdministration(this);
1472
1622
  adm.reportObserved();
1473
- return adm.source[method]((_element, index) => {
1474
- return callback.call(thisArg, this[index], index, this);
1475
- });
1623
+ const observedInput = [];
1624
+ observedInput.length = adm.source.length;
1625
+ const keys = Object.keys(adm.source);
1626
+ for (let i = 0; i < keys.length; i++) {
1627
+ const key = keys[i];
1628
+ const idx = Number(key);
1629
+ if (!Number.isNaN(idx)) {
1630
+ observedInput[idx] = this[idx];
1631
+ }
1632
+ }
1633
+ return Array.prototype[method].call(
1634
+ observedInput,
1635
+ (_element, index) => {
1636
+ return callback.call(thisArg, this[index], index, this);
1637
+ }
1638
+ );
1476
1639
  }
1477
1640
  );
1478
1641
  }
@@ -1480,7 +1643,22 @@ function createReduceMethod(method) {
1480
1643
  return createMethod(method, function() {
1481
1644
  const adm = getAdministration(this);
1482
1645
  adm.reportObserved();
1483
- return adm.source[method].apply(adm.source, arguments);
1646
+ const observedInput = [];
1647
+ observedInput.length = adm.source.length;
1648
+ const keys = Object.keys(adm.source);
1649
+ for (let i = 0; i < keys.length; i++) {
1650
+ const key = keys[i];
1651
+ const idx = Number(key);
1652
+ if (!Number.isNaN(idx)) {
1653
+ observedInput[idx] = this[idx];
1654
+ }
1655
+ }
1656
+ const args = Array.from(arguments);
1657
+ const callback = args[0];
1658
+ args[0] = (acc, _element, index) => {
1659
+ return callback(acc, this[index], index, this);
1660
+ };
1661
+ return Array.prototype[method].apply(observedInput, args);
1484
1662
  });
1485
1663
  }
1486
1664
  class DateAdministration extends Administration {
@@ -1714,7 +1892,7 @@ function getDiff(o1, o2, getConfig) {
1714
1892
  for (let i = 0; i < keys.length; i++) {
1715
1893
  const key = keys[i];
1716
1894
  if (obj1[key] !== obj2[key]) {
1717
- if (config?.[key]?.type === CommonCfgTypes.child) {
1895
+ if (getConfigType(config?.[key]) === CommonCfgTypes.child) {
1718
1896
  const value = obj2[key];
1719
1897
  if (Array.isArray(value)) {
1720
1898
  diff[key] = value.map((model2, index) => {
@@ -1760,7 +1938,7 @@ function createCircularMountError(frame) {
1760
1938
  const chain = formatMountChain(frame);
1761
1939
  const models = frame.modelsKeys.length === 0 ? "no models provided" : `models: ${frame.modelsKeys.join(", ")}`;
1762
1940
  return new Error(
1763
- `r-state-tree: detected circular store/model creation while mounting ${chain} (using ${models}). Passing models into child stores during mount can create recursive wiring. Move child store/model creation into storeDidMount or break the cycle.`
1941
+ `r-state-tree: detected circular store/model creation while mounting ${chain} (using ${models}). Passing models into child stores during mount can create recursive wiring. Break the ownership cycle.`
1764
1942
  );
1765
1943
  }
1766
1944
  function updateProps(props, newProps) {
@@ -1819,11 +1997,11 @@ class StoreAdministration extends PreactObjectAdministration {
1819
1997
  PreactObjectAdministration.proxyTraps,
1820
1998
  {
1821
1999
  get(target, name) {
1822
- if (name === "key") {
1823
- return target.key;
2000
+ if (name === "key" || name === Symbol.dispose) {
2001
+ return Reflect.get(target, name);
1824
2002
  }
1825
2003
  const adm = getAdministration(target);
1826
- switch (adm.configuration[name]?.type) {
2004
+ switch (getConfigType(adm.configuration[name])) {
1827
2005
  case CommonCfgTypes.child:
1828
2006
  return adm.getStore(name);
1829
2007
  case StoreCfgTypes.model:
@@ -1840,7 +2018,7 @@ class StoreAdministration extends PreactObjectAdministration {
1840
2018
  if (name === "props") {
1841
2019
  throw new Error(`r-state-tree: ${name} is read-only`);
1842
2020
  }
1843
- if (adm.configuration[name]?.type === StoreCfgTypes.model) {
2021
+ if (getConfigType(adm.configuration[name]) === StoreCfgTypes.model) {
1844
2022
  if (value !== void 0) {
1845
2023
  throw new Error(`r-state-tree: model ${String(name)} is read-only`);
1846
2024
  }
@@ -1853,10 +2031,11 @@ class StoreAdministration extends PreactObjectAdministration {
1853
2031
  }
1854
2032
  );
1855
2033
  parent = null;
1856
- mounted = false;
2034
+ mountedSignal = createSignal(false);
2035
+ disposed = false;
1857
2036
  contextCache = /* @__PURE__ */ new Map();
1858
2037
  childStoreDataMap = /* @__PURE__ */ new Map();
1859
- reactionsUnsub = [];
2038
+ ownedDisposers = /* @__PURE__ */ new Set();
1860
2039
  configurationGetter;
1861
2040
  setConfiguration(configurationGetter) {
1862
2041
  this.configurationGetter = configurationGetter;
@@ -1930,7 +2109,7 @@ class StoreAdministration extends PreactObjectAdministration {
1930
2109
  if (newStores.size || removedStores.size || keyedIndexChanged) {
1931
2110
  batch(() => childStoreData.value.set(stores));
1932
2111
  }
1933
- removedStores.forEach((s) => getStoreAdm(s).unmount());
2112
+ removedStores.forEach((s) => getStoreAdm(s).dispose(true));
1934
2113
  newStores.forEach((s) => getStoreAdm(s).mount(this, name));
1935
2114
  return stores;
1936
2115
  }
@@ -1941,12 +2120,12 @@ class StoreAdministration extends PreactObjectAdministration {
1941
2120
  );
1942
2121
  const { key, Type, props } = element || {};
1943
2122
  if (!element) {
1944
- oldStore && getStoreAdm(oldStore).unmount();
2123
+ oldStore && getStoreAdm(oldStore).dispose(true);
1945
2124
  batch(() => childStoreData.value.set(null));
1946
2125
  return null;
1947
2126
  } else if (!oldStore || oldStore.props.key !== key || !(oldStore instanceof Type)) {
1948
2127
  if (oldStore) {
1949
- getStoreAdm(oldStore).unmount();
2128
+ getStoreAdm(oldStore).dispose(true);
1950
2129
  }
1951
2130
  const childStore = this.createChildStore(element);
1952
2131
  batch(() => childStoreData.value.set(childStore));
@@ -1990,7 +2169,8 @@ class StoreAdministration extends PreactObjectAdministration {
1990
2169
  getStore(name) {
1991
2170
  const childStoreData = this.childStoreDataMap.get(name);
1992
2171
  if (!childStoreData) {
1993
- return this.initializeStore(name);
2172
+ untracked(() => this.initializeStore(name));
2173
+ return this.childStoreDataMap.get(name).value.get();
1994
2174
  } else {
1995
2175
  const storeElement = untracked(() => childStoreData.computed.get());
1996
2176
  validateStoreChildValue(storeElement, name);
@@ -2006,6 +2186,9 @@ class StoreAdministration extends PreactObjectAdministration {
2006
2186
  isRoot() {
2007
2187
  return !this.parent;
2008
2188
  }
2189
+ get isMounted() {
2190
+ return this.mountedSignal.get();
2191
+ }
2009
2192
  getContextValue(contextId, provideSymbol, defaultValue, hasDefault) {
2010
2193
  let computed2 = this.contextCache.get(contextId);
2011
2194
  if (!computed2) {
@@ -2039,12 +2222,37 @@ class StoreAdministration extends PreactObjectAdministration {
2039
2222
  }
2040
2223
  return void 0;
2041
2224
  }
2225
+ own(disposer) {
2226
+ if (this.disposed) {
2227
+ disposer();
2228
+ throw new Error(
2229
+ "r-state-tree: cannot register reactive resources on a disposed store"
2230
+ );
2231
+ }
2232
+ let active = true;
2233
+ const wrapped = () => {
2234
+ if (!active) return;
2235
+ active = false;
2236
+ this.ownedDisposers.delete(wrapped);
2237
+ disposer();
2238
+ };
2239
+ this.ownedDisposers.add(wrapped);
2240
+ return wrapped;
2241
+ }
2042
2242
  reaction(track, callback) {
2043
2243
  const unsub = reaction(track, callback);
2044
- this.reactionsUnsub.push(unsub);
2045
- return unsub;
2244
+ return this.own(unsub);
2245
+ }
2246
+ effect(callback) {
2247
+ return this.own(effect(callback));
2046
2248
  }
2047
2249
  mount(parent = null, childName) {
2250
+ if (this.disposed) {
2251
+ throw new Error("r-state-tree: cannot mount a disposed store");
2252
+ }
2253
+ if (this.isMounted) {
2254
+ throw new Error("r-state-tree: store is already mounted");
2255
+ }
2048
2256
  const frame = {
2049
2257
  storeName: this.proxy.constructor.name || "Store",
2050
2258
  childName,
@@ -2055,19 +2263,20 @@ class StoreAdministration extends PreactObjectAdministration {
2055
2263
  }
2056
2264
  mountingStack.push(frame);
2057
2265
  try {
2058
- this.parent = parent || null;
2059
- this.childStoreDataMap.forEach(({ value }, name) => {
2060
- const stores = value.get();
2061
- if (Array.isArray(stores)) {
2062
- stores?.forEach((s) => getStoreAdm(s)?.mount(this, name));
2063
- } else if (stores) {
2064
- getStoreAdm(stores)?.mount(this, name);
2065
- }
2266
+ batch(() => {
2267
+ this.parent = parent || null;
2268
+ this.childStoreDataMap.forEach(({ value }, name) => {
2269
+ const stores = value.get();
2270
+ if (Array.isArray(stores)) {
2271
+ stores?.forEach((s) => getStoreAdm(s)?.mount(this, name));
2272
+ } else if (stores) {
2273
+ getStoreAdm(stores)?.mount(this, name);
2274
+ }
2275
+ });
2276
+ this.mountedSignal.set(true);
2066
2277
  });
2067
- this.mounted = true;
2068
- batch(() => this.proxy.storeDidMount?.());
2069
2278
  } catch (error) {
2070
- if (error instanceof RangeError && /call stack/i.test(error.message)) {
2279
+ if (error instanceof Error && (error instanceof RangeError && /call stack/i.test(error.message) || /cycle detected/i.test(error.message))) {
2071
2280
  throw createCircularMountError(frame);
2072
2281
  }
2073
2282
  throw error;
@@ -2075,25 +2284,31 @@ class StoreAdministration extends PreactObjectAdministration {
2075
2284
  mountingStack.pop();
2076
2285
  }
2077
2286
  }
2078
- unmount() {
2079
- this.proxy.storeWillUnmount?.();
2080
- this.mounted = false;
2081
- this.childStoreDataMap.forEach((data) => {
2082
- const { value, computed: computed2, listener } = data;
2083
- const stores = value.get();
2084
- if (Array.isArray(stores)) {
2085
- stores?.forEach((s) => getStoreAdm(s)?.unmount());
2086
- } else if (stores) {
2087
- getStoreAdm(stores)?.unmount();
2088
- }
2089
- computed2.clear();
2090
- listener.dispose();
2287
+ dispose(internal = false) {
2288
+ if (this.disposed) return;
2289
+ if (!internal && !this.isRoot()) {
2290
+ throw new Error("r-state-tree: can only dispose root stores");
2291
+ }
2292
+ batch(() => {
2293
+ this.childStoreDataMap.forEach((data) => {
2294
+ const { value, computed: computed2, listener } = data;
2295
+ const stores = value.get();
2296
+ if (Array.isArray(stores)) {
2297
+ stores?.forEach((s) => getStoreAdm(s)?.dispose(true));
2298
+ } else if (stores) {
2299
+ getStoreAdm(stores)?.dispose(true);
2300
+ }
2301
+ computed2.clear();
2302
+ listener.dispose();
2303
+ });
2304
+ this.childStoreDataMap.clear();
2305
+ this.contextCache.forEach((computed2) => computed2.clear());
2306
+ this.contextCache.clear();
2307
+ this.parent = null;
2091
2308
  });
2092
- this.childStoreDataMap.clear();
2093
- this.contextCache.forEach((computed2) => computed2.clear());
2094
- this.contextCache.clear();
2095
- this.reactionsUnsub.forEach((u) => u());
2096
- this.parent = null;
2309
+ this.mountedSignal.set(false);
2310
+ this.disposed = true;
2311
+ Array.from(this.ownedDisposers).forEach((dispose) => dispose());
2097
2312
  }
2098
2313
  }
2099
2314
  let initEnabled$1 = false;
@@ -2117,9 +2332,6 @@ function updateStore(store, props) {
2117
2332
  return store;
2118
2333
  }
2119
2334
  class Store {
2120
- static get types() {
2121
- return this[Symbol.metadata];
2122
- }
2123
2335
  props;
2124
2336
  constructor(props) {
2125
2337
  if (!initEnabled$1) {
@@ -2131,7 +2343,9 @@ class Store {
2131
2343
  );
2132
2344
  const adm = getStoreAdm(observable2);
2133
2345
  adm.setConfiguration(
2134
- () => this.constructor.types ?? {}
2346
+ () => getConfigurationForCtor(
2347
+ this.constructor
2348
+ ) ?? {}
2135
2349
  );
2136
2350
  adm.write("props", getObservable({}));
2137
2351
  updateProps(observable2.props, props);
@@ -2140,141 +2354,109 @@ class Store {
2140
2354
  get key() {
2141
2355
  return this.props.key;
2142
2356
  }
2357
+ get isMounted() {
2358
+ return getStoreAdm(this).isMounted;
2359
+ }
2143
2360
  reaction(track, callback) {
2144
2361
  return getStoreAdm(this).reaction(track, callback);
2145
2362
  }
2146
- // eslint-disable-next-line @typescript-eslint/no-empty-function
2147
- storeDidMount() {
2363
+ effect(callback) {
2364
+ return getStoreAdm(this).effect(callback);
2148
2365
  }
2149
- // eslint-disable-next-line @typescript-eslint/no-empty-function
2150
- storeWillUnmount() {
2366
+ [Symbol.dispose]() {
2367
+ getStoreAdm(this).dispose();
2151
2368
  }
2152
2369
  }
2153
2370
  const attachedIdMap = /* @__PURE__ */ new WeakMap();
2154
2371
  const idMap = /* @__PURE__ */ new WeakMap();
2155
- let loadingSnapshot = false;
2156
- const potentialDups = /* @__PURE__ */ new Set();
2157
- function onSnapshotLoad(fn) {
2158
- const wasLoadingSnapshot = loadingSnapshot;
2159
- loadingSnapshot = true;
2160
- try {
2161
- return fn();
2162
- } finally {
2163
- if (!wasLoadingSnapshot) {
2164
- loadingSnapshot = false;
2165
- const rootMap = /* @__PURE__ */ new Map();
2166
- try {
2167
- potentialDups.forEach((model2) => {
2168
- const root = getModelAdm(model2).root.proxy;
2169
- let set = rootMap.get(root);
2170
- if (!set) {
2171
- set = /* @__PURE__ */ new Set();
2172
- rootMap.set(root, set);
2173
- }
2174
- if (idMap.has(model2)) {
2175
- const id2 = idMap.get(model2);
2176
- if (set.has(id2)) {
2177
- throw new Error(
2178
- "r-state-tree duplicate ids detected after snapshot was loaded"
2179
- );
2180
- }
2181
- set.add(id2);
2182
- }
2183
- });
2184
- } finally {
2185
- potentialDups.clear();
2186
- }
2372
+ function getModelType(model2) {
2373
+ return Object.getPrototypeOf(model2).constructor;
2374
+ }
2375
+ function entriesFor(model2) {
2376
+ const entries = [];
2377
+ attachedIdMap.get(model2)?.forEach((ids, Type) => {
2378
+ ids.forEach((value, id22) => {
2379
+ if (value) entries.push([Type, id22, value]);
2380
+ });
2381
+ });
2382
+ const id2 = idMap.get(model2);
2383
+ if (id2 != null) entries.push([getModelType(model2), id2, model2]);
2384
+ return entries;
2385
+ }
2386
+ function assertAvailable(node, entries) {
2387
+ const map = attachedIdMap.get(node);
2388
+ for (const [Type, id2, model2] of entries) {
2389
+ const existing = map?.get(Type)?.get(id2);
2390
+ if (existing && existing !== model2) {
2391
+ throw new Error(
2392
+ `r-state-tree: id: ${id2} is already assigned to another model`
2393
+ );
2187
2394
  }
2188
2395
  }
2189
2396
  }
2397
+ function bucket(node, Type) {
2398
+ let map = attachedIdMap.get(node);
2399
+ if (!map) {
2400
+ map = observable(/* @__PURE__ */ new Map());
2401
+ attachedIdMap.set(node, map);
2402
+ }
2403
+ let ids = map.get(Type);
2404
+ if (!ids) {
2405
+ ids = observable(/* @__PURE__ */ new Map());
2406
+ map.set(Type, ids);
2407
+ }
2408
+ return ids;
2409
+ }
2190
2410
  function setIdentifier(model2, id2) {
2191
- const prevId = idMap.get(model2);
2192
- idMap.set(model2, id2);
2193
- if (prevId == null) {
2194
- if (model2.parent) {
2195
- onModelAttached(model2);
2196
- }
2197
- } else if (prevId !== id2) {
2198
- updateIdentifier(model2);
2411
+ const previousId = idMap.get(model2);
2412
+ if (previousId === id2) return;
2413
+ const Type = getModelType(model2);
2414
+ const ancestors = [];
2415
+ let node = model2.parent;
2416
+ while (node) {
2417
+ assertAvailable(node, [[Type, id2, model2]]);
2418
+ ancestors.push(node);
2419
+ node = node.parent;
2420
+ }
2421
+ for (const ancestor of ancestors) {
2422
+ const ids = bucket(ancestor, Type);
2423
+ if (previousId != null && ids.get(previousId) === model2)
2424
+ ids.delete(previousId);
2425
+ ids.set(id2, model2);
2199
2426
  }
2427
+ idMap.set(model2, id2);
2200
2428
  }
2201
2429
  function getIdentifier(model2) {
2202
2430
  return idMap.get(model2);
2203
2431
  }
2204
- function getModelById(root, id2) {
2205
- const map = attachedIdMap.get(root);
2206
- return map?.get(id2);
2432
+ function getModelById(root, Type, id2) {
2433
+ return attachedIdMap.get(root)?.get(Type)?.get(id2);
2207
2434
  }
2208
- function updateIdentifier(model2) {
2209
- const id2 = idMap.get(model2);
2435
+ function onModelAttached(model2) {
2436
+ const entries = entriesFor(model2);
2437
+ if (!entries.length) return;
2438
+ const ancestors = [];
2210
2439
  let node = model2.parent;
2211
2440
  while (node) {
2212
- const map = attachedIdMap.get(node);
2213
- if (map) {
2214
- map.set(id2, model2);
2215
- }
2441
+ assertAvailable(node, entries);
2442
+ ancestors.push(node);
2216
2443
  node = node.parent;
2217
2444
  }
2218
- }
2219
- function onModelAttached(model2) {
2220
- const attachedMap = attachedIdMap.get(model2);
2221
- const id2 = idMap.get(model2);
2222
- if (attachedMap || id2 != null) {
2223
- const id22 = idMap.get(model2);
2224
- let node = model2.parent;
2225
- while (node) {
2226
- let map = attachedIdMap.get(node);
2227
- if (!map) {
2228
- map = observable(/* @__PURE__ */ new Map());
2229
- attachedIdMap.set(node, map);
2230
- }
2231
- attachedMap?.forEach((value, key) => {
2232
- if (map.has(key)) {
2233
- if (loadingSnapshot) {
2234
- potentialDups.add(model2);
2235
- potentialDups.add(map.get(key));
2236
- } else {
2237
- throw new Error(
2238
- `r-state-tree: id: ${key} is already assigned to another model`
2239
- );
2240
- }
2241
- }
2242
- map.set(key, value);
2243
- });
2244
- if (id22 != null) {
2245
- if (map.has(id22)) {
2246
- if (loadingSnapshot) {
2247
- potentialDups.add(model2);
2248
- potentialDups.add(map.get(id22));
2249
- } else {
2250
- throw new Error(
2251
- `r-state-tree: id: ${id22} is already assigned to another model`
2252
- );
2253
- }
2254
- }
2255
- map.set(id22, model2);
2256
- }
2257
- node = node.parent;
2258
- }
2445
+ for (const ancestor of ancestors) {
2446
+ for (const [Type, id2, value] of entries)
2447
+ bucket(ancestor, Type).set(id2, value);
2259
2448
  }
2260
2449
  }
2261
2450
  function onModelDetached(model2) {
2262
- const id2 = idMap.get(model2);
2263
- if (attachedIdMap.has(model2) || id2 != null) {
2264
- const attachedMap = attachedIdMap.get(model2);
2265
- let node = model2.parent;
2266
- while (node) {
2267
- const map = attachedIdMap.get(node);
2268
- if (map) {
2269
- attachedMap?.forEach((value, key) => {
2270
- map.delete(key);
2271
- });
2272
- if (id2 != null) {
2273
- map.delete(id2);
2274
- }
2275
- }
2276
- node = node.parent;
2451
+ const entries = entriesFor(model2);
2452
+ let node = model2.parent;
2453
+ while (node) {
2454
+ const map = attachedIdMap.get(node);
2455
+ for (const [Type, id2, value] of entries) {
2456
+ const ids = map?.get(Type);
2457
+ if (ids?.get(id2) === value) ids.delete(id2);
2277
2458
  }
2459
+ node = node.parent;
2278
2460
  }
2279
2461
  }
2280
2462
  const listenerMap = /* @__PURE__ */ new WeakMap();
@@ -2330,19 +2512,28 @@ class ObservableListener {
2330
2512
  notify(ev) {
2331
2513
  if (!this.listeners) return;
2332
2514
  this.notifying = true;
2333
- for (let i = 0; i < this.listeners.length; i++) {
2334
- this.listeners[i](ev);
2515
+ try {
2516
+ for (let i = 0; i < this.listeners.length; i++) {
2517
+ this.listeners[i](ev);
2518
+ }
2519
+ } finally {
2520
+ this.notifying = false;
2335
2521
  }
2336
- this.notifying = false;
2337
2522
  }
2338
2523
  }
2339
2524
  class ChildModelsAdministration extends ArrayAdministration {
2340
2525
  set(index, newValue) {
2341
2526
  return batch(() => {
2527
+ const oldValue = this.source[index];
2342
2528
  const result = super.set(index, newValue);
2343
2529
  const sourceValue = getSource(newValue);
2344
- if (this.source[index] !== sourceValue) {
2345
- notifyArrayUpdate(this.proxy, index, this.source[index], sourceValue);
2530
+ if (oldValue !== sourceValue) {
2531
+ try {
2532
+ notifyArrayUpdate(this.proxy, index, oldValue, sourceValue);
2533
+ } catch (error) {
2534
+ super.set(index, oldValue);
2535
+ throw error;
2536
+ }
2346
2537
  }
2347
2538
  return result;
2348
2539
  });
@@ -2351,7 +2542,12 @@ class ChildModelsAdministration extends ArrayAdministration {
2351
2542
  return batch(() => {
2352
2543
  const deleted = super.spliceWithArray(index, deleteCount, newItems);
2353
2544
  if (deleteCount || newItems?.length) {
2354
- notifySpliceArray(this.proxy, index, newItems ?? [], deleted);
2545
+ try {
2546
+ notifySpliceArray(this.proxy, index, newItems ?? [], deleted);
2547
+ } catch (error) {
2548
+ super.spliceWithArray(index, newItems?.length ?? 0, deleted);
2549
+ throw error;
2550
+ }
2355
2551
  }
2356
2552
  return deleted;
2357
2553
  });
@@ -2367,8 +2563,9 @@ function getModelAdm(model2) {
2367
2563
  }
2368
2564
  function getIdKey(Ctor) {
2369
2565
  if (!ctorIdKeyMap.has(Ctor)) {
2370
- const key = Object.keys(Ctor.types).find(
2371
- (prop) => Ctor.types[prop].type === ModelCfgTypes.id
2566
+ const config = getConfigurationForCtor(Ctor);
2567
+ const key = Object.keys(config).find(
2568
+ (prop) => config[prop]?.type === ModelCfgTypes.id
2372
2569
  ) ?? null;
2373
2570
  ctorIdKeyMap.set(Ctor, key);
2374
2571
  }
@@ -2417,16 +2614,25 @@ function validateModelChildValue(value, propertyName) {
2417
2614
  );
2418
2615
  }
2419
2616
  class ModelAdministration extends PreactObjectAdministration {
2617
+ getCfgType(name) {
2618
+ return getConfigType(this.configuration[name]);
2619
+ }
2620
+ getCfgChildType(name) {
2621
+ return getConfigChildType(this.configuration[name]);
2622
+ }
2420
2623
  static proxyTraps = Object.assign(
2421
2624
  {},
2422
2625
  PreactObjectAdministration.proxyTraps,
2423
2626
  {
2424
2627
  get(target, prop, proxy) {
2425
2628
  const adm = getAdministration(target);
2629
+ if (prop === Symbol.dispose) {
2630
+ return Reflect.get(target, prop, proxy);
2631
+ }
2426
2632
  if (prop === "parent") {
2427
2633
  return target.parent;
2428
2634
  }
2429
- switch (adm.configuration[prop]?.type) {
2635
+ switch (adm.getCfgType(prop)) {
2430
2636
  case ModelCfgTypes.modelRef:
2431
2637
  if (Array.isArray(adm.source[prop])) {
2432
2638
  return adm.getModelRefs(prop);
@@ -2441,9 +2647,10 @@ class ModelAdministration extends PreactObjectAdministration {
2441
2647
  },
2442
2648
  set(target, name, value) {
2443
2649
  const adm = getAdministration(target);
2650
+ adm.assertUsable();
2444
2651
  adm.writeInProgress.add(name);
2445
2652
  try {
2446
- switch (adm.configuration[name]?.type) {
2653
+ switch (adm.getCfgType(name)) {
2447
2654
  case ModelCfgTypes.modelRef: {
2448
2655
  Array.isArray(value) ? adm.setModelRefs(name, value) : adm.setModelRef(name, value);
2449
2656
  return true;
@@ -2478,7 +2685,7 @@ class ModelAdministration extends PreactObjectAdministration {
2478
2685
  defineProperty(target, name, desc) {
2479
2686
  const adm = getAdministration(target);
2480
2687
  if (desc && "value" in desc && !adm.writeInProgress.has(name)) {
2481
- switch (adm.configuration[name]?.type) {
2688
+ switch (adm.getCfgType(name)) {
2482
2689
  case ModelCfgTypes.modelRef:
2483
2690
  case CommonCfgTypes.child:
2484
2691
  case ModelCfgTypes.id: {
@@ -2503,6 +2710,8 @@ class ModelAdministration extends PreactObjectAdministration {
2503
2710
  computedSnapshot;
2504
2711
  snapshotMap = /* @__PURE__ */ new Map();
2505
2712
  contextCache = /* @__PURE__ */ new Map();
2713
+ ownedDisposers = /* @__PURE__ */ new Set();
2714
+ disposed = false;
2506
2715
  parentName = null;
2507
2716
  get parent() {
2508
2717
  this.parentAtom.reportObserved();
@@ -2517,6 +2726,29 @@ class ModelAdministration extends PreactObjectAdministration {
2517
2726
  setConfiguration(configurationGetter) {
2518
2727
  this.configurationGetter = configurationGetter;
2519
2728
  }
2729
+ assertUsable() {
2730
+ if (this.disposed) {
2731
+ throw new Error("r-state-tree: cannot use a disposed model");
2732
+ }
2733
+ }
2734
+ own(disposer) {
2735
+ this.assertUsable();
2736
+ let active = true;
2737
+ const wrapped = () => {
2738
+ if (!active) return;
2739
+ active = false;
2740
+ this.ownedDisposers.delete(wrapped);
2741
+ disposer();
2742
+ };
2743
+ this.ownedDisposers.add(wrapped);
2744
+ return wrapped;
2745
+ }
2746
+ reaction(track, callback) {
2747
+ return this.own(reaction(track, callback));
2748
+ }
2749
+ effect(callback) {
2750
+ return this.own(effect(callback));
2751
+ }
2520
2752
  get configuration() {
2521
2753
  return this.configurationGetter?.() ?? {};
2522
2754
  }
@@ -2542,6 +2774,39 @@ class ModelAdministration extends PreactObjectAdministration {
2542
2774
  });
2543
2775
  }
2544
2776
  }
2777
+ hydrateStateValue(currentValue, snapshotValue) {
2778
+ if (currentValue instanceof Signal) {
2779
+ currentValue.value = this.hydrateStateValue(
2780
+ currentValue.value,
2781
+ snapshotValue
2782
+ );
2783
+ return currentValue;
2784
+ }
2785
+ if (Array.isArray(currentValue) && Array.isArray(snapshotValue)) {
2786
+ const nextItems = snapshotValue.map(
2787
+ (item, index) => this.hydrateStateValue(currentValue[index], item)
2788
+ );
2789
+ currentValue.splice(0, currentValue.length, ...nextItems);
2790
+ return currentValue;
2791
+ }
2792
+ if (isPlainObject(currentValue) && isPlainObject(snapshotValue)) {
2793
+ const currentRecord = currentValue;
2794
+ const snapshotRecord = snapshotValue;
2795
+ Object.keys(snapshotRecord).forEach((key) => {
2796
+ currentRecord[key] = this.hydrateStateValue(
2797
+ currentRecord[key],
2798
+ snapshotRecord[key]
2799
+ );
2800
+ });
2801
+ Object.keys(currentRecord).forEach((key) => {
2802
+ if (!(key in snapshotRecord)) {
2803
+ delete currentRecord[key];
2804
+ }
2805
+ });
2806
+ return currentValue;
2807
+ }
2808
+ return snapshotValue;
2809
+ }
2545
2810
  setId(name, v) {
2546
2811
  const id2 = getIdentifier(this.proxy);
2547
2812
  if (id2 === v) {
@@ -2553,8 +2818,8 @@ class ModelAdministration extends PreactObjectAdministration {
2553
2818
  );
2554
2819
  }
2555
2820
  if (v !== void 0) {
2556
- this.source[name] = v;
2557
2821
  setIdentifier(this.proxy, v);
2822
+ this.source[name] = v;
2558
2823
  }
2559
2824
  }
2560
2825
  setModel(name, newModel) {
@@ -2609,11 +2874,30 @@ class ModelAdministration extends PreactObjectAdministration {
2609
2874
  name,
2610
2875
  observe(this.proxy[name], (event) => {
2611
2876
  if (event.type === "updateArray") {
2612
- getModelAdm(event.oldValue).detach();
2613
- getModelAdm(event.newValue).attach(this, name);
2877
+ const oldAdm = getModelAdm(event.oldValue);
2878
+ const newAdm = getModelAdm(event.newValue);
2879
+ oldAdm.detach();
2880
+ try {
2881
+ newAdm.attach(this, name);
2882
+ } catch (error) {
2883
+ oldAdm.attach(this, name);
2884
+ throw error;
2885
+ }
2614
2886
  } else if (event.type === "spliceArray") {
2887
+ const attached = [];
2615
2888
  event.removed.forEach((model2) => getModelAdm(model2).detach());
2616
- event.added.forEach((model2) => getModelAdm(model2).attach(this, name));
2889
+ try {
2890
+ event.added.forEach((model2) => {
2891
+ getModelAdm(model2).attach(this, name);
2892
+ attached.push(model2);
2893
+ });
2894
+ } catch (error) {
2895
+ attached.forEach((model2) => getModelAdm(model2).detach());
2896
+ event.removed.forEach(
2897
+ (model2) => getModelAdm(model2).attach(this, name)
2898
+ );
2899
+ throw error;
2900
+ }
2617
2901
  }
2618
2902
  })
2619
2903
  );
@@ -2628,7 +2912,9 @@ class ModelAdministration extends PreactObjectAdministration {
2628
2912
  getModelRef(name) {
2629
2913
  const a = this.getReferencedAtom(name);
2630
2914
  a.reportObserved();
2631
- return this.source[name] != null ? getModelById(this.root.proxy, this.source[name]) : void 0;
2915
+ const Type = this.getRequiredModelRefType(name);
2916
+ const root = this.getReactiveRoot().proxy;
2917
+ return this.source[name] != null ? getModelById(root, Type, this.source[name]) : void 0;
2632
2918
  }
2633
2919
  getModelRefs(name) {
2634
2920
  const a = this.getReferencedAtom(name);
@@ -2637,7 +2923,9 @@ class ModelAdministration extends PreactObjectAdministration {
2637
2923
  if (!this.referencedModels) this.referencedModels = /* @__PURE__ */ new Map();
2638
2924
  c = createComputed(() => {
2639
2925
  a.reportObserved();
2640
- const models = (this.source[name] || []).map((id2) => getModelById(this.root.proxy, id2)).filter((m) => !!m);
2926
+ const Type = this.getRequiredModelRefType(name);
2927
+ const root = this.getReactiveRoot().proxy;
2928
+ const models = (this.source[name] || []).map((id2) => getModelById(root, Type, id2)).filter((m) => !!m);
2641
2929
  return models;
2642
2930
  });
2643
2931
  this.referencedModels.set(name, c);
@@ -2647,6 +2935,7 @@ class ModelAdministration extends PreactObjectAdministration {
2647
2935
  setModelRef(name, modelValue) {
2648
2936
  let id2 = void 0;
2649
2937
  if (modelValue) {
2938
+ this.assertModelRefType(name, modelValue);
2650
2939
  id2 = getIdentifier(modelValue);
2651
2940
  if (id2 == null) {
2652
2941
  throw new Error(
@@ -2659,6 +2948,7 @@ class ModelAdministration extends PreactObjectAdministration {
2659
2948
  }
2660
2949
  setModelRefs(name, modelValue) {
2661
2950
  const ids = modelValue.map((model2) => {
2951
+ this.assertModelRefType(name, model2);
2662
2952
  const id2 = getIdentifier(model2);
2663
2953
  if (id2 == null) {
2664
2954
  throw new Error(
@@ -2670,31 +2960,87 @@ class ModelAdministration extends PreactObjectAdministration {
2670
2960
  this.source[name] = ids;
2671
2961
  this.referencedAtoms?.get(name)?.reportChanged();
2672
2962
  }
2963
+ getRequiredModelRefType(name) {
2964
+ const Type = this.getCfgChildType(name);
2965
+ if (!Type) {
2966
+ throw new Error(
2967
+ `r-state-tree: modelRef '${String(name)}' requires a model constructor`
2968
+ );
2969
+ }
2970
+ return Type;
2971
+ }
2972
+ assertModelRefType(name, model2) {
2973
+ const Type = this.getRequiredModelRefType(name);
2974
+ if (!(model2 instanceof Type)) {
2975
+ throw new Error(
2976
+ `r-state-tree: modelRef '${String(name)}' must reference ${Type.name}`
2977
+ );
2978
+ }
2979
+ }
2673
2980
  attach(parent = null, parentName = null) {
2981
+ this.assertUsable();
2674
2982
  if (this.parent) {
2675
2983
  throw new Error(
2676
2984
  "r-state-tree: child model already attached to a parent. Did you mean to use modelRef?"
2677
2985
  );
2678
2986
  }
2679
- if (parent) {
2680
- this.parent = parent;
2681
- this.root = parent.root;
2682
- this.parentName = parentName;
2683
- }
2684
2987
  batch(() => {
2685
- onModelAttached(this.proxy);
2686
- this.proxy.modelDidAttach();
2988
+ if (parent) {
2989
+ this.parent = parent;
2990
+ this.root = parent.root;
2991
+ this.parentName = parentName;
2992
+ }
2993
+ try {
2994
+ onModelAttached(this.proxy);
2995
+ } catch (error) {
2996
+ this.parent = null;
2997
+ this.root = this;
2998
+ this.parentName = null;
2999
+ throw error;
3000
+ }
2687
3001
  });
2688
3002
  }
3003
+ getReactiveRoot() {
3004
+ const parent = this.parent;
3005
+ return parent ? parent.getReactiveRoot() : this;
3006
+ }
2689
3007
  detach() {
2690
3008
  batch(() => {
2691
- this.proxy.modelWillDetach();
2692
3009
  onModelDetached(this.proxy);
3010
+ this.contextCache.forEach((computed2) => computed2.clear());
3011
+ this.contextCache.clear();
3012
+ this.parent = null;
3013
+ this.parentName = null;
3014
+ this.root = this;
2693
3015
  });
3016
+ }
3017
+ dispose(internal = false) {
3018
+ if (this.disposed) return;
3019
+ if (!internal && this.parent) {
3020
+ throw new Error(
3021
+ "r-state-tree: cannot directly dispose an attached child model"
3022
+ );
3023
+ }
3024
+ batch(() => {
3025
+ this.activeModels.forEach((name) => {
3026
+ const child2 = this.proxy[name];
3027
+ if (Array.isArray(child2)) {
3028
+ child2.forEach((model2) => getModelAdm(model2).dispose(true));
3029
+ } else if (child2) {
3030
+ getModelAdm(child2).dispose(true);
3031
+ }
3032
+ });
3033
+ if (this.parent) onModelDetached(this.proxy);
3034
+ this.parent = null;
3035
+ this.parentName = null;
3036
+ this.root = this;
3037
+ });
3038
+ this.disposed = true;
3039
+ this.modelsTraceUnsub.forEach((dispose) => dispose());
3040
+ this.modelsTraceUnsub.clear();
2694
3041
  this.contextCache.forEach((computed2) => computed2.clear());
2695
3042
  this.contextCache.clear();
2696
- this.parent = null;
2697
- this.root = this;
3043
+ Array.from(this.ownedDisposers).forEach((dispose) => dispose());
2698
3044
  }
2699
3045
  getContextValue(contextId, provideSymbol, defaultValue, hasDefault) {
2700
3046
  let computed2 = this.contextCache.get(contextId);
@@ -2732,7 +3078,7 @@ class ModelAdministration extends PreactObjectAdministration {
2732
3078
  }
2733
3079
  toJSON() {
2734
3080
  return Object.keys(this.configuration).reduce((json, key) => {
2735
- switch (this.configuration[key].type) {
3081
+ switch (getConfigType(this.configuration[key])) {
2736
3082
  case ModelCfgTypes.state: {
2737
3083
  json[key] = clone(this.proxy[key], key);
2738
3084
  break;
@@ -2789,88 +3135,103 @@ class ModelAdministration extends PreactObjectAdministration {
2789
3135
  }
2790
3136
  loadSnapshot(snapshot) {
2791
3137
  const ensureChildTypes = (key) => {
2792
- if (!this.configuration[key].childType) {
3138
+ const childType2 = this.getCfgChildType(key);
3139
+ if (!childType2) {
2793
3140
  throw new Error(
2794
3141
  "r-state-tree: child constructor must be specified to load snapshots with child/children. eg: `@child(ChildCtor) MyChild`"
2795
3142
  );
2796
3143
  }
2797
3144
  return true;
2798
3145
  };
2799
- onSnapshotLoad(() => {
2800
- Object.keys(snapshot).forEach((key) => {
2801
- const { type, childType: childType2 } = this.configuration[key] ?? {};
2802
- const value = snapshot[key];
2803
- switch (type) {
2804
- case ModelCfgTypes.state:
2805
- this.proxy[key] = value;
2806
- break;
2807
- case ModelCfgTypes.modelRef:
2808
- if (Array.isArray(value)) {
2809
- if (value?.[0] instanceof Model) {
3146
+ untracked(() => {
3147
+ batch(() => {
3148
+ Object.keys(snapshot).forEach((key) => {
3149
+ const type = this.getCfgType(key);
3150
+ const childType2 = this.getCfgChildType(key);
3151
+ const value = snapshot[key];
3152
+ switch (type) {
3153
+ case ModelCfgTypes.state:
3154
+ this.proxy[key] = this.hydrateStateValue(this.proxy[key], value);
3155
+ break;
3156
+ case ModelCfgTypes.modelRef:
3157
+ if (Array.isArray(value)) {
3158
+ if (value?.[0] instanceof Model) {
3159
+ this.proxy[key] = value;
3160
+ } else {
3161
+ this.source[key] = value.map(
3162
+ (snapshot2) => getSnapshotRefId(snapshot2)
3163
+ );
3164
+ this.referencedAtoms?.get(key)?.reportChanged();
3165
+ }
3166
+ break;
3167
+ } else if (value instanceof Model) {
2810
3168
  this.proxy[key] = value;
2811
3169
  } else {
2812
- this.source[key] = value.map(
2813
- (snapshot2) => getSnapshotRefId(snapshot2)
2814
- );
3170
+ this.source[key] = getSnapshotRefId(value);
2815
3171
  this.referencedAtoms?.get(key)?.reportChanged();
2816
3172
  }
2817
3173
  break;
2818
- } else if (value instanceof Model) {
2819
- this.proxy[key] = value;
2820
- } else {
2821
- this.source[key] = getSnapshotRefId(value);
2822
- this.referencedAtoms?.get(key)?.reportChanged();
2823
- }
2824
- break;
2825
- case ModelCfgTypes.id:
2826
- this.setId(key, value);
2827
- break;
2828
- case CommonCfgTypes.child:
2829
- let model2;
2830
- if (Array.isArray(value)) {
2831
- const Ctor = childType2;
2832
- this.proxy[key] = value?.map(
2833
- (snapshot2, index) => {
2834
- snapshot2 = snapshot2 ?? {};
2835
- let model22;
2836
- if (snapshot2 instanceof Model) {
2837
- model22 = snapshot2;
2838
- } else {
2839
- ensureChildTypes(key);
2840
- const id2 = childType2 && getSnapshotId(snapshot2, Ctor);
2841
- const foundModel = id2 != null ? getModelById(this.root.proxy, id2) : this.proxy[key][index];
2842
- const adm = foundModel && getModelAdm(foundModel);
2843
- if (adm && foundModel.parent === this.proxy && adm?.parentName === key) {
2844
- adm.loadSnapshot(snapshot2);
2845
- model22 = foundModel;
3174
+ case ModelCfgTypes.id:
3175
+ this.setId(key, value);
3176
+ break;
3177
+ case CommonCfgTypes.child:
3178
+ let model2;
3179
+ if (Array.isArray(value)) {
3180
+ const Ctor = childType2;
3181
+ const snapshotIds = /* @__PURE__ */ new Set();
3182
+ for (const childSnapshot of value) {
3183
+ if (childSnapshot instanceof Model) continue;
3184
+ const snapshotId = childType2 ? getSnapshotId(childSnapshot ?? {}, Ctor) : null;
3185
+ if (snapshotId != null && snapshotIds.has(snapshotId)) {
3186
+ throw new Error(
3187
+ "r-state-tree duplicate ids detected after snapshot was loaded"
3188
+ );
3189
+ }
3190
+ if (snapshotId != null) snapshotIds.add(snapshotId);
3191
+ }
3192
+ this.proxy[key] = value?.map(
3193
+ (snapshot2, index) => {
3194
+ snapshot2 = snapshot2 ?? {};
3195
+ let model22;
3196
+ if (snapshot2 instanceof Model) {
3197
+ model22 = snapshot2;
2846
3198
  } else {
2847
- model22 = Ctor.create(snapshot2);
3199
+ ensureChildTypes(key);
3200
+ const id2 = childType2 && getSnapshotId(snapshot2, Ctor);
3201
+ const foundModel = id2 != null ? getModelById(this.root.proxy, Ctor, id2) : this.proxy[key][index];
3202
+ const adm = foundModel && getModelAdm(foundModel);
3203
+ if (adm && foundModel.parent === this.proxy && adm?.parentName === key) {
3204
+ adm.loadSnapshot(snapshot2);
3205
+ model22 = foundModel;
3206
+ } else {
3207
+ model22 = Ctor.create(snapshot2);
3208
+ }
2848
3209
  }
3210
+ return model22;
2849
3211
  }
2850
- return model22;
2851
- }
2852
- );
2853
- break;
2854
- } else if (value instanceof Model) {
2855
- model2 = value;
2856
- } else {
2857
- ensureChildTypes(key);
2858
- const id2 = childType2 && getSnapshotId(value, childType2);
2859
- if (id2 != null && this.proxy[key] && id2 === getIdentifier(this.proxy[key])) {
2860
- const adm = getModelAdm(this.proxy[key]);
2861
- adm.loadSnapshot(value);
2862
- model2 = this.proxy[key];
3212
+ );
3213
+ break;
3214
+ } else if (value instanceof Model) {
3215
+ model2 = value;
2863
3216
  } else {
2864
- model2 = childType2.create(value);
3217
+ ensureChildTypes(key);
3218
+ const id2 = childType2 && getSnapshotId(value, childType2);
3219
+ if (id2 != null && this.proxy[key] && this.proxy[key] instanceof childType2 && id2 === getIdentifier(this.proxy[key])) {
3220
+ const adm = getModelAdm(this.proxy[key]);
3221
+ adm.loadSnapshot(value);
3222
+ model2 = this.proxy[key];
3223
+ } else {
3224
+ model2 = childType2.create(value);
3225
+ }
2865
3226
  }
2866
- }
2867
- this.proxy[key] = model2;
2868
- break;
2869
- default:
2870
- console.warn(
2871
- `r-state-tree: invalid key '${key}' found in snapshot, ignored.`
2872
- );
2873
- }
3227
+ this.proxy[key] = model2;
3228
+ break;
3229
+ default:
3230
+ console.warn(
3231
+ `r-state-tree: invalid key '${key}' found in snapshot, ignored.`
3232
+ );
3233
+ }
3234
+ });
2874
3235
  });
2875
3236
  });
2876
3237
  }
@@ -2884,14 +3245,14 @@ class ModelAdministration extends PreactObjectAdministration {
2884
3245
  }
2885
3246
  return this.computedSnapshot.get();
2886
3247
  }
3248
+ getSnapshotForRollback() {
3249
+ return this.toJSON();
3250
+ }
2887
3251
  }
2888
3252
  let initEnabled = false;
2889
3253
  class Model {
2890
- static get types() {
2891
- return this[Symbol.metadata];
2892
- }
2893
3254
  static childTypes = {};
2894
- static create(snapshot, ...args) {
3255
+ static create(snapshot) {
2895
3256
  let instance;
2896
3257
  try {
2897
3258
  initEnabled = true;
@@ -2900,8 +3261,12 @@ class Model {
2900
3261
  initEnabled = false;
2901
3262
  }
2902
3263
  const adm = getModelAdm(instance);
2903
- snapshot && adm.loadSnapshot(snapshot);
2904
- instance.modelDidInit(snapshot, ...args);
3264
+ try {
3265
+ snapshot && adm.loadSnapshot(snapshot);
3266
+ } catch (error) {
3267
+ adm.dispose(true);
3268
+ throw error;
3269
+ }
2905
3270
  return instance;
2906
3271
  }
2907
3272
  constructor() {
@@ -2916,38 +3281,42 @@ class Model {
2916
3281
  );
2917
3282
  const adm = getModelAdm(observable2);
2918
3283
  adm.setConfiguration(
2919
- () => this.constructor.types ?? {}
3284
+ () => getConfigurationForCtor(
3285
+ this.constructor
3286
+ ) ?? {}
2920
3287
  );
2921
3288
  return observable2;
2922
3289
  }
2923
3290
  get parent() {
2924
3291
  return getModelAdm(this).parent?.proxy ?? null;
2925
3292
  }
2926
- // eslint-disable-next-line @typescript-eslint/no-empty-function
2927
- modelDidInit(snapshot, ...args) {
3293
+ reaction(track, callback) {
3294
+ return getModelAdm(this).reaction(track, callback);
2928
3295
  }
2929
- // eslint-disable-next-line @typescript-eslint/no-empty-function
2930
- modelDidAttach() {
3296
+ effect(callback) {
3297
+ return getModelAdm(this).effect(callback);
2931
3298
  }
2932
- // eslint-disable-next-line @typescript-eslint/no-empty-function
2933
- modelWillDetach() {
3299
+ [Symbol.dispose]() {
3300
+ getModelAdm(this).dispose();
2934
3301
  }
2935
3302
  }
3303
+ function findModelById(root, ModelType, id2) {
3304
+ return getModelById(getModelAdm(root).root.proxy, ModelType, id2);
3305
+ }
2936
3306
  function mount(container) {
2937
3307
  return allowNewStore(() => {
2938
3308
  const element = container;
2939
3309
  const s = new element.Type(element.props);
2940
- getStoreAdm(s).mount();
3310
+ const adm = getStoreAdm(s);
3311
+ try {
3312
+ adm.mount();
3313
+ } catch (error) {
3314
+ adm.dispose(true);
3315
+ throw error;
3316
+ }
2941
3317
  return s;
2942
3318
  });
2943
3319
  }
2944
- function unmount(container) {
2945
- const internalStore = getStoreAdm(container);
2946
- if (!internalStore.isRoot()) {
2947
- throw new Error("r-state-tree: can only unmount root stores");
2948
- }
2949
- internalStore.unmount();
2950
- }
2951
3320
  function toSnapshot(model2) {
2952
3321
  return getModelAdm(model2).getSnapshot();
2953
3322
  }
@@ -2967,7 +3336,13 @@ function onSnapshotDiff(model2, callback) {
2967
3336
  }
2968
3337
  function applySnapshot(model2, snapshot) {
2969
3338
  const adm = getModelAdm(model2);
2970
- adm.loadSnapshot(snapshot);
3339
+ const previousSnapshot = adm.getSnapshotForRollback();
3340
+ try {
3341
+ adm.loadSnapshot(snapshot);
3342
+ } catch (error) {
3343
+ adm.loadSnapshot(previousSnapshot);
3344
+ throw error;
3345
+ }
2971
3346
  return model2;
2972
3347
  }
2973
3348
  const CONTEXT_SYMBOL = Symbol("context");
@@ -3062,29 +3437,6 @@ function toObservableTreeInternal(value, ancestorPath, path) {
3062
3437
  }
3063
3438
  return value;
3064
3439
  }
3065
- function makeDecorator(type) {
3066
- return function(value, context) {
3067
- context.metadata[context.name] = type;
3068
- return value;
3069
- };
3070
- }
3071
- function makeChildDecorator(typeObj) {
3072
- return function(valueOrChildType, context) {
3073
- if (context !== void 0) {
3074
- return makeDecorator(typeObj)(valueOrChildType, context);
3075
- }
3076
- const childCtor = valueOrChildType;
3077
- return function(value, context2) {
3078
- const typeWithCtor = typeObj(childCtor);
3079
- return makeDecorator(typeWithCtor)(value, context2);
3080
- };
3081
- };
3082
- }
3083
- const child = makeChildDecorator(childType);
3084
- const modelRef = makeChildDecorator(modelRefType);
3085
- const model = makeDecorator(modelType);
3086
- const id = makeDecorator(idType);
3087
- const state = makeDecorator(stateType);
3088
3440
  export {
3089
3441
  Model,
3090
3442
  Observable,
@@ -3097,6 +3449,7 @@ export {
3097
3449
  createContext,
3098
3450
  createStore,
3099
3451
  effect2 as effect,
3452
+ findModelById,
3100
3453
  getSignal,
3101
3454
  id,
3102
3455
  isObservable,
@@ -3114,7 +3467,6 @@ export {
3114
3467
  state,
3115
3468
  toObservableTree,
3116
3469
  toSnapshot,
3117
- unmount,
3118
3470
  untracked2 as untracked,
3119
3471
  updateStore
3120
3472
  };