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