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.
- package/README.md +4 -4
- package/agent-config/claude-code/skills/guide/api-reference.md +10 -10
- package/dist/Channel.cjs +291 -0
- package/dist/Channel.cjs.map +1 -0
- package/dist/Channel.d.ts +1 -1
- package/dist/Channel.d.ts.map +1 -1
- package/dist/Channel.js +291 -0
- package/dist/Channel.js.map +1 -0
- package/dist/Collection.cjs +452 -0
- package/dist/Collection.cjs.map +1 -0
- package/dist/Collection.d.ts +7 -7
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Collection.js +452 -0
- package/dist/Collection.js.map +1 -0
- package/dist/Controller.cjs +57 -0
- package/dist/Controller.cjs.map +1 -0
- package/dist/Controller.js +57 -0
- package/dist/Controller.js.map +1 -0
- package/dist/EventBus.cjs +84 -0
- package/dist/EventBus.cjs.map +1 -0
- package/dist/EventBus.js +84 -0
- package/dist/EventBus.js.map +1 -0
- package/dist/Model.cjs +175 -0
- package/dist/Model.cjs.map +1 -0
- package/dist/Model.d.ts +4 -4
- package/dist/Model.d.ts.map +1 -1
- package/dist/Model.js +175 -0
- package/dist/Model.js.map +1 -0
- package/dist/PersistentCollection.cjs +285 -0
- package/dist/PersistentCollection.cjs.map +1 -0
- package/dist/PersistentCollection.d.ts +4 -4
- package/dist/PersistentCollection.d.ts.map +1 -1
- package/dist/PersistentCollection.js +285 -0
- package/dist/PersistentCollection.js.map +1 -0
- package/dist/Resource.cjs +308 -0
- package/dist/Resource.cjs.map +1 -0
- package/dist/Resource.d.ts +6 -6
- package/dist/Resource.d.ts.map +1 -1
- package/dist/Resource.js +308 -0
- package/dist/Resource.js.map +1 -0
- package/dist/Service.cjs +51 -0
- package/dist/Service.cjs.map +1 -0
- package/dist/Service.js +51 -0
- package/dist/Service.js.map +1 -0
- package/dist/ViewModel.cjs +582 -0
- package/dist/ViewModel.cjs.map +1 -0
- package/dist/ViewModel.d.ts +3 -9
- package/dist/ViewModel.d.ts.map +1 -1
- package/dist/ViewModel.js +582 -0
- package/dist/ViewModel.js.map +1 -0
- package/dist/errors.cjs +79 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.js +79 -0
- package/dist/errors.js.map +1 -0
- package/dist/mvc-kit.cjs +29 -1
- package/dist/mvc-kit.cjs.map +1 -1
- package/dist/mvc-kit.js +27 -1132
- package/dist/mvc-kit.js.map +1 -1
- package/dist/react/guards.cjs +7 -0
- package/dist/react/guards.cjs.map +1 -0
- package/dist/react/guards.js +7 -0
- package/dist/react/guards.js.map +1 -0
- package/dist/react/provider.cjs +26 -0
- package/dist/react/provider.cjs.map +1 -0
- package/dist/react/provider.js +26 -0
- package/dist/react/provider.js.map +1 -0
- package/dist/react/types.d.ts +1 -1
- package/dist/react/types.d.ts.map +1 -1
- package/dist/react/use-event-bus.cjs +26 -0
- package/dist/react/use-event-bus.cjs.map +1 -0
- package/dist/react/use-event-bus.js +26 -0
- package/dist/react/use-event-bus.js.map +1 -0
- package/dist/react/use-instance.cjs +31 -0
- package/dist/react/use-instance.cjs.map +1 -0
- package/dist/react/use-instance.d.ts +1 -1
- package/dist/react/use-instance.d.ts.map +1 -1
- package/dist/react/use-instance.js +31 -0
- package/dist/react/use-instance.js.map +1 -0
- package/dist/react/use-local.cjs +64 -0
- package/dist/react/use-local.cjs.map +1 -0
- package/dist/react/use-local.d.ts +4 -4
- package/dist/react/use-local.d.ts.map +1 -1
- package/dist/react/use-local.js +64 -0
- package/dist/react/use-local.js.map +1 -0
- package/dist/react/use-model.cjs +80 -0
- package/dist/react/use-model.cjs.map +1 -0
- package/dist/react/use-model.d.ts +1 -1
- package/dist/react/use-model.d.ts.map +1 -1
- package/dist/react/use-model.js +80 -0
- package/dist/react/use-model.js.map +1 -0
- package/dist/react/use-singleton.cjs +21 -0
- package/dist/react/use-singleton.cjs.map +1 -0
- package/dist/react/use-singleton.d.ts +1 -1
- package/dist/react/use-singleton.d.ts.map +1 -1
- package/dist/react/use-singleton.js +21 -0
- package/dist/react/use-singleton.js.map +1 -0
- package/dist/react/use-teardown.cjs +22 -0
- package/dist/react/use-teardown.cjs.map +1 -0
- package/dist/react/use-teardown.js +22 -0
- package/dist/react/use-teardown.js.map +1 -0
- package/dist/react-native/NativeCollection.cjs +76 -0
- package/dist/react-native/NativeCollection.cjs.map +1 -0
- package/dist/react-native/NativeCollection.js +76 -0
- package/dist/react-native/NativeCollection.js.map +1 -0
- package/dist/react-native.cjs +4 -1
- package/dist/react-native.cjs.map +1 -1
- package/dist/react-native.js +2 -60
- package/dist/react-native.js.map +1 -1
- package/dist/react.cjs +19 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +17 -145
- package/dist/react.js.map +1 -1
- package/dist/singleton.cjs +34 -0
- package/dist/singleton.cjs.map +1 -0
- package/dist/singleton.js +34 -0
- package/dist/singleton.js.map +1 -0
- package/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/walkPrototypeChain.cjs +15 -0
- package/dist/walkPrototypeChain.cjs.map +1 -0
- package/dist/walkPrototypeChain.d.ts +9 -0
- package/dist/walkPrototypeChain.d.ts.map +1 -0
- package/dist/walkPrototypeChain.js +15 -0
- package/dist/walkPrototypeChain.js.map +1 -0
- package/dist/web/IndexedDBCollection.cjs +37 -0
- package/dist/web/IndexedDBCollection.cjs.map +1 -0
- package/dist/web/IndexedDBCollection.js +37 -0
- package/dist/web/IndexedDBCollection.js.map +1 -0
- package/dist/web/WebStorageCollection.cjs +85 -0
- package/dist/web/WebStorageCollection.cjs.map +1 -0
- package/dist/web/WebStorageCollection.d.ts +2 -2
- package/dist/web/WebStorageCollection.d.ts.map +1 -1
- package/dist/web/WebStorageCollection.js +85 -0
- package/dist/web/WebStorageCollection.js.map +1 -0
- package/dist/web/idb.cjs +121 -0
- package/dist/web/idb.cjs.map +1 -0
- package/dist/web/idb.js +121 -0
- package/dist/web/idb.js.map +1 -0
- package/dist/web.cjs +6 -1
- package/dist/web.cjs.map +1 -1
- package/dist/web.js +4 -178
- package/dist/web.js.map +1 -1
- package/package.json +4 -2
- package/dist/PersistentCollection-B8kNECDj.cjs +0 -2
- package/dist/PersistentCollection-B8kNECDj.cjs.map +0 -1
- package/dist/PersistentCollection-BFrgskju.js +0 -542
- package/dist/PersistentCollection-BFrgskju.js.map +0 -1
- package/dist/singleton-CaEXSbYg.js +0 -89
- package/dist/singleton-CaEXSbYg.js.map +0 -1
- package/dist/singleton-L-u2W_lX.cjs +0 -2
- package/dist/singleton-L-u2W_lX.cjs.map +0 -1
package/dist/mvc-kit.js
CHANGED
|
@@ -1,1135 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
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
|