mvc-kit 2.2.3 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/agent-config/claude-code/skills/guide/api-reference.md +6 -0
- package/agent-config/copilot/copilot-instructions.md +9 -0
- package/agent-config/cursor/cursorrules +9 -0
- package/dist/Collection.d.ts +22 -0
- package/dist/Collection.d.ts.map +1 -1
- package/dist/mvc-kit.cjs +1 -1
- package/dist/mvc-kit.cjs.map +1 -1
- package/dist/mvc-kit.js +407 -295
- package/dist/mvc-kit.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# mvc-kit
|
|
2
2
|
|
|
3
|
-
<img src="
|
|
3
|
+
<img src="/mvc-kit-logo.jpg" alt="mvc-kit logo" width="300" />
|
|
4
4
|
|
|
5
5
|
Zero-
|
|
6
6
|
|
|
@@ -143,6 +143,12 @@ const rollback = todos.optimistic(() => {
|
|
|
143
143
|
});
|
|
144
144
|
// On failure: rollback() restores pre-update state
|
|
145
145
|
|
|
146
|
+
// Eviction & TTL (opt-in via static overrides)
|
|
147
|
+
class RecentMessages extends Collection<Message> {
|
|
148
|
+
static MAX_SIZE = 500; // FIFO eviction when exceeded
|
|
149
|
+
static TTL = 5 * 60_000; // Auto-expire after 5 minutes
|
|
150
|
+
}
|
|
151
|
+
|
|
146
152
|
// Properties
|
|
147
153
|
todos.items; // readonly T[] (same as state)
|
|
148
154
|
todos.length; // number of items
|
|
@@ -133,6 +133,12 @@ const rollback = collection.optimistic(() => {
|
|
|
133
133
|
// On failure: rollback()
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
+
### Eviction & TTL
|
|
137
|
+
Static overrides for auto-eviction (zero-cost when not configured):
|
|
138
|
+
- `static MAX_SIZE = 0` — FIFO eviction when exceeded. `0` = unlimited.
|
|
139
|
+
- `static TTL = 0` — Time-to-live in ms. `0` = no expiry.
|
|
140
|
+
- `protected onEvict?(items: T[], reason: 'capacity' | 'ttl'): T[] | false | void` — Control eviction. Return subset, `false` to veto, or void.
|
|
141
|
+
|
|
136
142
|
### Lifecycle & Cleanup
|
|
137
143
|
`subscribe()`, `dispose()`, `disposeSignal`, `addCleanup()`, `onDispose()`.
|
|
138
144
|
|
|
@@ -130,6 +130,15 @@ class UserService extends Service {
|
|
|
130
130
|
class UsersCollection extends Collection<UserState> {}
|
|
131
131
|
// Thin subclass. Query logic in ViewModel getters.
|
|
132
132
|
// Use reset() for full loads, upsert() for paginated/incremental loads.
|
|
133
|
+
|
|
134
|
+
// Optional eviction (zero-cost when not configured):
|
|
135
|
+
class MessagesCollection extends Collection<Message> {
|
|
136
|
+
static MAX_SIZE = 500; // FIFO eviction when exceeded
|
|
137
|
+
static TTL = 5 * 60_000; // Auto-expire after 5 min
|
|
138
|
+
protected onEvict(items: Message[], reason: 'capacity' | 'ttl') {
|
|
139
|
+
return items.filter(m => !m.pinned); // keep pinned
|
|
140
|
+
}
|
|
141
|
+
}
|
|
133
142
|
```
|
|
134
143
|
|
|
135
144
|
## Model Pattern
|
|
@@ -130,6 +130,15 @@ class UserService extends Service {
|
|
|
130
130
|
class UsersCollection extends Collection<UserState> {}
|
|
131
131
|
// Thin subclass. Query logic in ViewModel getters.
|
|
132
132
|
// Use reset() for full loads, upsert() for paginated/incremental loads.
|
|
133
|
+
|
|
134
|
+
// Optional eviction (zero-cost when not configured):
|
|
135
|
+
class MessagesCollection extends Collection<Message> {
|
|
136
|
+
static MAX_SIZE = 500; // FIFO eviction when exceeded
|
|
137
|
+
static TTL = 5 * 60_000; // Auto-expire after 5 min
|
|
138
|
+
protected onEvict(items: Message[], reason: 'capacity' | 'ttl') {
|
|
139
|
+
return items.filter(m => !m.pinned); // keep pinned
|
|
140
|
+
}
|
|
141
|
+
}
|
|
133
142
|
```
|
|
134
143
|
|
|
135
144
|
## Model Pattern
|
package/dist/Collection.d.ts
CHANGED
|
@@ -7,12 +7,18 @@ type CollectionListener<T> = Listener<CollectionState<T>>;
|
|
|
7
7
|
export declare class Collection<T extends {
|
|
8
8
|
id: string | number;
|
|
9
9
|
}> implements Subscribable<CollectionState<T>> {
|
|
10
|
+
/** Maximum number of items before FIFO eviction. 0 = unlimited. */
|
|
11
|
+
static MAX_SIZE: number;
|
|
12
|
+
/** Time-to-live in milliseconds. 0 = no expiry. */
|
|
13
|
+
static TTL: number;
|
|
10
14
|
private _items;
|
|
11
15
|
private _disposed;
|
|
12
16
|
private _listeners;
|
|
13
17
|
private _index;
|
|
14
18
|
private _abortController;
|
|
15
19
|
private _cleanups;
|
|
20
|
+
private _timestamps;
|
|
21
|
+
private _evictionTimer;
|
|
16
22
|
constructor(initialItems?: T[]);
|
|
17
23
|
/**
|
|
18
24
|
* Alias for Subscribable compatibility.
|
|
@@ -26,6 +32,8 @@ export declare class Collection<T extends {
|
|
|
26
32
|
get disposed(): boolean;
|
|
27
33
|
/** AbortSignal that fires when this instance is disposed. Lazily created. */
|
|
28
34
|
get disposeSignal(): AbortSignal;
|
|
35
|
+
private get _maxSize();
|
|
36
|
+
private get _ttl();
|
|
29
37
|
/**
|
|
30
38
|
* Add one or more items. Items with existing IDs are silently skipped.
|
|
31
39
|
*/
|
|
@@ -90,8 +98,22 @@ export declare class Collection<T extends {
|
|
|
90
98
|
protected addCleanup(fn: () => void): void;
|
|
91
99
|
/** Lifecycle hook called during dispose(). Override for custom teardown. @protected */
|
|
92
100
|
protected onDispose?(): void;
|
|
101
|
+
/**
|
|
102
|
+
* Called before items are auto-evicted by capacity or TTL.
|
|
103
|
+
* Override to filter which items get evicted, or veto entirely.
|
|
104
|
+
*
|
|
105
|
+
* @param items - Candidates for eviction
|
|
106
|
+
* @param reason - Why eviction is happening
|
|
107
|
+
* @returns void to proceed with all, false to veto, or T[] subset to evict only those
|
|
108
|
+
*/
|
|
109
|
+
protected onEvict?(items: T[], reason: 'capacity' | 'ttl'): T[] | false | void;
|
|
93
110
|
private notify;
|
|
94
111
|
private rebuildIndex;
|
|
112
|
+
private _evictForCapacity;
|
|
113
|
+
private _applyOnEvict;
|
|
114
|
+
private _sweepExpired;
|
|
115
|
+
private _scheduleEvictionTimer;
|
|
116
|
+
private _clearEvictionTimer;
|
|
95
117
|
}
|
|
96
118
|
export {};
|
|
97
119
|
//# sourceMappingURL=Collection.d.ts.map
|
package/dist/Collection.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Collection.d.ts","sourceRoot":"","sources":["../src/Collection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"Collection.d.ts","sourceRoot":"","sources":["../src/Collection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAItD,KAAK,eAAe,CAAC,CAAC,IAAI,SAAS,CAAC,EAAE,CAAC;AACvC,KAAK,kBAAkB,CAAC,CAAC,IAAI,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AAE1D;;GAEG;AACH,qBAAa,UAAU,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAE,YAAW,YAAY,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACpG,mEAAmE;IACnE,MAAM,CAAC,QAAQ,SAAK;IACpB,mDAAmD;IACnD,MAAM,CAAC,GAAG,SAAK;IAEf,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,WAAW,CAAqC;IACxD,OAAO,CAAC,cAAc,CAA8C;gBAExD,YAAY,GAAE,CAAC,EAAO;IA0BlC;;OAEG;IACH,IAAI,KAAK,IAAI,SAAS,CAAC,EAAE,CAExB;IAED,uCAAuC;IACvC,IAAI,KAAK,IAAI,SAAS,CAAC,EAAE,CAExB;IAED,yCAAyC;IACzC,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,+CAA+C;IAC/C,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,6EAA6E;IAC7E,IAAI,aAAa,IAAI,WAAW,CAK/B;IAID,OAAO,KAAK,QAAQ,GAEnB;IAED,OAAO,KAAK,IAAI,GAEf;IAID;;OAEG;IACH,GAAG,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI;IA4CxB;;;;;OAKG;IACH,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI;IA8D3B;;OAEG;IACH,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI;IA4B/B;;OAEG;IACH,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IA6B9C;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI;IA8BvB;;OAEG;IACH,KAAK,IAAI,IAAI;IAkBb;;;OAGG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IA2B5C;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,SAAS;IAI/B;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO;IAIzB;;OAEG;IACH,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,GAAG,CAAC,GAAG,SAAS;IAIpD;;OAEG;IACH,MAAM,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,GAAG,SAAS,CAAC,EAAE;IAIrD;;OAEG;IACH,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC,EAAE;IAIvD;;OAEG;IACH,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC,EAAE;IAMxC,oEAAoE;IACpE,SAAS,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAYtD,0EAA0E;IAC1E,OAAO,IAAI,IAAI;IAkBf,uEAAuE;IACvE,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAO1C,uFAAuF;IACvF,SAAS,CAAC,SAAS,CAAC,IAAI,IAAI;IAE5B;;;;;;;OAOG;IACH,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,GAAG,KAAK,GAAG,CAAC,EAAE,GAAG,KAAK,GAAG,IAAI;IAE9E,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,iBAAiB;IAuBzB,OAAO,CAAC,aAAa;IAyBrB,OAAO,CAAC,aAAa;IA8CrB,OAAO,CAAC,sBAAsB;IAiB9B,OAAO,CAAC,mBAAmB;CAM5B"}
|
package/dist/mvc-kit.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const b=require("./singleton-L-u2W_lX.cjs");class y extends Error{constructor(t,e){super(e??`HTTP ${t}`),this.status=t,this.name="HttpError"}}function v(n){return n instanceof Error&&n.name==="AbortError"}function S(n){return n===401?"unauthorized":n===403?"forbidden":n===404?"not_found":n===422?"validation":n===429?"rate_limited":n>=500?"server_error":"unknown"}function O(n){return typeof n=="object"&&n!==null&&typeof n.status=="number"&&typeof n.statusText=="string"&&!(n instanceof Error)}function T(n){return n instanceof Error&&n.name==="AbortError"?{code:"abort",message:"Request was aborted",original:n}:n instanceof y?{code:S(n.status),message:n.message,status:n.status,original:n}:O(n)?{code:S(n.status),message:n.statusText||`HTTP ${n.status}`,status:n.status,original:n}:n instanceof TypeError&&n.message.toLowerCase().includes("fetch")?{code:"network",message:n.message,original:n}:n instanceof Error&&n.name==="TimeoutError"?{code:"timeout",message:n.message,original:n}:n instanceof Error?{code:"unknown",message:n.message,original:n}:{code:"unknown",message:String(n),original:n}}const u=typeof __MVC_KIT_DEV__<"u"&&__MVC_KIT_DEV__;function E(n){return n!==null&&typeof n=="object"&&typeof n.subscribe=="function"}function m(n,t,e){let s=Object.getPrototypeOf(n);for(;s&&s!==t;){const i=Object.getOwnPropertyDescriptors(s);for(const[r,c]of Object.entries(i))r!=="constructor"&&e(r,c,s);s=Object.getPrototypeOf(s)}}const A=Object.freeze({loading:!1,error:null,errorCode:null}),w=["async","subscribeAsync"],z=new Set(["onInit","onSet","onDispose"]);class g{_state;_initialState;_disposed=!1;_initialized=!1;_listeners=new Set;_abortController=null;_cleanups=null;_subscriptionCleanups=null;_eventBus=null;_revision=0;_stateTracking=null;_sourceTracking=null;_trackedSources=new Map;_asyncStates=new Map;_asyncSnapshots=new Map;_asyncListeners=new Set;_asyncProxy=null;_activeOps=null;static GHOST_TIMEOUT=3e3;constructor(t){this._state=Object.freeze({...t}),this._initialState=this._state,this._guardReservedKeys()}get state(){return this._state}get disposed(){return this._disposed}get initialized(){return this._initialized}get disposeSignal(){return this._abortController||(this._abortController=new AbortController),this._abortController.signal}get events(){return this._eventBus||(this._eventBus=new b.EventBus),this._eventBus}init(){if(!(this._initialized||this._disposed))return this._initialized=!0,this._trackSubscribables(),this._installStateProxy(),this._memoizeGetters(),this._wrapMethods(),this.onInit?.()}set(t){if(this._disposed)return;if(u&&this._stateTracking){console.error("[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.");return}const e=typeof t=="function"?t(this._state):t;if(!Object.keys(e).some(a=>e[a]!==this._state[a]))return;const r=this._state,c=Object.freeze({...r,...e});this._state=c,this._revision++,this.onSet?.(r,c);for(const a of this._listeners)a(c,r)}emit(t,e){(this._eventBus?.disposed??this._disposed)||this.events.emit(t,e)}subscribe(t){return this._disposed?()=>{}:(this._listeners.add(t),()=>{this._listeners.delete(t)})}dispose(){if(!this._disposed){if(this._disposed=!0,this._teardownSubscriptions(),this._abortController?.abort(),this._cleanups){for(const t of this._cleanups)t();this._cleanups=null}this._eventBus?.dispose(),this.onDispose?.(),this._listeners.clear()}}reset(t){if(!this._disposed){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();for(const e of this._listeners)e(this._state,this._state);return this.onInit?.()}}addCleanup(t){this._cleanups||(this._cleanups=[]),this._cleanups.push(t)}subscribeTo(t,e){const s=t.subscribe(e);return this._subscriptionCleanups||(this._subscriptionCleanups=[]),this._subscriptionCleanups.push(s),s}get async(){if(!this._asyncProxy){const t=this;this._asyncProxy=new Proxy({},{get(e,s){return t._asyncSnapshots.get(s)??A},has(e,s){return t._asyncSnapshots.has(s)},ownKeys(){return Array.from(t._asyncSnapshots.keys())},getOwnPropertyDescriptor(e,s){if(t._asyncSnapshots.has(s))return{configurable:!0,enumerable:!0,value:t._asyncSnapshots.get(s)}}})}return this._asyncProxy}subscribeAsync(t){return this._disposed?()=>{}:(this._asyncListeners.add(t),()=>{this._asyncListeners.delete(t)})}_notifyAsync(){for(const t of this._asyncListeners)t()}_teardownSubscriptions(){for(const t of this._trackedSources.values())t.unsubscribe();if(this._trackedSources.clear(),this._subscriptionCleanups){for(const t of this._subscriptionCleanups)t();this._subscriptionCleanups=null}}_guardReservedKeys(){m(this,g.prototype,t=>{if(w.includes(t))throw new Error(`[mvc-kit] "${t}" is a reserved property on ViewModel and cannot be overridden.`)})}_wrapMethods(){for(const i of w)if(Object.getOwnPropertyDescriptor(this,i)?.value!==void 0)throw new Error(`[mvc-kit] "${i}" is a reserved property on ViewModel and cannot be overridden.`);const t=this,e=new Set,s=[];u&&(this._activeOps=new Map),m(this,g.prototype,(i,r)=>{if(r.get||r.set||typeof r.value!="function"||i.startsWith("_")||z.has(i)||e.has(i))return;e.add(i);const c=r.value;let a=!1;const l=function(...f){if(t._disposed){u&&console.warn(`[mvc-kit] "${i}" called after dispose — ignored.`);return}u&&!t._initialized&&console.warn(`[mvc-kit] "${i}" called before init(). Async tracking is active only after init().`);let _;try{_=c.apply(t,f)}catch(h){throw h}if(!_||typeof _.then!="function")return a||(a=!0,t._asyncStates.delete(i),t._asyncSnapshots.delete(i),t[i]=c.bind(t)),_;let o=t._asyncStates.get(i);return o||(o={loading:!1,error:null,errorCode:null,count:0},t._asyncStates.set(i,o)),o.count++,o.loading=!0,o.error=null,o.errorCode=null,t._asyncSnapshots.set(i,Object.freeze({loading:!0,error:null,errorCode:null})),t._notifyAsync(),u&&t._activeOps&&t._activeOps.set(i,(t._activeOps.get(i)??0)+1),_.then(h=>{if(t._disposed)return h;if(o.count--,o.loading=o.count>0,t._asyncSnapshots.set(i,Object.freeze({loading:o.loading,error:o.error,errorCode:o.errorCode})),t._notifyAsync(),u&&t._activeOps){const d=(t._activeOps.get(i)??1)-1;d<=0?t._activeOps.delete(i):t._activeOps.set(i,d)}return h},h=>{if(v(h)){if(t._disposed||(o.count--,o.loading=o.count>0,t._asyncSnapshots.set(i,Object.freeze({loading:o.loading,error:o.error,errorCode:o.errorCode})),t._notifyAsync()),u&&t._activeOps){const p=(t._activeOps.get(i)??1)-1;p<=0?t._activeOps.delete(i):t._activeOps.set(i,p)}return}if(t._disposed)return;o.count--,o.loading=o.count>0;const d=T(h);if(o.error=d.message,o.errorCode=d.code,t._asyncSnapshots.set(i,Object.freeze({loading:o.loading,error:d.message,errorCode:d.code})),t._notifyAsync(),u&&t._activeOps){const p=(t._activeOps.get(i)??1)-1;p<=0?t._activeOps.delete(i):t._activeOps.set(i,p)}throw h})};s.push(i),t[i]=l}),s.length>0&&this.addCleanup(()=>{const i=u&&t._activeOps?new Map(t._activeOps):null;for(const r of s)u?t[r]=()=>{console.warn(`[mvc-kit] "${r}" called after dispose — ignored.`)}:t[r]=()=>{};t._asyncListeners.clear(),t._asyncStates.clear(),t._asyncSnapshots.clear(),u&&i&&i.size>0&&t._scheduleGhostCheck(i)})}_scheduleGhostCheck(t){u&&setTimeout(()=>{for(const[e,s]of t)console.warn(`[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.`)},this.constructor.GHOST_TIMEOUT)}_installStateProxy(){const t=new Proxy({},{get:(e,s)=>(this._stateTracking?.add(s),this._state[s]),ownKeys:()=>Reflect.ownKeys(this._state),getOwnPropertyDescriptor:(e,s)=>Reflect.getOwnPropertyDescriptor(this._state,s),set:()=>{throw new Error("Cannot mutate state directly. Use set() instead.")},has:(e,s)=>s in this._state});Object.defineProperty(this,"state",{get:()=>this._stateTracking?t:this._state,configurable:!0,enumerable:!0})}_trackSubscribables(){for(const t of Object.getOwnPropertyNames(this)){const e=this[t];if(!E(e))continue;const s={source:e,revision:0,unsubscribe:e.subscribe(()=>{if(!this._disposed){s.revision++,this._revision++,this._state=Object.freeze({...this._state});for(const i of this._listeners)i(this._state,this._state)}})};this._trackedSources.set(t,s),Object.defineProperty(this,t,{get:()=>(this._sourceTracking?.set(t,s),e),configurable:!0,enumerable:!1})}}_memoizeGetters(){const t=new Set;m(this,g.prototype,(e,s)=>{!s.get||t.has(e)||(t.add(e),this._wrapGetter(e,s.get))})}_wrapGetter(t,e){let s,i=-1,r,c,a;Object.defineProperty(this,t,{get:()=>{if(this._disposed||i===this._revision)return s;if(r&&c){let o=!0;for(const[h,d]of c)if(this._state[h]!==d){o=!1;break}if(o&&a)for(const[h,d]of a){const p=this._trackedSources.get(h);if(p&&p.revision!==d){o=!1;break}}if(o)return i=this._revision,s}const l=this._stateTracking,f=this._sourceTracking;this._stateTracking=new Set,this._sourceTracking=new Map;try{s=e.call(this)}catch(o){throw this._stateTracking=l,this._sourceTracking=f,o}r=this._stateTracking;const _=this._sourceTracking;if(this._stateTracking=l,this._sourceTracking=f,l)for(const o of r)l.add(o);if(f)for(const[o,h]of _)f.set(o,h);c=new Map;for(const o of r)c.set(o,this._state[o]);a=new Map;for(const[o,h]of _)a.set(o,h.revision);return i=this._revision,s},configurable:!0,enumerable:!0})}}class k{_state;_committed;_disposed=!1;_initialized=!1;_listeners=new Set;_abortController=null;_cleanups=null;constructor(t){const e=Object.freeze({...t});this._state=e,this._committed=e}get state(){return this._state}get committed(){return this._committed}get dirty(){return!this.shallowEqual(this._state,this._committed)}get errors(){return this.validate(this._state)}get valid(){return Object.keys(this.errors).length===0}get disposed(){return this._disposed}get initialized(){return this._initialized}get disposeSignal(){return this._abortController||(this._abortController=new AbortController),this._abortController.signal}init(){if(!(this._initialized||this._disposed))return this._initialized=!0,this.onInit?.()}set(t){if(this._disposed)throw new Error("Cannot set state on disposed Model");const e=typeof t=="function"?t(this._state):t;if(!Object.keys(e).some(a=>e[a]!==this._state[a]))return;const r=this._state,c=Object.freeze({...r,...e});this._state=c,this.onSet?.(r,c);for(const a of this._listeners)a(c,r)}commit(){if(this._disposed)throw new Error("Cannot commit on disposed Model");this._committed=this._state}rollback(){if(this._disposed)throw new Error("Cannot rollback on disposed Model");if(this.shallowEqual(this._state,this._committed))return;const t=this._state;this._state=this._committed,this.onSet?.(t,this._state);for(const e of this._listeners)e(this._state,t)}subscribe(t){return this._disposed?()=>{}:(this._listeners.add(t),()=>{this._listeners.delete(t)})}dispose(){if(!this._disposed){if(this._disposed=!0,this._abortController?.abort(),this._cleanups){for(const t of this._cleanups)t();this._cleanups=null}this.onDispose?.(),this._listeners.clear()}}validate(t){return{}}addCleanup(t){this._cleanups||(this._cleanups=[]),this._cleanups.push(t)}subscribeTo(t,e){const s=t.subscribe(e);return this.addCleanup(s),s}shallowEqual(t,e){const s=Object.keys(t),i=Object.keys(e);if(s.length!==i.length)return!1;for(const r of s)if(t[r]!==e[r])return!1;return!0}}class M{_items=[];_disposed=!1;_listeners=new Set;_index=new Map;_abortController=null;_cleanups=null;constructor(t=[]){this._items=Object.freeze([...t]),this.rebuildIndex()}get state(){return this._items}get items(){return this._items}get length(){return this._items.length}get disposed(){return this._disposed}get disposeSignal(){return this._abortController||(this._abortController=new AbortController),this._abortController.signal}add(...t){if(this._disposed)throw new Error("Cannot add to disposed Collection");if(t.length===0)return;const e=new Set,s=[];for(const r of t)!this._index.has(r.id)&&!e.has(r.id)&&(s.push(r),e.add(r.id));if(s.length===0)return;const i=this._items;this._items=Object.freeze([...i,...s]);for(const r of s)this._index.set(r.id,r);this.notify(i)}upsert(...t){if(this._disposed)throw new Error("Cannot upsert on disposed Collection");if(t.length===0)return;const e=new Map;for(const a of t)e.set(a.id,a);const s=this._items;let i=!1;const r=new Set,c=[];for(const a of s)if(e.has(a.id)){const l=e.get(a.id);l!==a&&(i=!0),c.push(l),r.add(a.id)}else c.push(a);for(const[a,l]of e)r.has(a)||(c.push(l),i=!0);if(i){this._items=Object.freeze(c);for(const[a,l]of e)this._index.set(a,l);this.notify(s)}}remove(...t){if(this._disposed)throw new Error("Cannot remove from disposed Collection");if(t.length===0)return;const e=new Set(t),s=this._items.filter(r=>!e.has(r.id));if(s.length===this._items.length)return;const i=this._items;this._items=Object.freeze(s);for(const r of t)this._index.delete(r);this.notify(i)}update(t,e){if(this._disposed)throw new Error("Cannot update disposed Collection");const s=this._items.findIndex(_=>_.id===t);if(s===-1)return;const i=this._items[s],r={...i,...e,id:t};if(!Object.keys(e).some(_=>e[_]!==i[_]))return;const l=this._items,f=[...l];f[s]=r,this._items=Object.freeze(f),this._index.set(t,r),this.notify(l)}reset(t){if(this._disposed)throw new Error("Cannot reset disposed Collection");const e=this._items;this._items=Object.freeze([...t]),this.rebuildIndex(),this.notify(e)}clear(){if(this._disposed)throw new Error("Cannot clear disposed Collection");if(this._items.length===0)return;const t=this._items;this._items=Object.freeze([]),this._index.clear(),this.notify(t)}optimistic(t){if(this._disposed)throw new Error("Cannot perform optimistic update on disposed Collection");const e=this._items;t();let s=!1;return()=>{if(s||this._disposed)return;s=!0;const i=this._items;this._items=e,this.rebuildIndex(),this.notify(i)}}get(t){return this._index.get(t)}has(t){return this._index.has(t)}find(t){return this._items.find(t)}filter(t){return this._items.filter(t)}sorted(t){return[...this._items].sort(t)}map(t){return this._items.map(t)}subscribe(t){return this._disposed?()=>{}:(this._listeners.add(t),()=>{this._listeners.delete(t)})}dispose(){if(!this._disposed){if(this._disposed=!0,this._abortController?.abort(),this._cleanups){for(const t of this._cleanups)t();this._cleanups=null}this.onDispose?.(),this._listeners.clear(),this._index.clear()}}addCleanup(t){this._cleanups||(this._cleanups=[]),this._cleanups.push(t)}notify(t){for(const e of this._listeners)e(this._items,t)}rebuildIndex(){this._index.clear();for(const t of this._items)this._index.set(t.id,t)}}class j{_disposed=!1;_initialized=!1;_abortController=null;_cleanups=null;get disposed(){return this._disposed}get initialized(){return this._initialized}get disposeSignal(){return this._abortController||(this._abortController=new AbortController),this._abortController.signal}init(){if(!(this._initialized||this._disposed))return this._initialized=!0,this.onInit?.()}dispose(){if(!this._disposed){if(this._disposed=!0,this._abortController?.abort(),this._cleanups){for(const t of this._cleanups)t();this._cleanups=null}this.onDispose?.()}}addCleanup(t){this._cleanups||(this._cleanups=[]),this._cleanups.push(t)}subscribeTo(t,e){const s=t.subscribe(e);return this.addCleanup(s),s}}class x{_disposed=!1;_initialized=!1;_abortController=null;_cleanups=null;get disposed(){return this._disposed}get initialized(){return this._initialized}get disposeSignal(){return this._abortController||(this._abortController=new AbortController),this._abortController.signal}init(){if(!(this._initialized||this._disposed))return this._initialized=!0,this.onInit?.()}dispose(){if(!this._disposed){if(this._disposed=!0,this._abortController?.abort(),this._cleanups){for(const t of this._cleanups)t();this._cleanups=null}this.onDispose?.()}}addCleanup(t){this._cleanups||(this._cleanups=[]),this._cleanups.push(t)}}const C=typeof __MVC_KIT_DEV__<"u"&&__MVC_KIT_DEV__,P=Object.freeze({connected:!1,reconnecting:!1,attempt:0,error:null});class D{static RECONNECT_BASE=1e3;static RECONNECT_MAX=3e4;static RECONNECT_FACTOR=2;static MAX_ATTEMPTS=1/0;_status=P;_connState=0;_disposed=!1;_initialized=!1;_listeners=new Set;_handlers=new Map;_abortController=null;_connectAbort=null;_reconnectTimer=null;_cleanups=null;get state(){return this._status}subscribe(t){return this._disposed?()=>{}:(this._listeners.add(t),()=>{this._listeners.delete(t)})}get disposed(){return this._disposed}get initialized(){return this._initialized}get disposeSignal(){return this._abortController||(this._abortController=new AbortController),this._abortController.signal}init(){if(!(this._initialized||this._disposed))return this._initialized=!0,this.onInit?.()}dispose(){if(!this._disposed){this._disposed=!0,this._connState=4,this._reconnectTimer!==null&&(clearTimeout(this._reconnectTimer),this._reconnectTimer=null),this._connectAbort?.abort(),this._connectAbort=null,this._abortController?.abort();try{this.close()}catch{}if(this._cleanups){for(const t of this._cleanups)t();this._cleanups=null}this.onDispose?.(),this._listeners.clear(),this._handlers.clear()}}connect(){if(this._disposed){C&&console.warn("[mvc-kit] connect() called after dispose — ignored.");return}C&&!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))}disconnect(){if(!this._disposed){if(this._reconnectTimer!==null&&(clearTimeout(this._reconnectTimer),this._reconnectTimer=null),this._connectAbort?.abort(),this._connectAbort=null,this._connState===2||this._connState===1){this._connState=0;try{this.close()}catch{}}else this._connState=0;this._setStatus({connected:!1,reconnecting:!1,attempt:0,error:null})}}receive(t,e){if(this._disposed){C&&console.warn(`[mvc-kit] receive("${String(t)}") called after dispose — ignored.`);return}const s=this._handlers.get(t);if(s)for(const i of s)i(e)}disconnected(){this._disposed||this._connState!==2&&this._connState!==1||(this._connectAbort?.abort(),this._connectAbort=null,this._connState=3,this._scheduleReconnect(1))}on(t,e){if(this._disposed)return()=>{};let s=this._handlers.get(t);return s||(s=new Set,this._handlers.set(t,s)),s.add(e),()=>{s.delete(e)}}once(t,e){const s=this.on(t,i=>{s(),e(i)});return s}addCleanup(t){this._cleanups||(this._cleanups=[]),this._cleanups.push(t)}subscribeTo(t,e){const s=t.subscribe(e);return this.addCleanup(s),s}_calculateDelay(t){const e=this.constructor,s=Math.min(e.RECONNECT_BASE*Math.pow(e.RECONNECT_FACTOR,t),e.RECONNECT_MAX);return Math.random()*s}_setStatus(t){const e=this._status;if(!(e.connected===t.connected&&e.reconnecting===t.reconnecting&&e.attempt===t.attempt&&e.error===t.error)){this._status=Object.freeze(t);for(const s of this._listeners)s(this._status,e)}}_attemptConnect(t){if(this._disposed)return;this._connState=1,this._connectAbort?.abort(),this._connectAbort=new AbortController;const e=this._abortController?AbortSignal.any([this._abortController.signal,this._connectAbort.signal]):this._connectAbort.signal;this._setStatus({connected:!1,reconnecting:t>0,attempt:t,error:null});let s;try{s=this.open(e)}catch(i){this._onOpenFailed(t,i);return}s&&typeof s.then=="function"?s.then(()=>this._onOpenSucceeded(),i=>this._onOpenFailed(t,i)):this._onOpenSucceeded()}_onOpenSucceeded(){this._disposed||this._connState===1&&(this._connState=2,this._setStatus({connected:!0,reconnecting:!1,attempt:0,error:null}))}_onOpenFailed(t,e){this._disposed||this._connState!==0&&(this._connectAbort?.abort(),this._connectAbort=null,this._connState=3,this._scheduleReconnect(t+1,e))}_scheduleReconnect(t,e){const s=this.constructor;if(t>s.MAX_ATTEMPTS){this._connState=0,this._setStatus({connected:!1,reconnecting:!1,attempt:t,error:e instanceof Error?e.message:"Max reconnection attempts reached"});return}const i=e instanceof Error?e.message:e?String(e):null;this._setStatus({connected:!1,reconnecting:!0,attempt:t,error:i});const r=this._calculateDelay(t-1);this._reconnectTimer=setTimeout(()=>{this._reconnectTimer=null,this._attemptConnect(t)},r)}}exports.EventBus=b.EventBus;exports.hasSingleton=b.hasSingleton;exports.singleton=b.singleton;exports.teardown=b.teardown;exports.teardownAll=b.teardownAll;exports.Channel=D;exports.Collection=M;exports.Controller=j;exports.HttpError=y;exports.Model=k;exports.Service=x;exports.ViewModel=g;exports.classifyError=T;exports.isAbortError=v;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const m=require("./singleton-L-u2W_lX.cjs");class C extends Error{constructor(t,s){super(s??`HTTP ${t}`),this.status=t,this.name="HttpError"}}function y(r){return r instanceof Error&&r.name==="AbortError"}function v(r){return r===401?"unauthorized":r===403?"forbidden":r===404?"not_found":r===422?"validation":r===429?"rate_limited":r>=500?"server_error":"unknown"}function E(r){return typeof r=="object"&&r!==null&&typeof r.status=="number"&&typeof r.statusText=="string"&&!(r instanceof Error)}function T(r){return r instanceof Error&&r.name==="AbortError"?{code:"abort",message:"Request was aborted",original:r}:r instanceof C?{code:v(r.status),message:r.message,status:r.status,original:r}:E(r)?{code:v(r.status),message:r.statusText||`HTTP ${r.status}`,status:r.status,original:r}:r instanceof TypeError&&r.message.toLowerCase().includes("fetch")?{code:"network",message:r.message,original:r}:r instanceof Error&&r.name==="TimeoutError"?{code:"timeout",message:r.message,original:r}:r instanceof Error?{code:"unknown",message:r.message,original:r}:{code:"unknown",message:String(r),original:r}}const d=typeof __MVC_KIT_DEV__<"u"&&__MVC_KIT_DEV__;function O(r){return r!==null&&typeof r=="object"&&typeof r.subscribe=="function"}function g(r,t,s){let e=Object.getPrototypeOf(r);for(;e&&e!==t;){const i=Object.getOwnPropertyDescriptors(e);for(const[n,a]of Object.entries(i))n!=="constructor"&&s(n,a,e);e=Object.getPrototypeOf(e)}}const z=Object.freeze({loading:!1,error:null,errorCode:null}),w=["async","subscribeAsync"],A=new Set(["onInit","onSet","onDispose"]);class b{_state;_initialState;_disposed=!1;_initialized=!1;_listeners=new Set;_abortController=null;_cleanups=null;_subscriptionCleanups=null;_eventBus=null;_revision=0;_stateTracking=null;_sourceTracking=null;_trackedSources=new Map;_asyncStates=new Map;_asyncSnapshots=new Map;_asyncListeners=new Set;_asyncProxy=null;_activeOps=null;static GHOST_TIMEOUT=3e3;constructor(t){this._state=Object.freeze({...t}),this._initialState=this._state,this._guardReservedKeys()}get state(){return this._state}get disposed(){return this._disposed}get initialized(){return this._initialized}get disposeSignal(){return this._abortController||(this._abortController=new AbortController),this._abortController.signal}get events(){return this._eventBus||(this._eventBus=new m.EventBus),this._eventBus}init(){if(!(this._initialized||this._disposed))return this._initialized=!0,this._trackSubscribables(),this._installStateProxy(),this._memoizeGetters(),this._wrapMethods(),this.onInit?.()}set(t){if(this._disposed)return;if(d&&this._stateTracking){console.error("[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.");return}const s=typeof t=="function"?t(this._state):t;if(!Object.keys(s).some(o=>s[o]!==this._state[o]))return;const n=this._state,a=Object.freeze({...n,...s});this._state=a,this._revision++,this.onSet?.(n,a);for(const o of this._listeners)o(a,n)}emit(t,s){(this._eventBus?.disposed??this._disposed)||this.events.emit(t,s)}subscribe(t){return this._disposed?()=>{}:(this._listeners.add(t),()=>{this._listeners.delete(t)})}dispose(){if(!this._disposed){if(this._disposed=!0,this._teardownSubscriptions(),this._abortController?.abort(),this._cleanups){for(const t of this._cleanups)t();this._cleanups=null}this._eventBus?.dispose(),this.onDispose?.(),this._listeners.clear()}}reset(t){if(!this._disposed){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();for(const s of this._listeners)s(this._state,this._state);return this.onInit?.()}}addCleanup(t){this._cleanups||(this._cleanups=[]),this._cleanups.push(t)}subscribeTo(t,s){const e=t.subscribe(s);return this._subscriptionCleanups||(this._subscriptionCleanups=[]),this._subscriptionCleanups.push(e),e}get async(){if(!this._asyncProxy){const t=this;this._asyncProxy=new Proxy({},{get(s,e){return t._asyncSnapshots.get(e)??z},has(s,e){return t._asyncSnapshots.has(e)},ownKeys(){return Array.from(t._asyncSnapshots.keys())},getOwnPropertyDescriptor(s,e){if(t._asyncSnapshots.has(e))return{configurable:!0,enumerable:!0,value:t._asyncSnapshots.get(e)}}})}return this._asyncProxy}subscribeAsync(t){return this._disposed?()=>{}:(this._asyncListeners.add(t),()=>{this._asyncListeners.delete(t)})}_notifyAsync(){for(const t of this._asyncListeners)t()}_teardownSubscriptions(){for(const t of this._trackedSources.values())t.unsubscribe();if(this._trackedSources.clear(),this._subscriptionCleanups){for(const t of this._subscriptionCleanups)t();this._subscriptionCleanups=null}}_guardReservedKeys(){g(this,b.prototype,t=>{if(w.includes(t))throw new Error(`[mvc-kit] "${t}" is a reserved property on ViewModel and cannot be overridden.`)})}_wrapMethods(){for(const i of w)if(Object.getOwnPropertyDescriptor(this,i)?.value!==void 0)throw new Error(`[mvc-kit] "${i}" is a reserved property on ViewModel and cannot be overridden.`);const t=this,s=new Set,e=[];d&&(this._activeOps=new Map),g(this,b.prototype,(i,n)=>{if(n.get||n.set||typeof n.value!="function"||i.startsWith("_")||A.has(i)||s.has(i))return;s.add(i);const a=n.value;let o=!1;const l=function(...h){if(t._disposed){d&&console.warn(`[mvc-kit] "${i}" called after dispose — ignored.`);return}d&&!t._initialized&&console.warn(`[mvc-kit] "${i}" called before init(). Async tracking is active only after init().`);let u;try{u=a.apply(t,h)}catch(_){throw _}if(!u||typeof u.then!="function")return o||(o=!0,t._asyncStates.delete(i),t._asyncSnapshots.delete(i),t[i]=a.bind(t)),u;let c=t._asyncStates.get(i);return c||(c={loading:!1,error:null,errorCode:null,count:0},t._asyncStates.set(i,c)),c.count++,c.loading=!0,c.error=null,c.errorCode=null,t._asyncSnapshots.set(i,Object.freeze({loading:!0,error:null,errorCode:null})),t._notifyAsync(),d&&t._activeOps&&t._activeOps.set(i,(t._activeOps.get(i)??0)+1),u.then(_=>{if(t._disposed)return _;if(c.count--,c.loading=c.count>0,t._asyncSnapshots.set(i,Object.freeze({loading:c.loading,error:c.error,errorCode:c.errorCode})),t._notifyAsync(),d&&t._activeOps){const f=(t._activeOps.get(i)??1)-1;f<=0?t._activeOps.delete(i):t._activeOps.set(i,f)}return _},_=>{if(y(_)){if(t._disposed||(c.count--,c.loading=c.count>0,t._asyncSnapshots.set(i,Object.freeze({loading:c.loading,error:c.error,errorCode:c.errorCode})),t._notifyAsync()),d&&t._activeOps){const p=(t._activeOps.get(i)??1)-1;p<=0?t._activeOps.delete(i):t._activeOps.set(i,p)}return}if(t._disposed)return;c.count--,c.loading=c.count>0;const f=T(_);if(c.error=f.message,c.errorCode=f.code,t._asyncSnapshots.set(i,Object.freeze({loading:c.loading,error:f.message,errorCode:f.code})),t._notifyAsync(),d&&t._activeOps){const p=(t._activeOps.get(i)??1)-1;p<=0?t._activeOps.delete(i):t._activeOps.set(i,p)}throw _})};e.push(i),t[i]=l}),e.length>0&&this.addCleanup(()=>{const i=d&&t._activeOps?new Map(t._activeOps):null;for(const n of e)d?t[n]=()=>{console.warn(`[mvc-kit] "${n}" called after dispose — ignored.`)}:t[n]=()=>{};t._asyncListeners.clear(),t._asyncStates.clear(),t._asyncSnapshots.clear(),d&&i&&i.size>0&&t._scheduleGhostCheck(i)})}_scheduleGhostCheck(t){d&&setTimeout(()=>{for(const[s,e]of t)console.warn(`[mvc-kit] Ghost async operation detected: "${s}" had ${e} pending call(s) when the ViewModel was disposed. Consider using disposeSignal to cancel in-flight work.`)},this.constructor.GHOST_TIMEOUT)}_installStateProxy(){const t=new Proxy({},{get:(s,e)=>(this._stateTracking?.add(e),this._state[e]),ownKeys:()=>Reflect.ownKeys(this._state),getOwnPropertyDescriptor:(s,e)=>Reflect.getOwnPropertyDescriptor(this._state,e),set:()=>{throw new Error("Cannot mutate state directly. Use set() instead.")},has:(s,e)=>e in this._state});Object.defineProperty(this,"state",{get:()=>this._stateTracking?t:this._state,configurable:!0,enumerable:!0})}_trackSubscribables(){for(const t of Object.getOwnPropertyNames(this)){const s=this[t];if(!O(s))continue;const e={source:s,revision:0,unsubscribe:s.subscribe(()=>{if(!this._disposed){e.revision++,this._revision++,this._state=Object.freeze({...this._state});for(const i of this._listeners)i(this._state,this._state)}})};this._trackedSources.set(t,e),Object.defineProperty(this,t,{get:()=>(this._sourceTracking?.set(t,e),s),configurable:!0,enumerable:!1})}}_memoizeGetters(){const t=new Set;g(this,b.prototype,(s,e)=>{!e.get||t.has(s)||(t.add(s),this._wrapGetter(s,e.get))})}_wrapGetter(t,s){let e,i=-1,n,a,o;Object.defineProperty(this,t,{get:()=>{if(this._disposed||i===this._revision)return e;if(n&&a){let c=!0;for(const[_,f]of a)if(this._state[_]!==f){c=!1;break}if(c&&o)for(const[_,f]of o){const p=this._trackedSources.get(_);if(p&&p.revision!==f){c=!1;break}}if(c)return i=this._revision,e}const l=this._stateTracking,h=this._sourceTracking;this._stateTracking=new Set,this._sourceTracking=new Map;try{e=s.call(this)}catch(c){throw this._stateTracking=l,this._sourceTracking=h,c}n=this._stateTracking;const u=this._sourceTracking;if(this._stateTracking=l,this._sourceTracking=h,l)for(const c of n)l.add(c);if(h)for(const[c,_]of u)h.set(c,_);a=new Map;for(const c of n)a.set(c,this._state[c]);o=new Map;for(const[c,_]of u)o.set(c,_.revision);return i=this._revision,e},configurable:!0,enumerable:!0})}}class x{_state;_committed;_disposed=!1;_initialized=!1;_listeners=new Set;_abortController=null;_cleanups=null;constructor(t){const s=Object.freeze({...t});this._state=s,this._committed=s}get state(){return this._state}get committed(){return this._committed}get dirty(){return!this.shallowEqual(this._state,this._committed)}get errors(){return this.validate(this._state)}get valid(){return Object.keys(this.errors).length===0}get disposed(){return this._disposed}get initialized(){return this._initialized}get disposeSignal(){return this._abortController||(this._abortController=new AbortController),this._abortController.signal}init(){if(!(this._initialized||this._disposed))return this._initialized=!0,this.onInit?.()}set(t){if(this._disposed)throw new Error("Cannot set state on disposed Model");const s=typeof t=="function"?t(this._state):t;if(!Object.keys(s).some(o=>s[o]!==this._state[o]))return;const n=this._state,a=Object.freeze({...n,...s});this._state=a,this.onSet?.(n,a);for(const o of this._listeners)o(a,n)}commit(){if(this._disposed)throw new Error("Cannot commit on disposed Model");this._committed=this._state}rollback(){if(this._disposed)throw new Error("Cannot rollback on disposed Model");if(this.shallowEqual(this._state,this._committed))return;const t=this._state;this._state=this._committed,this.onSet?.(t,this._state);for(const s of this._listeners)s(this._state,t)}subscribe(t){return this._disposed?()=>{}:(this._listeners.add(t),()=>{this._listeners.delete(t)})}dispose(){if(!this._disposed){if(this._disposed=!0,this._abortController?.abort(),this._cleanups){for(const t of this._cleanups)t();this._cleanups=null}this.onDispose?.(),this._listeners.clear()}}validate(t){return{}}addCleanup(t){this._cleanups||(this._cleanups=[]),this._cleanups.push(t)}subscribeTo(t,s){const e=t.subscribe(s);return this.addCleanup(e),e}shallowEqual(t,s){const e=Object.keys(t),i=Object.keys(s);if(e.length!==i.length)return!1;for(const n of e)if(t[n]!==s[n])return!1;return!0}}const M=typeof __MVC_KIT_DEV__<"u"&&__MVC_KIT_DEV__;class k{static MAX_SIZE=0;static TTL=0;_items=[];_disposed=!1;_listeners=new Set;_index=new Map;_abortController=null;_cleanups=null;_timestamps=null;_evictionTimer=null;constructor(t=[]){let s=[...t];if(this._ttl>0){this._timestamps=new Map;const e=Date.now();for(const i of s)this._timestamps.set(i.id,e)}if(this._maxSize>0&&s.length>this._maxSize){const e=s.length-this._maxSize,i=s.slice(0,e);s=s.slice(e);for(const n of i)this._timestamps?.delete(n.id)}this._items=Object.freeze(s),this.rebuildIndex(),this._scheduleEvictionTimer()}get state(){return this._items}get items(){return this._items}get length(){return this._items.length}get disposed(){return this._disposed}get disposeSignal(){return this._abortController||(this._abortController=new AbortController),this._abortController.signal}get _maxSize(){return this.constructor.MAX_SIZE}get _ttl(){return this.constructor.TTL}add(...t){if(this._disposed)throw new Error("Cannot add to disposed Collection");if(t.length===0)return;const s=new Set,e=[];for(const a of t)!this._index.has(a.id)&&!s.has(a.id)&&(e.push(a),s.add(a.id));if(e.length===0)return;const i=this._items;let n=[...i,...e];for(const a of e)this._index.set(a.id,a);if(this._timestamps){const a=Date.now();for(const o of e)this._timestamps.set(o.id,a)}this._maxSize>0&&n.length>this._maxSize&&(n=this._evictForCapacity(n)),this._items=Object.freeze(n),this.notify(i),this._scheduleEvictionTimer()}upsert(...t){if(this._disposed)throw new Error("Cannot upsert on disposed Collection");if(t.length===0)return;const s=new Map;for(const l of t)s.set(l.id,l);const e=this._items;let i=!1;const n=new Set,a=[];for(const l of e)if(s.has(l.id)){const h=s.get(l.id);h!==l&&(i=!0),a.push(h),n.add(l.id)}else a.push(l);for(const[l,h]of s)n.has(l)||(a.push(h),i=!0);if(!i)return;if(this._timestamps){const l=Date.now();for(const[h]of s)this._timestamps.set(h,l)}for(const[l,h]of s)this._index.set(l,h);let o=a;this._maxSize>0&&o.length>this._maxSize&&(o=this._evictForCapacity(o)),this._items=Object.freeze(o),this.notify(e),this._scheduleEvictionTimer()}remove(...t){if(this._disposed)throw new Error("Cannot remove from disposed Collection");if(t.length===0)return;const s=new Set(t),e=this._items.filter(n=>!s.has(n.id));if(e.length===this._items.length)return;const i=this._items;this._items=Object.freeze(e);for(const n of t)this._index.delete(n),this._timestamps?.delete(n);this.notify(i),this._scheduleEvictionTimer()}update(t,s){if(this._disposed)throw new Error("Cannot update disposed Collection");const e=this._items.findIndex(u=>u.id===t);if(e===-1)return;const i=this._items[e],n={...i,...s,id:t};if(!Object.keys(s).some(u=>s[u]!==i[u]))return;const l=this._items,h=[...l];h[e]=n,this._items=Object.freeze(h),this._index.set(t,n),this.notify(l)}reset(t){if(this._disposed)throw new Error("Cannot reset disposed Collection");const s=this._items;if(this._timestamps){this._timestamps.clear();const i=Date.now();for(const n of t)this._timestamps.set(n.id,i)}let e=[...t];this._maxSize>0&&e.length>this._maxSize&&(e=this._evictForCapacity(e)),this._items=Object.freeze(e),this.rebuildIndex(),this.notify(s),this._scheduleEvictionTimer()}clear(){if(this._disposed)throw new Error("Cannot clear disposed Collection");if(this._items.length===0)return;const t=this._items;this._items=Object.freeze([]),this._index.clear(),this._timestamps?.clear(),this._clearEvictionTimer(),this.notify(t)}optimistic(t){if(this._disposed)throw new Error("Cannot perform optimistic update on disposed Collection");const s=this._items,e=this._timestamps?new Map(this._timestamps):null;t();let i=!1;return()=>{if(i||this._disposed)return;i=!0;const n=this._items;this._items=s,e&&(this._timestamps=e),this.rebuildIndex(),this.notify(n),this._scheduleEvictionTimer()}}get(t){return this._index.get(t)}has(t){return this._index.has(t)}find(t){return this._items.find(t)}filter(t){return this._items.filter(t)}sorted(t){return[...this._items].sort(t)}map(t){return this._items.map(t)}subscribe(t){return this._disposed?()=>{}:(this._listeners.add(t),()=>{this._listeners.delete(t)})}dispose(){if(!this._disposed){if(this._disposed=!0,this._clearEvictionTimer(),this._abortController?.abort(),this._cleanups){for(const t of this._cleanups)t();this._cleanups=null}this.onDispose?.(),this._listeners.clear(),this._index.clear(),this._timestamps?.clear()}}addCleanup(t){this._cleanups||(this._cleanups=[]),this._cleanups.push(t)}notify(t){for(const s of this._listeners)s(this._items,t)}rebuildIndex(){this._index.clear();for(const t of this._items)this._index.set(t.id,t)}_evictForCapacity(t){const s=t.length-this._maxSize;if(s<=0)return t;const e=t.slice(0,s),i=this._applyOnEvict(e,"capacity");if(i===!1||i.length===0)return t;const n=new Set(i.map(o=>o.id)),a=t.filter(o=>!n.has(o.id));for(const o of i)this._index.delete(o.id),this._timestamps?.delete(o.id);return a}_applyOnEvict(t,s){if(!this.onEvict)return t;const e=this.onEvict(t,s);if(e===!1){if(M&&s==="capacity"&&this._maxSize>0){const i=this._items.length+t.length;i>this._maxSize*2&&console.warn(`[mvc-kit] Collection exceeded 2x MAX_SIZE (${i}/${this._maxSize}). onEvict is vetoing eviction — this may cause unbounded growth.`)}return!1}if(Array.isArray(e)){const i=new Set(t.map(n=>n.id));return e.filter(n=>i.has(n.id))}return t}_sweepExpired(){if(this._disposed||!this._timestamps||this._ttl<=0)return;const t=Date.now(),s=this._ttl,e=[];for(const o of this._items){const l=this._timestamps.get(o.id);l!==void 0&&t-l>=s&&e.push(o)}if(e.length===0){this._scheduleEvictionTimer();return}const i=this._applyOnEvict(e,"ttl");if(i===!1){this._scheduleEvictionTimer();return}if(i.length===0){this._scheduleEvictionTimer();return}const n=new Set(i.map(o=>o.id)),a=this._items;this._items=Object.freeze(a.filter(o=>!n.has(o.id)));for(const o of i)this._index.delete(o.id),this._timestamps.delete(o.id);this.notify(a),this._scheduleEvictionTimer()}_scheduleEvictionTimer(){if(this._clearEvictionTimer(),this._disposed||!this._timestamps||this._ttl<=0||this._timestamps.size===0)return;const t=Date.now(),s=this._ttl;let e=1/0;for(const n of this._timestamps.values())n<e&&(e=n);const i=Math.max(0,e+s-t);this._evictionTimer=setTimeout(()=>this._sweepExpired(),i)}_clearEvictionTimer(){this._evictionTimer!==null&&(clearTimeout(this._evictionTimer),this._evictionTimer=null)}}class j{_disposed=!1;_initialized=!1;_abortController=null;_cleanups=null;get disposed(){return this._disposed}get initialized(){return this._initialized}get disposeSignal(){return this._abortController||(this._abortController=new AbortController),this._abortController.signal}init(){if(!(this._initialized||this._disposed))return this._initialized=!0,this.onInit?.()}dispose(){if(!this._disposed){if(this._disposed=!0,this._abortController?.abort(),this._cleanups){for(const t of this._cleanups)t();this._cleanups=null}this.onDispose?.()}}addCleanup(t){this._cleanups||(this._cleanups=[]),this._cleanups.push(t)}subscribeTo(t,s){const e=t.subscribe(s);return this.addCleanup(e),e}}class D{_disposed=!1;_initialized=!1;_abortController=null;_cleanups=null;get disposed(){return this._disposed}get initialized(){return this._initialized}get disposeSignal(){return this._abortController||(this._abortController=new AbortController),this._abortController.signal}init(){if(!(this._initialized||this._disposed))return this._initialized=!0,this.onInit?.()}dispose(){if(!this._disposed){if(this._disposed=!0,this._abortController?.abort(),this._cleanups){for(const t of this._cleanups)t();this._cleanups=null}this.onDispose?.()}}addCleanup(t){this._cleanups||(this._cleanups=[]),this._cleanups.push(t)}}const S=typeof __MVC_KIT_DEV__<"u"&&__MVC_KIT_DEV__,I=Object.freeze({connected:!1,reconnecting:!1,attempt:0,error:null});class P{static RECONNECT_BASE=1e3;static RECONNECT_MAX=3e4;static RECONNECT_FACTOR=2;static MAX_ATTEMPTS=1/0;_status=I;_connState=0;_disposed=!1;_initialized=!1;_listeners=new Set;_handlers=new Map;_abortController=null;_connectAbort=null;_reconnectTimer=null;_cleanups=null;get state(){return this._status}subscribe(t){return this._disposed?()=>{}:(this._listeners.add(t),()=>{this._listeners.delete(t)})}get disposed(){return this._disposed}get initialized(){return this._initialized}get disposeSignal(){return this._abortController||(this._abortController=new AbortController),this._abortController.signal}init(){if(!(this._initialized||this._disposed))return this._initialized=!0,this.onInit?.()}dispose(){if(!this._disposed){this._disposed=!0,this._connState=4,this._reconnectTimer!==null&&(clearTimeout(this._reconnectTimer),this._reconnectTimer=null),this._connectAbort?.abort(),this._connectAbort=null,this._abortController?.abort();try{this.close()}catch{}if(this._cleanups){for(const t of this._cleanups)t();this._cleanups=null}this.onDispose?.(),this._listeners.clear(),this._handlers.clear()}}connect(){if(this._disposed){S&&console.warn("[mvc-kit] connect() called after dispose — ignored.");return}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))}disconnect(){if(!this._disposed){if(this._reconnectTimer!==null&&(clearTimeout(this._reconnectTimer),this._reconnectTimer=null),this._connectAbort?.abort(),this._connectAbort=null,this._connState===2||this._connState===1){this._connState=0;try{this.close()}catch{}}else this._connState=0;this._setStatus({connected:!1,reconnecting:!1,attempt:0,error:null})}}receive(t,s){if(this._disposed){S&&console.warn(`[mvc-kit] receive("${String(t)}") called after dispose — ignored.`);return}const e=this._handlers.get(t);if(e)for(const i of e)i(s)}disconnected(){this._disposed||this._connState!==2&&this._connState!==1||(this._connectAbort?.abort(),this._connectAbort=null,this._connState=3,this._scheduleReconnect(1))}on(t,s){if(this._disposed)return()=>{};let e=this._handlers.get(t);return e||(e=new Set,this._handlers.set(t,e)),e.add(s),()=>{e.delete(s)}}once(t,s){const e=this.on(t,i=>{e(),s(i)});return e}addCleanup(t){this._cleanups||(this._cleanups=[]),this._cleanups.push(t)}subscribeTo(t,s){const e=t.subscribe(s);return this.addCleanup(e),e}_calculateDelay(t){const s=this.constructor,e=Math.min(s.RECONNECT_BASE*Math.pow(s.RECONNECT_FACTOR,t),s.RECONNECT_MAX);return Math.random()*e}_setStatus(t){const s=this._status;if(!(s.connected===t.connected&&s.reconnecting===t.reconnecting&&s.attempt===t.attempt&&s.error===t.error)){this._status=Object.freeze(t);for(const e of this._listeners)e(this._status,s)}}_attemptConnect(t){if(this._disposed)return;this._connState=1,this._connectAbort?.abort(),this._connectAbort=new AbortController;const s=this._abortController?AbortSignal.any([this._abortController.signal,this._connectAbort.signal]):this._connectAbort.signal;this._setStatus({connected:!1,reconnecting:t>0,attempt:t,error:null});let e;try{e=this.open(s)}catch(i){this._onOpenFailed(t,i);return}e&&typeof e.then=="function"?e.then(()=>this._onOpenSucceeded(),i=>this._onOpenFailed(t,i)):this._onOpenSucceeded()}_onOpenSucceeded(){this._disposed||this._connState===1&&(this._connState=2,this._setStatus({connected:!0,reconnecting:!1,attempt:0,error:null}))}_onOpenFailed(t,s){this._disposed||this._connState!==0&&(this._connectAbort?.abort(),this._connectAbort=null,this._connState=3,this._scheduleReconnect(t+1,s))}_scheduleReconnect(t,s){const e=this.constructor;if(t>e.MAX_ATTEMPTS){this._connState=0,this._setStatus({connected:!1,reconnecting:!1,attempt:t,error:s instanceof Error?s.message:"Max reconnection attempts reached"});return}const i=s instanceof Error?s.message:s?String(s):null;this._setStatus({connected:!1,reconnecting:!0,attempt:t,error:i});const n=this._calculateDelay(t-1);this._reconnectTimer=setTimeout(()=>{this._reconnectTimer=null,this._attemptConnect(t)},n)}}exports.EventBus=m.EventBus;exports.hasSingleton=m.hasSingleton;exports.singleton=m.singleton;exports.teardown=m.teardown;exports.teardownAll=m.teardownAll;exports.Channel=P;exports.Collection=k;exports.Controller=j;exports.HttpError=C;exports.Model=x;exports.Service=D;exports.ViewModel=b;exports.classifyError=T;exports.isAbortError=y;
|
|
2
2
|
//# sourceMappingURL=mvc-kit.cjs.map
|