mvc-kit 2.5.2 → 2.5.4

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.
Files changed (151) hide show
  1. package/README.md +4 -4
  2. package/agent-config/claude-code/skills/guide/api-reference.md +10 -10
  3. package/dist/Channel.cjs +291 -0
  4. package/dist/Channel.cjs.map +1 -0
  5. package/dist/Channel.d.ts +1 -1
  6. package/dist/Channel.d.ts.map +1 -1
  7. package/dist/Channel.js +291 -0
  8. package/dist/Channel.js.map +1 -0
  9. package/dist/Collection.cjs +452 -0
  10. package/dist/Collection.cjs.map +1 -0
  11. package/dist/Collection.d.ts +7 -7
  12. package/dist/Collection.d.ts.map +1 -1
  13. package/dist/Collection.js +452 -0
  14. package/dist/Collection.js.map +1 -0
  15. package/dist/Controller.cjs +57 -0
  16. package/dist/Controller.cjs.map +1 -0
  17. package/dist/Controller.js +57 -0
  18. package/dist/Controller.js.map +1 -0
  19. package/dist/EventBus.cjs +84 -0
  20. package/dist/EventBus.cjs.map +1 -0
  21. package/dist/EventBus.js +84 -0
  22. package/dist/EventBus.js.map +1 -0
  23. package/dist/Model.cjs +175 -0
  24. package/dist/Model.cjs.map +1 -0
  25. package/dist/Model.d.ts +4 -4
  26. package/dist/Model.d.ts.map +1 -1
  27. package/dist/Model.js +175 -0
  28. package/dist/Model.js.map +1 -0
  29. package/dist/PersistentCollection.cjs +285 -0
  30. package/dist/PersistentCollection.cjs.map +1 -0
  31. package/dist/PersistentCollection.d.ts +4 -4
  32. package/dist/PersistentCollection.d.ts.map +1 -1
  33. package/dist/PersistentCollection.js +285 -0
  34. package/dist/PersistentCollection.js.map +1 -0
  35. package/dist/Resource.cjs +308 -0
  36. package/dist/Resource.cjs.map +1 -0
  37. package/dist/Resource.d.ts +6 -6
  38. package/dist/Resource.d.ts.map +1 -1
  39. package/dist/Resource.js +308 -0
  40. package/dist/Resource.js.map +1 -0
  41. package/dist/Service.cjs +51 -0
  42. package/dist/Service.cjs.map +1 -0
  43. package/dist/Service.js +51 -0
  44. package/dist/Service.js.map +1 -0
  45. package/dist/ViewModel.cjs +582 -0
  46. package/dist/ViewModel.cjs.map +1 -0
  47. package/dist/ViewModel.d.ts +3 -9
  48. package/dist/ViewModel.d.ts.map +1 -1
  49. package/dist/ViewModel.js +582 -0
  50. package/dist/ViewModel.js.map +1 -0
  51. package/dist/errors.cjs +79 -0
  52. package/dist/errors.cjs.map +1 -0
  53. package/dist/errors.js +79 -0
  54. package/dist/errors.js.map +1 -0
  55. package/dist/mvc-kit.cjs +29 -1
  56. package/dist/mvc-kit.cjs.map +1 -1
  57. package/dist/mvc-kit.js +27 -1132
  58. package/dist/mvc-kit.js.map +1 -1
  59. package/dist/react/guards.cjs +7 -0
  60. package/dist/react/guards.cjs.map +1 -0
  61. package/dist/react/guards.js +7 -0
  62. package/dist/react/guards.js.map +1 -0
  63. package/dist/react/provider.cjs +26 -0
  64. package/dist/react/provider.cjs.map +1 -0
  65. package/dist/react/provider.js +26 -0
  66. package/dist/react/provider.js.map +1 -0
  67. package/dist/react/types.d.ts +1 -1
  68. package/dist/react/types.d.ts.map +1 -1
  69. package/dist/react/use-event-bus.cjs +26 -0
  70. package/dist/react/use-event-bus.cjs.map +1 -0
  71. package/dist/react/use-event-bus.js +26 -0
  72. package/dist/react/use-event-bus.js.map +1 -0
  73. package/dist/react/use-instance.cjs +31 -0
  74. package/dist/react/use-instance.cjs.map +1 -0
  75. package/dist/react/use-instance.d.ts +1 -1
  76. package/dist/react/use-instance.d.ts.map +1 -1
  77. package/dist/react/use-instance.js +31 -0
  78. package/dist/react/use-instance.js.map +1 -0
  79. package/dist/react/use-local.cjs +64 -0
  80. package/dist/react/use-local.cjs.map +1 -0
  81. package/dist/react/use-local.d.ts +4 -4
  82. package/dist/react/use-local.d.ts.map +1 -1
  83. package/dist/react/use-local.js +64 -0
  84. package/dist/react/use-local.js.map +1 -0
  85. package/dist/react/use-model.cjs +80 -0
  86. package/dist/react/use-model.cjs.map +1 -0
  87. package/dist/react/use-model.d.ts +1 -1
  88. package/dist/react/use-model.d.ts.map +1 -1
  89. package/dist/react/use-model.js +80 -0
  90. package/dist/react/use-model.js.map +1 -0
  91. package/dist/react/use-singleton.cjs +21 -0
  92. package/dist/react/use-singleton.cjs.map +1 -0
  93. package/dist/react/use-singleton.d.ts +1 -1
  94. package/dist/react/use-singleton.d.ts.map +1 -1
  95. package/dist/react/use-singleton.js +21 -0
  96. package/dist/react/use-singleton.js.map +1 -0
  97. package/dist/react/use-teardown.cjs +22 -0
  98. package/dist/react/use-teardown.cjs.map +1 -0
  99. package/dist/react/use-teardown.js +22 -0
  100. package/dist/react/use-teardown.js.map +1 -0
  101. package/dist/react-native/NativeCollection.cjs +76 -0
  102. package/dist/react-native/NativeCollection.cjs.map +1 -0
  103. package/dist/react-native/NativeCollection.js +76 -0
  104. package/dist/react-native/NativeCollection.js.map +1 -0
  105. package/dist/react-native.cjs +4 -1
  106. package/dist/react-native.cjs.map +1 -1
  107. package/dist/react-native.js +2 -60
  108. package/dist/react-native.js.map +1 -1
  109. package/dist/react.cjs +19 -1
  110. package/dist/react.cjs.map +1 -1
  111. package/dist/react.js +17 -145
  112. package/dist/react.js.map +1 -1
  113. package/dist/singleton.cjs +34 -0
  114. package/dist/singleton.cjs.map +1 -0
  115. package/dist/singleton.js +34 -0
  116. package/dist/singleton.js.map +1 -0
  117. package/dist/types.d.ts +3 -3
  118. package/dist/types.d.ts.map +1 -1
  119. package/dist/walkPrototypeChain.cjs +15 -0
  120. package/dist/walkPrototypeChain.cjs.map +1 -0
  121. package/dist/walkPrototypeChain.d.ts +9 -0
  122. package/dist/walkPrototypeChain.d.ts.map +1 -0
  123. package/dist/walkPrototypeChain.js +15 -0
  124. package/dist/walkPrototypeChain.js.map +1 -0
  125. package/dist/web/IndexedDBCollection.cjs +37 -0
  126. package/dist/web/IndexedDBCollection.cjs.map +1 -0
  127. package/dist/web/IndexedDBCollection.js +37 -0
  128. package/dist/web/IndexedDBCollection.js.map +1 -0
  129. package/dist/web/WebStorageCollection.cjs +85 -0
  130. package/dist/web/WebStorageCollection.cjs.map +1 -0
  131. package/dist/web/WebStorageCollection.d.ts +2 -2
  132. package/dist/web/WebStorageCollection.d.ts.map +1 -1
  133. package/dist/web/WebStorageCollection.js +85 -0
  134. package/dist/web/WebStorageCollection.js.map +1 -0
  135. package/dist/web/idb.cjs +121 -0
  136. package/dist/web/idb.cjs.map +1 -0
  137. package/dist/web/idb.js +121 -0
  138. package/dist/web/idb.js.map +1 -0
  139. package/dist/web.cjs +6 -1
  140. package/dist/web.cjs.map +1 -1
  141. package/dist/web.js +4 -178
  142. package/dist/web.js.map +1 -1
  143. package/package.json +4 -2
  144. package/dist/PersistentCollection-B8kNECDj.cjs +0 -2
  145. package/dist/PersistentCollection-B8kNECDj.cjs.map +0 -1
  146. package/dist/PersistentCollection-BFrgskju.js +0 -542
  147. package/dist/PersistentCollection-BFrgskju.js.map +0 -1
  148. package/dist/singleton-CaEXSbYg.js +0 -89
  149. package/dist/singleton-CaEXSbYg.js.map +0 -1
  150. package/dist/singleton-L-u2W_lX.cjs +0 -2
  151. package/dist/singleton-L-u2W_lX.cjs.map +0 -1
package/dist/mvc-kit.js CHANGED
@@ -1,1135 +1,30 @@
1
- import { E } from "./singleton-CaEXSbYg.js";
2
- import { h as B, s as F, t as X, a as Y } from "./singleton-CaEXSbYg.js";
3
- import { C as A } from "./PersistentCollection-BFrgskju.js";
4
- import { P as U } from "./PersistentCollection-BFrgskju.js";
5
- class x extends Error {
6
- constructor(t, e) {
7
- super(e ?? `HTTP ${t}`), this.status = t, this.name = "HttpError";
8
- }
9
- }
10
- function O(r) {
11
- return r instanceof Error && r.name === "AbortError";
12
- }
13
- function C(r) {
14
- return r === 401 ? "unauthorized" : r === 403 ? "forbidden" : r === 404 ? "not_found" : r === 422 ? "validation" : r === 429 ? "rate_limited" : r >= 500 ? "server_error" : "unknown";
15
- }
16
- function z(r) {
17
- return typeof r == "object" && r !== null && typeof r.status == "number" && typeof r.statusText == "string" && !(r instanceof Error);
18
- }
19
- function T(r) {
20
- return r instanceof Error && r.name === "AbortError" ? {
21
- code: "abort",
22
- message: "Request was aborted",
23
- original: r
24
- } : r instanceof x ? {
25
- code: C(r.status),
26
- message: r.message,
27
- status: r.status,
28
- original: r
29
- } : z(r) ? {
30
- code: C(r.status),
31
- message: r.statusText || `HTTP ${r.status}`,
32
- status: r.status,
33
- original: r
34
- } : r instanceof TypeError && r.message.toLowerCase().includes("fetch") ? {
35
- code: "network",
36
- message: r.message,
37
- original: r
38
- } : r instanceof Error && r.name === "TimeoutError" ? {
39
- code: "timeout",
40
- message: r.message,
41
- original: r
42
- } : r instanceof Error ? {
43
- code: "unknown",
44
- message: r.message,
45
- original: r
46
- } : {
47
- code: "unknown",
48
- message: String(r),
49
- original: r
50
- };
51
- }
52
- const d = typeof __MVC_KIT_DEV__ < "u" && __MVC_KIT_DEV__;
53
- function M(r) {
54
- return r !== null && typeof r == "object" && typeof r.subscribe == "function";
55
- }
56
- function b(r, t, e) {
57
- let s = Object.getPrototypeOf(r);
58
- for (; s && s !== t; ) {
59
- const n = Object.getOwnPropertyDescriptors(s);
60
- for (const [o, c] of Object.entries(n))
61
- o !== "constructor" && e(o, c, s);
62
- s = Object.getPrototypeOf(s);
63
- }
64
- }
65
- const j = Object.freeze({ loading: !1, error: null, errorCode: null }), m = ["async", "subscribeAsync"], P = /* @__PURE__ */ new Set(["onInit", "onSet", "onDispose"]);
66
- class y {
67
- _state;
68
- _initialState;
69
- _disposed = !1;
70
- _initialized = !1;
71
- _listeners = /* @__PURE__ */ new Set();
72
- _abortController = null;
73
- _cleanups = null;
74
- _subscriptionCleanups = null;
75
- _eventBus = null;
76
- // ── Reactive derived state (RFC 1) ─────────────────────────────
77
- _revision = 0;
78
- _stateTracking = null;
79
- _sourceTracking = null;
80
- _trackedSources = /* @__PURE__ */ new Map();
81
- // ── Async tracking (RFC 2) ──────────────────────────────────────
82
- _asyncStates = /* @__PURE__ */ new Map();
83
- _asyncSnapshots = /* @__PURE__ */ new Map();
84
- _asyncListeners = /* @__PURE__ */ new Set();
85
- _asyncProxy = null;
86
- _activeOps = null;
87
- /** DEV-only timeout (ms) for detecting ghost async operations after dispose. */
88
- static GHOST_TIMEOUT = 3e3;
89
- constructor(t) {
90
- this._state = Object.freeze({ ...t }), this._initialState = this._state, this._guardReservedKeys();
91
- }
92
- /** Current frozen state object. */
93
- get state() {
94
- return this._state;
95
- }
96
- /** Whether this instance has been disposed. */
97
- get disposed() {
98
- return this._disposed;
99
- }
100
- /** Whether init() has been called. */
101
- get initialized() {
102
- return this._initialized;
103
- }
104
- /** AbortSignal that fires when this instance is disposed. Lazily created. */
105
- get disposeSignal() {
106
- return this._abortController || (this._abortController = new AbortController()), this._abortController.signal;
107
- }
108
- /** Lazily-created typed EventBus for emitting and subscribing to events. */
109
- get events() {
110
- return this._eventBus || (this._eventBus = new E()), this._eventBus;
111
- }
112
- /** Initializes the instance. Called automatically by React hooks after mount. */
113
- init() {
114
- if (!(this._initialized || this._disposed))
115
- return this._initialized = !0, this._trackSubscribables(), this._installStateProxy(), this._memoizeGetters(), this._wrapMethods(), this.onInit?.();
116
- }
117
- /**
118
- * Merges partial state into current state. If no values actually
119
- * changed by reference, this is a no-op.
120
- *
121
- * Triggers React re-render via listener notification. Called when:
122
- * - User code calls set() to update source state
123
- *
124
- * NOT called for subscribable member notifications — those use
125
- * a separate notification path (see _trackSubscribables).
126
- */
127
- set(t) {
128
- if (this._disposed) return;
129
- if (d && this._stateTracking) {
130
- console.error(
131
- "[mvc-kit] set() called inside a getter. Getters must be pure — they read state and return a value. They must never call set(), which would cause an infinite render loop. Move this logic to an action method."
132
- );
133
- return;
134
- }
135
- const e = typeof t == "function" ? t(this._state) : t;
136
- if (!Object.keys(e).some(
137
- (l) => e[l] !== this._state[l]
138
- ))
139
- return;
140
- const o = this._state, c = Object.freeze({ ...o, ...e });
141
- this._state = c, this._revision++, this.onSet?.(o, c);
142
- for (const l of this._listeners)
143
- l(c, o);
144
- }
145
- /**
146
- * Emits a typed event via the internal EventBus.
147
- * Safe to call during dispose cleanup callbacks.
148
- * @protected
149
- */
150
- emit(t, e) {
151
- (this._eventBus?.disposed ?? this._disposed) || this.events.emit(t, e);
152
- }
153
- /** Subscribes to state changes. Returns an unsubscribe function. */
154
- subscribe(t) {
155
- return this._disposed ? () => {
156
- } : (this._listeners.add(t), () => {
157
- this._listeners.delete(t);
158
- });
159
- }
160
- /** Tears down the instance, releasing all subscriptions and resources. */
161
- dispose() {
162
- if (!this._disposed) {
163
- if (this._disposed = !0, this._teardownSubscriptions(), this._abortController?.abort(), this._cleanups) {
164
- for (const t of this._cleanups) t();
165
- this._cleanups = null;
166
- }
167
- this._eventBus?.dispose(), this.onDispose?.(), this._listeners.clear();
168
- }
169
- }
170
- /**
171
- * Resets state to initial values (or provided state), aborts in-flight work,
172
- * clears async tracking, and re-runs onInit().
173
- */
174
- reset(t) {
175
- if (!this._disposed) {
176
- this._abortController?.abort(), this._abortController = null, this._teardownSubscriptions(), this._state = t ? Object.freeze({ ...t }) : this._initialState, this._revision++, this._asyncStates.clear(), this._asyncSnapshots.clear(), this._notifyAsync(), this._trackSubscribables();
177
- for (const e of this._listeners)
178
- e(this._state, this._state);
179
- return this.onInit?.();
180
- }
181
- }
182
- /**
183
- * Registers a cleanup function to be called on dispose. Used internally for things like method wrapper
184
- * cleanup and event bus disposal, but can also be used by subclasses for custom cleanup logic.
185
- * @param fn
186
- * @protected
187
- */
188
- addCleanup(t) {
189
- this._cleanups || (this._cleanups = []), this._cleanups.push(t);
190
- }
191
- /** Subscribes to an external Subscribable with automatic cleanup on dispose. @protected */
192
- subscribeTo(t, e) {
193
- const s = t.subscribe(e);
194
- return this._subscriptionCleanups || (this._subscriptionCleanups = []), this._subscriptionCleanups.push(s), s;
195
- }
196
- // ── Async tracking API ──────────────────────────────────────────
197
- /** Proxy providing `TaskState` (loading, error, errorCode) per async method. */
198
- get async() {
199
- if (!this._asyncProxy) {
200
- const t = this;
201
- this._asyncProxy = new Proxy({}, {
202
- get(e, s) {
203
- return t._asyncSnapshots.get(s) ?? j;
204
- },
205
- has(e, s) {
206
- return t._asyncSnapshots.has(s);
207
- },
208
- ownKeys() {
209
- return Array.from(t._asyncSnapshots.keys());
210
- },
211
- getOwnPropertyDescriptor(e, s) {
212
- if (t._asyncSnapshots.has(s))
213
- return { configurable: !0, enumerable: !0, value: t._asyncSnapshots.get(s) };
214
- }
215
- });
216
- }
217
- return this._asyncProxy;
218
- }
219
- /** Subscribes to async state changes. Used by `useAsync` for React integration. */
220
- subscribeAsync(t) {
221
- return this._disposed ? () => {
222
- } : (this._asyncListeners.add(t), () => {
223
- this._asyncListeners.delete(t);
224
- });
225
- }
226
- _notifyAsync() {
227
- for (const t of this._asyncListeners)
228
- t();
229
- }
230
- // ── Async tracking internals ────────────────────────────────────
231
- _teardownSubscriptions() {
232
- for (const t of this._trackedSources.values()) t.unsubscribe();
233
- if (this._trackedSources.clear(), this._subscriptionCleanups) {
234
- for (const t of this._subscriptionCleanups) t();
235
- this._subscriptionCleanups = null;
236
- }
237
- }
238
- _guardReservedKeys() {
239
- b(this, y.prototype, (t) => {
240
- if (m.includes(t))
241
- throw new Error(
242
- `[mvc-kit] "${t}" is a reserved property on ViewModel and cannot be overridden.`
243
- );
244
- });
245
- }
246
- _wrapMethods() {
247
- for (const n of m)
248
- if (Object.getOwnPropertyDescriptor(this, n)?.value !== void 0)
249
- throw new Error(
250
- `[mvc-kit] "${n}" is a reserved property on ViewModel and cannot be overridden.`
251
- );
252
- const t = this, e = /* @__PURE__ */ new Set(), s = [];
253
- d && (this._activeOps = /* @__PURE__ */ new Map()), b(this, y.prototype, (n, o) => {
254
- if (o.get || o.set || typeof o.value != "function" || n.startsWith("_") || P.has(n) || e.has(n)) return;
255
- e.add(n);
256
- const c = o.value;
257
- let l = !1;
258
- const p = function(...g) {
259
- if (t._disposed) {
260
- d && console.warn(`[mvc-kit] "${n}" called after dispose — ignored.`);
261
- return;
262
- }
263
- d && !t._initialized && console.warn(
264
- `[mvc-kit] "${n}" called before init(). Async tracking is active only after init().`
265
- );
266
- let u;
267
- try {
268
- u = c.apply(t, g);
269
- } catch (a) {
270
- throw a;
271
- }
272
- if (!u || typeof u.then != "function")
273
- return l || (l = !0, t._asyncStates.delete(n), t._asyncSnapshots.delete(n), t[n] = c.bind(t)), u;
274
- let i = t._asyncStates.get(n);
275
- return i || (i = { loading: !1, error: null, errorCode: null, count: 0 }, t._asyncStates.set(n, i)), i.count++, i.loading = !0, i.error = null, i.errorCode = null, t._asyncSnapshots.set(n, Object.freeze({ loading: !0, error: null, errorCode: null })), t._notifyAsync(), d && t._activeOps && t._activeOps.set(n, (t._activeOps.get(n) ?? 0) + 1), u.then(
276
- (a) => {
277
- if (t._disposed) return a;
278
- if (i.count--, i.loading = i.count > 0, t._asyncSnapshots.set(
279
- n,
280
- Object.freeze({ loading: i.loading, error: i.error, errorCode: i.errorCode })
281
- ), t._notifyAsync(), d && t._activeOps) {
282
- const _ = (t._activeOps.get(n) ?? 1) - 1;
283
- _ <= 0 ? t._activeOps.delete(n) : t._activeOps.set(n, _);
284
- }
285
- return a;
286
- },
287
- (a) => {
288
- if (O(a)) {
289
- if (t._disposed || (i.count--, i.loading = i.count > 0, t._asyncSnapshots.set(
290
- n,
291
- Object.freeze({ loading: i.loading, error: i.error, errorCode: i.errorCode })
292
- ), t._notifyAsync()), d && t._activeOps) {
293
- const h = (t._activeOps.get(n) ?? 1) - 1;
294
- h <= 0 ? t._activeOps.delete(n) : t._activeOps.set(n, h);
295
- }
296
- return;
297
- }
298
- if (t._disposed) return;
299
- i.count--, i.loading = i.count > 0;
300
- const _ = T(a);
301
- if (i.error = _.message, i.errorCode = _.code, t._asyncSnapshots.set(
302
- n,
303
- Object.freeze({ loading: i.loading, error: _.message, errorCode: _.code })
304
- ), t._notifyAsync(), d && t._activeOps) {
305
- const h = (t._activeOps.get(n) ?? 1) - 1;
306
- h <= 0 ? t._activeOps.delete(n) : t._activeOps.set(n, h);
307
- }
308
- throw a;
309
- }
310
- );
311
- };
312
- s.push(n), t[n] = p;
313
- }), s.length > 0 && this.addCleanup(() => {
314
- const n = d && t._activeOps ? new Map(t._activeOps) : null;
315
- for (const o of s)
316
- d ? t[o] = () => {
317
- console.warn(`[mvc-kit] "${o}" called after dispose — ignored.`);
318
- } : t[o] = () => {
319
- };
320
- t._asyncListeners.clear(), t._asyncStates.clear(), t._asyncSnapshots.clear(), d && n && n.size > 0 && t._scheduleGhostCheck(n);
321
- });
322
- }
323
- _scheduleGhostCheck(t) {
324
- d && setTimeout(() => {
325
- for (const [e, s] of t)
326
- console.warn(
327
- `[mvc-kit] Ghost async operation detected: "${e}" had ${s} pending call(s) when the ViewModel was disposed. Consider using disposeSignal to cancel in-flight work.`
328
- );
329
- }, this.constructor.GHOST_TIMEOUT);
330
- }
331
- // ── Auto-tracking internals ────────────────────────────────────
332
- /**
333
- * Installs a context-sensitive state getter on the instance.
334
- *
335
- * During getter tracking (_stateTracking is active): returns a Proxy
336
- * that records which state properties are accessed.
337
- *
338
- * Otherwise: returns the frozen state object directly. This is critical
339
- * for React's useSyncExternalStore — it needs a changing reference to
340
- * detect state updates and trigger re-renders.
341
- */
342
- _installStateProxy() {
343
- const t = new Proxy({}, {
344
- get: (e, s) => (this._stateTracking?.add(s), this._state[s]),
345
- ownKeys: () => Reflect.ownKeys(this._state),
346
- getOwnPropertyDescriptor: (e, s) => Reflect.getOwnPropertyDescriptor(this._state, s),
347
- set: () => {
348
- throw new Error("Cannot mutate state directly. Use set() instead.");
349
- },
350
- has: (e, s) => s in this._state
351
- });
352
- Object.defineProperty(this, "state", {
353
- get: () => this._stateTracking ? t : this._state,
354
- configurable: !0,
355
- enumerable: !0
356
- });
357
- }
358
- /**
359
- * Scans own instance properties for Subscribable objects and sets up
360
- * automatic dependency tracking for each one found.
361
- *
362
- * For each subscribable member:
363
- * 1. Subscribe to it. On notification: bump its tracked revision
364
- * AND the VM's global revision, then force a new state reference
365
- * and notify listeners so React re-renders.
366
- * 2. Replace the instance property with a getter that participates
367
- * in dependency tracking.
368
- * 3. Register unsubscribe in the dispose chain.
369
- *
370
- * Called during init(), AFTER all subclass property initializers
371
- * have run (they execute during the constructor, before init()).
372
- */
373
- _trackSubscribables() {
374
- for (const t of Object.getOwnPropertyNames(this)) {
375
- const e = this[t];
376
- if (!M(e)) continue;
377
- let s;
378
- const n = () => {
379
- if (!this._disposed) {
380
- s.revision++, this._revision++, this._state = Object.freeze({ ...this._state });
381
- for (const l of this._listeners)
382
- l(this._state, this._state);
383
- }
384
- }, o = e.subscribe(n), c = typeof e.subscribeAsync == "function" ? e.subscribeAsync(n) : void 0;
385
- s = {
386
- source: e,
387
- revision: 0,
388
- unsubscribe: c ? () => {
389
- o(), c();
390
- } : o
391
- }, this._trackedSources.set(t, s), Object.defineProperty(this, t, {
392
- get: () => (this._sourceTracking?.set(t, s), e),
393
- configurable: !0,
394
- enumerable: !1
395
- });
396
- }
397
- }
398
- /**
399
- * Walks the prototype chain from the subclass up to (but not including)
400
- * ViewModel.prototype. For every getter found, replaces it on the
401
- * instance with a memoized version that tracks dependencies and caches.
402
- *
403
- * Processing order: most-derived class first. If a subclass overrides
404
- * a parent getter, only the subclass version is memoized.
405
- */
406
- _memoizeGetters() {
407
- const t = /* @__PURE__ */ new Set();
408
- b(this, y.prototype, (e, s) => {
409
- !s.get || t.has(e) || (t.add(e), this._wrapGetter(e, s.get));
410
- });
411
- }
412
- /**
413
- * Replaces a single prototype getter with a memoized version on this
414
- * instance. The memoized getter tracks both state dependencies and
415
- * subscribable member dependencies, caching its result and
416
- * revalidating through a three-tier strategy:
417
- *
418
- * Tier 1 (fast): revision unchanged → return cached (1 int compare)
419
- * Tier 2 (medium): revision changed but this getter's deps didn't → return cached
420
- * Tier 3 (slow): at least one dep changed → full recompute with tracking
421
- */
422
- _wrapGetter(t, e) {
423
- let s, n = -1, o, c, l;
424
- Object.defineProperty(this, t, {
425
- get: () => {
426
- if (this._disposed || n === this._revision)
427
- return s;
428
- if (o && c) {
429
- let i = !0;
430
- for (const [a, _] of c)
431
- if (this._state[a] !== _) {
432
- i = !1;
433
- break;
434
- }
435
- if (i && l)
436
- for (const [a, _] of l) {
437
- const h = this._trackedSources.get(a);
438
- if (h && h.revision !== _) {
439
- i = !1;
440
- break;
441
- }
442
- }
443
- if (i)
444
- return n = this._revision, s;
445
- }
446
- const p = this._stateTracking, g = this._sourceTracking;
447
- this._stateTracking = /* @__PURE__ */ new Set(), this._sourceTracking = /* @__PURE__ */ new Map();
448
- try {
449
- s = e.call(this);
450
- } catch (i) {
451
- throw this._stateTracking = p, this._sourceTracking = g, i;
452
- }
453
- o = this._stateTracking;
454
- const u = this._sourceTracking;
455
- if (this._stateTracking = p, this._sourceTracking = g, p)
456
- for (const i of o) p.add(i);
457
- if (g)
458
- for (const [i, a] of u)
459
- g.set(i, a);
460
- c = /* @__PURE__ */ new Map();
461
- for (const i of o)
462
- c.set(i, this._state[i]);
463
- l = /* @__PURE__ */ new Map();
464
- for (const [i, a] of u)
465
- l.set(i, a.revision);
466
- return n = this._revision, s;
467
- },
468
- configurable: !0,
469
- enumerable: !0
470
- });
471
- }
472
- }
473
- class $ {
474
- _state;
475
- _committed;
476
- _disposed = !1;
477
- _initialized = !1;
478
- _listeners = /* @__PURE__ */ new Set();
479
- _abortController = null;
480
- _cleanups = null;
481
- constructor(t) {
482
- const e = Object.freeze({ ...t });
483
- this._state = e, this._committed = e;
484
- }
485
- /** Current frozen state object. */
486
- get state() {
487
- return this._state;
488
- }
489
- /**
490
- * The baseline state for dirty tracking.
491
- */
492
- get committed() {
493
- return this._committed;
494
- }
495
- /**
496
- * True if current state differs from committed state.
497
- */
498
- get dirty() {
499
- return !this.shallowEqual(this._state, this._committed);
500
- }
501
- /**
502
- * Validation errors for the current state.
503
- */
504
- get errors() {
505
- return this.validate(this._state);
506
- }
507
- /**
508
- * True if there are no validation errors.
509
- */
510
- get valid() {
511
- return Object.keys(this.errors).length === 0;
512
- }
513
- /** Whether this instance has been disposed. */
514
- get disposed() {
515
- return this._disposed;
516
- }
517
- /** Whether init() has been called. */
518
- get initialized() {
519
- return this._initialized;
520
- }
521
- /** AbortSignal that fires when this instance is disposed. Lazily created. */
522
- get disposeSignal() {
523
- return this._abortController || (this._abortController = new AbortController()), this._abortController.signal;
524
- }
525
- /** Initializes the instance. Called automatically by React hooks after mount. */
526
- init() {
527
- if (!(this._initialized || this._disposed))
528
- return this._initialized = !0, this.onInit?.();
529
- }
530
- /**
531
- * Merges partial state with validation. No-op if no values changed by reference.
532
- * @protected
533
- */
534
- set(t) {
535
- if (this._disposed)
536
- throw new Error("Cannot set state on disposed Model");
537
- const e = typeof t == "function" ? t(this._state) : t;
538
- if (!Object.keys(e).some(
539
- (l) => e[l] !== this._state[l]
540
- ))
541
- return;
542
- const o = this._state, c = Object.freeze({ ...o, ...e });
543
- this._state = c, this.onSet?.(o, c);
544
- for (const l of this._listeners)
545
- l(c, o);
546
- }
547
- /**
548
- * Mark current state as the new baseline (not dirty).
549
- */
550
- commit() {
551
- if (this._disposed)
552
- throw new Error("Cannot commit on disposed Model");
553
- this._committed = this._state;
554
- }
555
- /**
556
- * Revert state to committed baseline.
557
- */
558
- rollback() {
559
- if (this._disposed)
560
- throw new Error("Cannot rollback on disposed Model");
561
- if (this.shallowEqual(this._state, this._committed))
562
- return;
563
- const t = this._state;
564
- this._state = this._committed, this.onSet?.(t, this._state);
565
- for (const e of this._listeners)
566
- e(this._state, t);
567
- }
568
- /** Subscribes to state changes. Returns an unsubscribe function. */
569
- subscribe(t) {
570
- return this._disposed ? () => {
571
- } : (this._listeners.add(t), () => {
572
- this._listeners.delete(t);
573
- });
574
- }
575
- /** Tears down the instance, releasing all subscriptions and resources. */
576
- dispose() {
577
- if (!this._disposed) {
578
- if (this._disposed = !0, this._abortController?.abort(), this._cleanups) {
579
- for (const t of this._cleanups) t();
580
- this._cleanups = null;
581
- }
582
- this.onDispose?.(), this._listeners.clear();
583
- }
584
- }
585
- /**
586
- * Override to provide validation logic.
587
- * Return an object mapping field keys to error messages.
588
- */
589
- validate(t) {
590
- return {};
591
- }
592
- /** Registers a cleanup function to be called on dispose. @protected */
593
- addCleanup(t) {
594
- this._cleanups || (this._cleanups = []), this._cleanups.push(t);
595
- }
596
- /** Subscribes to an external Subscribable with automatic cleanup on dispose. @protected */
597
- subscribeTo(t, e) {
598
- const s = t.subscribe(e);
599
- return this.addCleanup(s), s;
600
- }
601
- shallowEqual(t, e) {
602
- const s = Object.keys(t), n = Object.keys(e);
603
- if (s.length !== n.length)
604
- return !1;
605
- for (const o of s)
606
- if (t[o] !== e[o])
607
- return !1;
608
- return !0;
609
- }
610
- }
611
- const f = typeof __MVC_KIT_DEV__ < "u" && __MVC_KIT_DEV__, D = Object.freeze({ loading: !1, error: null, errorCode: null }), w = ["async", "subscribeAsync"], k = /* @__PURE__ */ new Set(["onInit", "onDispose"]);
612
- class v extends A {
613
- _external = null;
614
- _initialized = !1;
615
- // ── Async tracking fields ──
616
- _asyncStates = /* @__PURE__ */ new Map();
617
- _asyncSnapshots = /* @__PURE__ */ new Map();
618
- _asyncListeners = /* @__PURE__ */ new Set();
619
- _asyncProxy = null;
620
- _activeOps = null;
621
- /** DEV-only timeout (ms) for detecting ghost async operations after dispose. */
622
- static GHOST_TIMEOUT = 3e3;
623
- constructor(t) {
624
- const e = t != null && !Array.isArray(t);
625
- if (super(e ? [] : t ?? []), e && (this._external = t, f)) {
626
- const s = this.constructor;
627
- (s.MAX_SIZE > 0 || s.TTL > 0) && console.warn(
628
- `[mvc-kit] Resource "${s.name}" has MAX_SIZE or TTL set but uses an injected Collection. Configure these on the Collection instead.`
629
- );
630
- }
631
- this._guardReservedKeys();
632
- }
633
- // ── Lifecycle ─────────────────────────────────────────────────
634
- /** Whether init() has been called. */
635
- get initialized() {
636
- return this._initialized;
637
- }
638
- /** Initializes the instance. Called automatically by React hooks after mount. */
639
- init() {
640
- if (!(this._initialized || this.disposed))
641
- return this._initialized = !0, this._wrapMethods(), this.onInit?.();
642
- }
643
- // ── Collection delegation ─────────────────────────────────────
644
- get state() {
645
- return this._external ? this._external.state : super.state;
646
- }
647
- get items() {
648
- return this._external ? this._external.items : super.items;
649
- }
650
- get length() {
651
- return this._external ? this._external.length : super.length;
652
- }
653
- add(...t) {
654
- this._external ? this._external.add(...t) : super.add(...t);
655
- }
656
- upsert(...t) {
657
- this._external ? this._external.upsert(...t) : super.upsert(...t);
658
- }
659
- update(t, e) {
660
- this._external ? this._external.update(t, e) : super.update(t, e);
661
- }
662
- remove(...t) {
663
- this._external ? this._external.remove(...t) : super.remove(...t);
664
- }
665
- reset(t) {
666
- this._external ? this._external.reset(t) : super.reset(t);
667
- }
668
- clear() {
669
- this._external ? this._external.clear() : super.clear();
670
- }
671
- optimistic(t) {
672
- return this._external ? this._external.optimistic(t) : super.optimistic(t);
673
- }
674
- get(t) {
675
- return this._external ? this._external.get(t) : super.get(t);
676
- }
677
- has(t) {
678
- return this._external ? this._external.has(t) : super.has(t);
679
- }
680
- find(t) {
681
- return this._external ? this._external.find(t) : super.find(t);
682
- }
683
- filter(t) {
684
- return this._external ? this._external.filter(t) : super.filter(t);
685
- }
686
- sorted(t) {
687
- return this._external ? this._external.sorted(t) : super.sorted(t);
688
- }
689
- map(t) {
690
- return this._external ? this._external.map(t) : super.map(t);
691
- }
692
- subscribe(t) {
693
- return this.disposed ? () => {
694
- } : this._external ? this._external.subscribe(t) : super.subscribe(t);
695
- }
696
- // ── Async tracking API ────────────────────────────────────────
697
- /** Proxy providing `TaskState` (loading, error, errorCode) per async method. */
698
- get async() {
699
- if (!this._asyncProxy) {
700
- const t = this;
701
- this._asyncProxy = new Proxy({}, {
702
- get(e, s) {
703
- return t._asyncSnapshots.get(s) ?? D;
704
- },
705
- has(e, s) {
706
- return t._asyncSnapshots.has(s);
707
- },
708
- ownKeys() {
709
- return Array.from(t._asyncSnapshots.keys());
710
- },
711
- getOwnPropertyDescriptor(e, s) {
712
- if (t._asyncSnapshots.has(s))
713
- return { configurable: !0, enumerable: !0, value: t._asyncSnapshots.get(s) };
714
- }
715
- });
716
- }
717
- return this._asyncProxy;
718
- }
719
- /** Subscribes to async state changes. Used by `useAsync` and `useInstance` for React integration. */
720
- subscribeAsync(t) {
721
- return this.disposed ? () => {
722
- } : (this._asyncListeners.add(t), () => {
723
- this._asyncListeners.delete(t);
724
- });
725
- }
726
- // ── Private: async tracking internals ─────────────────────────
727
- _notifyAsync() {
728
- for (const t of this._asyncListeners)
729
- t();
730
- }
731
- _guardReservedKeys() {
732
- b(this, v.prototype, (t) => {
733
- if (w.includes(t))
734
- throw new Error(
735
- `[mvc-kit] "${t}" is a reserved property on Resource and cannot be overridden.`
736
- );
737
- });
738
- }
739
- _wrapMethods() {
740
- for (const n of w)
741
- if (Object.getOwnPropertyDescriptor(this, n)?.value !== void 0)
742
- throw new Error(
743
- `[mvc-kit] "${n}" is a reserved property on Resource and cannot be overridden.`
744
- );
745
- const t = this, e = /* @__PURE__ */ new Set(), s = [];
746
- f && (this._activeOps = /* @__PURE__ */ new Map()), b(this, v.prototype, (n, o) => {
747
- if (o.get || o.set || typeof o.value != "function" || n.startsWith("_") || k.has(n) || e.has(n)) return;
748
- e.add(n);
749
- const c = o.value;
750
- let l = !1;
751
- const p = function(...g) {
752
- if (t.disposed) {
753
- f && console.warn(`[mvc-kit] "${n}" called after dispose — ignored.`);
754
- return;
755
- }
756
- f && !t._initialized && console.warn(
757
- `[mvc-kit] "${n}" called before init(). Async tracking is active only after init().`
758
- );
759
- let u;
760
- try {
761
- u = c.apply(t, g);
762
- } catch (a) {
763
- throw a;
764
- }
765
- if (!u || typeof u.then != "function")
766
- return l || (l = !0, t._asyncStates.delete(n), t._asyncSnapshots.delete(n), t[n] = c.bind(t)), u;
767
- let i = t._asyncStates.get(n);
768
- return i || (i = { loading: !1, error: null, errorCode: null, count: 0 }, t._asyncStates.set(n, i)), i.count++, i.loading = !0, i.error = null, i.errorCode = null, t._asyncSnapshots.set(n, Object.freeze({ loading: !0, error: null, errorCode: null })), t._notifyAsync(), f && t._activeOps && t._activeOps.set(n, (t._activeOps.get(n) ?? 0) + 1), u.then(
769
- (a) => {
770
- if (t.disposed) return a;
771
- if (i.count--, i.loading = i.count > 0, t._asyncSnapshots.set(
772
- n,
773
- Object.freeze({ loading: i.loading, error: i.error, errorCode: i.errorCode })
774
- ), t._notifyAsync(), f && t._activeOps) {
775
- const _ = (t._activeOps.get(n) ?? 1) - 1;
776
- _ <= 0 ? t._activeOps.delete(n) : t._activeOps.set(n, _);
777
- }
778
- return a;
779
- },
780
- (a) => {
781
- if (O(a)) {
782
- if (t.disposed || (i.count--, i.loading = i.count > 0, t._asyncSnapshots.set(
783
- n,
784
- Object.freeze({ loading: i.loading, error: i.error, errorCode: i.errorCode })
785
- ), t._notifyAsync()), f && t._activeOps) {
786
- const h = (t._activeOps.get(n) ?? 1) - 1;
787
- h <= 0 ? t._activeOps.delete(n) : t._activeOps.set(n, h);
788
- }
789
- return;
790
- }
791
- if (t.disposed) return;
792
- i.count--, i.loading = i.count > 0;
793
- const _ = T(a);
794
- if (i.error = _.message, i.errorCode = _.code, t._asyncSnapshots.set(
795
- n,
796
- Object.freeze({ loading: i.loading, error: _.message, errorCode: _.code })
797
- ), t._notifyAsync(), f && t._activeOps) {
798
- const h = (t._activeOps.get(n) ?? 1) - 1;
799
- h <= 0 ? t._activeOps.delete(n) : t._activeOps.set(n, h);
800
- }
801
- throw a;
802
- }
803
- );
804
- };
805
- s.push(n), t[n] = p;
806
- }), s.length > 0 && this.addCleanup(() => {
807
- const n = f && t._activeOps ? new Map(t._activeOps) : null;
808
- for (const o of s)
809
- f ? t[o] = () => {
810
- console.warn(`[mvc-kit] "${o}" called after dispose — ignored.`);
811
- } : t[o] = () => {
812
- };
813
- t._asyncListeners.clear(), t._asyncStates.clear(), t._asyncSnapshots.clear(), f && n && n.size > 0 && t._scheduleGhostCheck(n);
814
- });
815
- }
816
- _scheduleGhostCheck(t) {
817
- f && setTimeout(() => {
818
- for (const [e, s] of t)
819
- console.warn(
820
- `[mvc-kit] Ghost async operation detected: "${e}" had ${s} pending call(s) when the Resource was disposed. Consider using disposeSignal to cancel in-flight work.`
821
- );
822
- }, this.constructor.GHOST_TIMEOUT);
823
- }
824
- }
825
- class L {
826
- _disposed = !1;
827
- _initialized = !1;
828
- _abortController = null;
829
- _cleanups = null;
830
- /** Whether this instance has been disposed. */
831
- get disposed() {
832
- return this._disposed;
833
- }
834
- /** Whether init() has been called. */
835
- get initialized() {
836
- return this._initialized;
837
- }
838
- /** AbortSignal that fires when this instance is disposed. Lazily created. */
839
- get disposeSignal() {
840
- return this._abortController || (this._abortController = new AbortController()), this._abortController.signal;
841
- }
842
- /** Initializes the instance. Called automatically by React hooks after mount. */
843
- init() {
844
- if (!(this._initialized || this._disposed))
845
- return this._initialized = !0, this.onInit?.();
846
- }
847
- /** Tears down the instance, releasing all subscriptions and resources. */
848
- dispose() {
849
- if (!this._disposed) {
850
- if (this._disposed = !0, this._abortController?.abort(), this._cleanups) {
851
- for (const t of this._cleanups) t();
852
- this._cleanups = null;
853
- }
854
- this.onDispose?.();
855
- }
856
- }
857
- /** Registers a cleanup function to be called on dispose. @protected */
858
- addCleanup(t) {
859
- this._cleanups || (this._cleanups = []), this._cleanups.push(t);
860
- }
861
- /** Subscribes to an external Subscribable with automatic cleanup on dispose. @protected */
862
- subscribeTo(t, e) {
863
- const s = t.subscribe(e);
864
- return this.addCleanup(s), s;
865
- }
866
- }
867
- class V {
868
- _disposed = !1;
869
- _initialized = !1;
870
- _abortController = null;
871
- _cleanups = null;
872
- /** Whether this instance has been disposed. */
873
- get disposed() {
874
- return this._disposed;
875
- }
876
- /** Whether init() has been called. */
877
- get initialized() {
878
- return this._initialized;
879
- }
880
- /** AbortSignal that fires when this instance is disposed. Lazily created. */
881
- get disposeSignal() {
882
- return this._abortController || (this._abortController = new AbortController()), this._abortController.signal;
883
- }
884
- /** Initializes the instance. Called automatically by React hooks after mount. */
885
- init() {
886
- if (!(this._initialized || this._disposed))
887
- return this._initialized = !0, this.onInit?.();
888
- }
889
- /** Tears down the instance, releasing all subscriptions and resources. */
890
- dispose() {
891
- if (!this._disposed) {
892
- if (this._disposed = !0, this._abortController?.abort(), this._cleanups) {
893
- for (const t of this._cleanups) t();
894
- this._cleanups = null;
895
- }
896
- this.onDispose?.();
897
- }
898
- }
899
- /** Registers a cleanup function to be called on dispose. @protected */
900
- addCleanup(t) {
901
- this._cleanups || (this._cleanups = []), this._cleanups.push(t);
902
- }
903
- }
904
- const S = typeof __MVC_KIT_DEV__ < "u" && __MVC_KIT_DEV__, R = Object.freeze({
905
- connected: !1,
906
- reconnecting: !1,
907
- attempt: 0,
908
- error: null
909
- });
910
- class N {
911
- // Static config (subclass overrides)
912
- /** Base delay (ms) for reconnection backoff. */
913
- static RECONNECT_BASE = 1e3;
914
- /** Maximum delay cap (ms) for reconnection backoff. */
915
- static RECONNECT_MAX = 3e4;
916
- /** Exponential backoff multiplier for reconnection delay. */
917
- static RECONNECT_FACTOR = 2;
918
- /** Maximum number of reconnection attempts before giving up. */
919
- static MAX_ATTEMPTS = 1 / 0;
920
- // ── Internal state ──────────────────────────────────────────────
921
- _status = R;
922
- _connState = 0;
923
- _disposed = !1;
924
- _initialized = !1;
925
- _listeners = /* @__PURE__ */ new Set();
926
- _handlers = /* @__PURE__ */ new Map();
927
- _abortController = null;
928
- _connectAbort = null;
929
- _reconnectTimer = null;
930
- _cleanups = null;
931
- // ── Subscribable<ChannelStatus> ─────────────────────────────────
932
- /** Current connection status. */
933
- get state() {
934
- return this._status;
935
- }
936
- /** Subscribes to connection status changes. Returns an unsubscribe function. */
937
- subscribe(t) {
938
- return this._disposed ? () => {
939
- } : (this._listeners.add(t), () => {
940
- this._listeners.delete(t);
941
- });
942
- }
943
- // ── Disposable / Initializable ──────────────────────────────────
944
- /** Whether this instance has been disposed. */
945
- get disposed() {
946
- return this._disposed;
947
- }
948
- /** Whether init() has been called. */
949
- get initialized() {
950
- return this._initialized;
951
- }
952
- /** AbortSignal that fires when this instance is disposed. Lazily created. */
953
- get disposeSignal() {
954
- return this._abortController || (this._abortController = new AbortController()), this._abortController.signal;
955
- }
956
- /** Initializes the instance. Called automatically by React hooks after mount. */
957
- init() {
958
- if (!(this._initialized || this._disposed))
959
- return this._initialized = !0, this.onInit?.();
960
- }
961
- /** Tears down the instance, releasing all subscriptions and resources. */
962
- dispose() {
963
- if (!this._disposed) {
964
- this._disposed = !0, this._connState = 4, this._reconnectTimer !== null && (clearTimeout(this._reconnectTimer), this._reconnectTimer = null), this._connectAbort?.abort(), this._connectAbort = null, this._abortController?.abort();
965
- try {
966
- this.close();
967
- } catch {
968
- }
969
- if (this._cleanups) {
970
- for (const t of this._cleanups) t();
971
- this._cleanups = null;
972
- }
973
- this.onDispose?.(), this._listeners.clear(), this._handlers.clear();
974
- }
975
- }
976
- // ── Connection control ──────────────────────────────────────────
977
- /** Initiates a connection with automatic reconnection on failure. */
978
- connect() {
979
- if (this._disposed) {
980
- S && console.warn("[mvc-kit] connect() called after dispose — ignored.");
981
- return;
982
- }
983
- S && !this._initialized && console.warn("[mvc-kit] connect() called before init()."), !(this._connState === 1 || this._connState === 2) && (this._reconnectTimer !== null && (clearTimeout(this._reconnectTimer), this._reconnectTimer = null), this._attemptConnect(0));
984
- }
985
- /** Closes the connection and cancels any pending reconnection. */
986
- disconnect() {
987
- if (!this._disposed) {
988
- if (this._reconnectTimer !== null && (clearTimeout(this._reconnectTimer), this._reconnectTimer = null), this._connectAbort?.abort(), this._connectAbort = null, this._connState === 2 || this._connState === 1) {
989
- this._connState = 0;
990
- try {
991
- this.close();
992
- } catch {
993
- }
994
- } else
995
- this._connState = 0;
996
- this._setStatus({ connected: !1, reconnecting: !1, attempt: 0, error: null });
997
- }
998
- }
999
- // ── Subclass signals ────────────────────────────────────────────
1000
- /** Call from subclass when a message arrives from the transport. @protected */
1001
- receive(t, e) {
1002
- if (this._disposed) {
1003
- S && console.warn(`[mvc-kit] receive("${String(t)}") called after dispose — ignored.`);
1004
- return;
1005
- }
1006
- const s = this._handlers.get(t);
1007
- if (s)
1008
- for (const n of s)
1009
- n(e);
1010
- }
1011
- /** Call from subclass when the transport connection drops unexpectedly. Triggers reconnection. @protected */
1012
- disconnected() {
1013
- this._disposed || this._connState !== 2 && this._connState !== 1 || (this._connectAbort?.abort(), this._connectAbort = null, this._connState = 3, this._scheduleReconnect(1));
1014
- }
1015
- // ── Consumer API ────────────────────────────────────────────────
1016
- /** Subscribes to a specific message type. Returns an unsubscribe function. */
1017
- on(t, e) {
1018
- if (this._disposed) return () => {
1019
- };
1020
- let s = this._handlers.get(t);
1021
- return s || (s = /* @__PURE__ */ new Set(), this._handlers.set(t, s)), s.add(e), () => {
1022
- s.delete(e);
1023
- };
1024
- }
1025
- /** Subscribes to a message type, auto-removing the handler after the first invocation. */
1026
- once(t, e) {
1027
- const s = this.on(t, (n) => {
1028
- s(), e(n);
1029
- });
1030
- return s;
1031
- }
1032
- // ── Infrastructure ──────────────────────────────────────────────
1033
- /** Registers a cleanup function to be called on dispose. @protected */
1034
- addCleanup(t) {
1035
- this._cleanups || (this._cleanups = []), this._cleanups.push(t);
1036
- }
1037
- /** Subscribes to an external Subscribable with automatic cleanup on dispose. @protected */
1038
- subscribeTo(t, e) {
1039
- const s = t.subscribe(e);
1040
- return this.addCleanup(s), s;
1041
- }
1042
- // ── Backoff ─────────────────────────────────────────────────────
1043
- /** Computes the reconnect backoff delay with jitter for the given attempt number. @protected */
1044
- _calculateDelay(t) {
1045
- const e = this.constructor, s = Math.min(
1046
- e.RECONNECT_BASE * Math.pow(e.RECONNECT_FACTOR, t),
1047
- e.RECONNECT_MAX
1048
- );
1049
- return Math.random() * s;
1050
- }
1051
- // ── Internals ───────────────────────────────────────────────────
1052
- _setStatus(t) {
1053
- const e = this._status;
1054
- if (!(e.connected === t.connected && e.reconnecting === t.reconnecting && e.attempt === t.attempt && e.error === t.error)) {
1055
- this._status = Object.freeze(t);
1056
- for (const s of this._listeners)
1057
- s(this._status, e);
1058
- }
1059
- }
1060
- _attemptConnect(t) {
1061
- if (this._disposed) return;
1062
- this._connState = 1, this._connectAbort?.abort(), this._connectAbort = new AbortController();
1063
- const e = this._abortController ? AbortSignal.any([this._abortController.signal, this._connectAbort.signal]) : this._connectAbort.signal;
1064
- this._setStatus({
1065
- connected: !1,
1066
- reconnecting: t > 0,
1067
- attempt: t,
1068
- error: null
1069
- });
1070
- let s;
1071
- try {
1072
- s = this.open(e);
1073
- } catch (n) {
1074
- this._onOpenFailed(t, n);
1075
- return;
1076
- }
1077
- s && typeof s.then == "function" ? s.then(
1078
- () => this._onOpenSucceeded(),
1079
- (n) => this._onOpenFailed(t, n)
1080
- ) : this._onOpenSucceeded();
1081
- }
1082
- _onOpenSucceeded() {
1083
- this._disposed || this._connState === 1 && (this._connState = 2, this._setStatus({
1084
- connected: !0,
1085
- reconnecting: !1,
1086
- attempt: 0,
1087
- error: null
1088
- }));
1089
- }
1090
- _onOpenFailed(t, e) {
1091
- this._disposed || this._connState !== 0 && (this._connectAbort?.abort(), this._connectAbort = null, this._connState = 3, this._scheduleReconnect(t + 1, e));
1092
- }
1093
- _scheduleReconnect(t, e) {
1094
- const s = this.constructor;
1095
- if (t > s.MAX_ATTEMPTS) {
1096
- this._connState = 0, this._setStatus({
1097
- connected: !1,
1098
- reconnecting: !1,
1099
- attempt: t,
1100
- error: e instanceof Error ? e.message : "Max reconnection attempts reached"
1101
- });
1102
- return;
1103
- }
1104
- const n = e instanceof Error ? e.message : e ? String(e) : null;
1105
- this._setStatus({
1106
- connected: !1,
1107
- reconnecting: !0,
1108
- attempt: t,
1109
- error: n
1110
- });
1111
- const o = this._calculateDelay(t - 1);
1112
- this._reconnectTimer = setTimeout(() => {
1113
- this._reconnectTimer = null, this._attemptConnect(t);
1114
- }, o);
1115
- }
1116
- }
1
+ import { ViewModel } from "./ViewModel.js";
2
+ import { Model } from "./Model.js";
3
+ import { Collection } from "./Collection.js";
4
+ import { PersistentCollection } from "./PersistentCollection.js";
5
+ import { Resource } from "./Resource.js";
6
+ import { Controller } from "./Controller.js";
7
+ import { Service } from "./Service.js";
8
+ import { EventBus } from "./EventBus.js";
9
+ import { Channel } from "./Channel.js";
10
+ import { HttpError, classifyError, isAbortError } from "./errors.js";
11
+ import { hasSingleton, singleton, teardown, teardownAll } from "./singleton.js";
1117
12
  export {
1118
- N as Channel,
1119
- A as Collection,
1120
- L as Controller,
1121
- E as EventBus,
1122
- x as HttpError,
1123
- $ as Model,
1124
- U as PersistentCollection,
1125
- v as Resource,
1126
- V as Service,
1127
- y as ViewModel,
1128
- T as classifyError,
1129
- B as hasSingleton,
1130
- O as isAbortError,
1131
- F as singleton,
1132
- X as teardown,
1133
- Y as teardownAll
13
+ Channel,
14
+ Collection,
15
+ Controller,
16
+ EventBus,
17
+ HttpError,
18
+ Model,
19
+ PersistentCollection,
20
+ Resource,
21
+ Service,
22
+ ViewModel,
23
+ classifyError,
24
+ hasSingleton,
25
+ isAbortError,
26
+ singleton,
27
+ teardown,
28
+ teardownAll
1134
29
  };
1135
30
  //# sourceMappingURL=mvc-kit.js.map