mvc-kit 2.2.3 → 2.4.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # mvc-kit
2
2
 
3
- <img src="./mvc-kit-logo.jpg" alt="mvc-kit logo" width="300" />
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
@@ -156,6 +162,49 @@ todos.sorted((a, b) => a.text.localeCompare(b.text));
156
162
  todos.map(t => t.text); // Map to new array
157
163
  ```
158
164
 
165
+ ### Resource
166
+
167
+ Collection + async tracking toolkit. Extends Collection with lifecycle and automatic async method tracking.
168
+
169
+ ```typescript
170
+ class UsersResource extends Resource<User> {
171
+ private api = singleton(UserService);
172
+
173
+ async loadAll() {
174
+ const data = await this.api.getAll(this.disposeSignal);
175
+ this.reset(data);
176
+ }
177
+
178
+ async loadById(id: number) {
179
+ const user = await this.api.getById(id, this.disposeSignal);
180
+ this.upsert(user);
181
+ }
182
+ }
183
+
184
+ const users = singleton(UsersResource);
185
+ await users.init();
186
+
187
+ users.loadAll();
188
+ users.async.loadAll.loading; // true while loading
189
+ users.async.loadAll.error; // error message, or null
190
+ users.async.loadAll.errorCode; // 'not_found', 'network', etc.
191
+
192
+ // Inherits all Collection methods
193
+ users.items; // readonly User[]
194
+ users.get(1); // User | undefined
195
+ users.filter(u => u.active);
196
+ ```
197
+
198
+ Supports external Collection injection for shared data scenarios:
199
+
200
+ ```typescript
201
+ class UsersResource extends Resource<User> {
202
+ constructor() {
203
+ super(singleton(SharedUsersCollection)); // All mutations go to the shared collection
204
+ }
205
+ }
206
+ ```
207
+
159
208
  ### Controller
160
209
 
161
210
  Stateless orchestrator for complex logic. Component-scoped, auto-disposed.
@@ -658,6 +707,7 @@ function App() {
658
707
  | `ViewModel<S, E?>` | Reactive state container with optional typed events |
659
708
  | `Model<S>` | Reactive entity with validation/dirty tracking |
660
709
  | `Collection<T>` | Reactive typed array with CRUD |
710
+ | `Resource<T>` | Collection + async tracking + external Collection injection |
661
711
  | `Controller` | Stateless orchestrator (Disposable) |
662
712
  | `Service` | Non-reactive infrastructure service (Disposable) |
663
713
  | `EventBus<E>` | Typed pub/sub event bus |
@@ -673,7 +723,8 @@ function App() {
673
723
  | `Updater<S>` | `(state: Readonly<S>) => Partial<S>` |
674
724
  | `ValidationErrors<S>` | `Partial<Record<keyof S, string>>` |
675
725
  | `TaskState` | `{ loading: boolean; error: string \| null }` |
676
- | `AsyncMethodKeys<T>` | Union of method names on `T` that return `Promise` |
726
+ | `AsyncMethodKeys<T>` | Union of method names on `T` that return `Promise` (ViewModel) |
727
+ | `ResourceAsyncMethodKeys<T>` | Union of method names on `T` that return `Promise` (Resource) |
677
728
 
678
729
  ### Singleton Functions
679
730
 
@@ -728,7 +779,7 @@ function App() {
728
779
  - After `init()`, all subclass methods are wrapped for automatic async tracking; `vm.async.methodName` returns `TaskState`
729
780
  - Sync methods are auto-pruned on first call — zero overhead after detection
730
781
  - Async errors are re-thrown (preserves standard Promise rejection); AbortErrors are silently swallowed by the wrapper (but internal catch blocks do receive them — use `isAbortError()` to guard shared-state side effects like Collection rollbacks)
731
- - `async` and `subscribeAsync` are reserved property names on ViewModel
782
+ - `async` and `subscribeAsync` are reserved property names on ViewModel and Resource
732
783
  - React hooks (`useLocal`, `useModel`, `useSingleton`) auto-call `init()` after mount
733
784
  - `singleton()` does **not** auto-call `init()` — call it manually outside React
734
785
  - StrictMode safe: the `_initialized` guard prevents double-init during React's double-mount cycle; `disposeSignal` is not aborted during StrictMode's fake unmount/remount cycle
@@ -745,6 +796,7 @@ Each core class and React hook has a dedicated reference doc with full API detai
745
796
  | [ViewModel](src/ViewModel.md) | State management, computed getters, async tracking, typed events, lifecycle hooks |
746
797
  | [Model](src/Model.md) | Validation, dirty tracking, commit/rollback for entity forms |
747
798
  | [Collection](src/Collection.md) | Reactive typed array, CRUD, optimistic updates, shared data cache |
799
+ | [Resource](src/Resource.md) | Collection + async tracking toolkit with external Collection injection |
748
800
  | [Controller](src/Controller.md) | Stateless orchestrator for multi-ViewModel coordination |
749
801
  | [Service](src/Service.md) | Non-reactive infrastructure adapters (HTTP, storage, SDKs) |
750
802
  | [EventBus](src/EventBus.md) | Typed pub/sub for cross-cutting event communication |
@@ -13,6 +13,7 @@ You are an architecture planning agent for applications built with **mvc-kit**,
13
13
  | `ViewModel<S, E?>` | Reactive state + computed getters + async tracking + typed events | Component-scoped (`useLocal`) |
14
14
  | `Model<S>` | Entity with validation + dirty tracking + commit/rollback | Component-scoped (`useModel`) |
15
15
  | `Collection<T>` | Reactive typed array, shared data cache, optimistic updates | Singleton |
16
+ | `Resource<T>` | Collection + async tracking, transparent Collection injection | Singleton |
16
17
  | `Service` | Stateless infrastructure adapter (HTTP, storage) | Singleton |
17
18
  | `EventBus<E>` | Typed pub/sub for cross-cutting events | Singleton |
18
19
  | `Channel<M>` | Persistent connection (WebSocket/SSE) with auto-reconnect | Singleton |
@@ -24,11 +25,12 @@ You are an architecture planning agent for applications built with **mvc-kit**,
24
25
  1. Holds UI state for a component? → **ViewModel**
25
26
  2. Single entity with validation? → **Model**
26
27
  3. List of entities with CRUD? → **Collection**
27
- 4. Fetches external data? → **Service**
28
- 5. Broadcasts cross-cutting events? → **EventBus**
29
- 6. Persistent connection? → **Channel**
30
- 7. Coordinates multiple ViewModels? → **Controller** (rare)
31
- 8. None of the above plain utility function
28
+ 4. List of entities with CRUD + API loading + loading/error tracking? → **Resource**
29
+ 5. Fetches external data? → **Service**
30
+ 6. Broadcasts cross-cutting events? → **EventBus**
31
+ 7. Persistent connection? → **Channel**
32
+ 8. Coordinates multiple ViewModels?**Controller** (rare)
33
+ 9. None of the above → plain utility function
32
34
 
33
35
  ### Which sharing pattern?
34
36
  - "Can the parent own one ViewModel and pass props?" → **Pattern A** (default)
@@ -341,7 +341,46 @@ async loadPage(page: number) {
341
341
 
342
342
  ---
343
343
 
344
- ## 16. Missing disposeSignal on Async Calls
344
+ ## 16. Empty Collection + Service When Resource Would Suffice
345
+
346
+ ```typescript
347
+ // BAD — boilerplate: empty Collection subclass + Service + manual cache check
348
+ class UsersCollection extends Collection<User> {}
349
+ class UsersViewModel extends ViewModel<State> {
350
+ private collection = singleton(UsersCollection);
351
+ private service = singleton(UserService);
352
+
353
+ onInit() {
354
+ if (this.collection.length === 0) this.load();
355
+ }
356
+
357
+ async load() {
358
+ const data = await this.service.getAll(this.disposeSignal);
359
+ this.collection.reset(data);
360
+ }
361
+ }
362
+
363
+ // GOOD — Resource combines Collection + async tracking
364
+ class UsersResource extends Resource<User> {
365
+ private api = singleton(UserService);
366
+
367
+ async loadAll() {
368
+ const data = await this.api.getAll(this.disposeSignal);
369
+ this.reset(data);
370
+ }
371
+ }
372
+ class UsersViewModel extends ViewModel<State> {
373
+ private users = singleton(UsersResource);
374
+
375
+ onInit() {
376
+ if (this.users.length === 0) this.users.loadAll();
377
+ }
378
+ }
379
+ ```
380
+
381
+ ---
382
+
383
+ ## 17. Missing disposeSignal on Async Calls
345
384
 
346
385
  ```typescript
347
386
  // BAD — no cancellation on unmount
@@ -4,10 +4,10 @@
4
4
 
5
5
  ```typescript
6
6
  // Core classes and utilities
7
- import { ViewModel, Model, Collection, Controller, Service, EventBus, Channel } from 'mvc-kit';
7
+ import { ViewModel, Model, Collection, Resource, Controller, Service, EventBus, Channel } from 'mvc-kit';
8
8
  import { singleton, hasSingleton, teardown, teardownAll } from 'mvc-kit';
9
9
  import { HttpError, isAbortError, classifyError } from 'mvc-kit';
10
- import type { Subscribable, Disposable, Initializable, Listener, Updater, ValidationErrors, TaskState, AppError, AsyncMethodKeys, ChannelStatus } from 'mvc-kit';
10
+ import type { Subscribable, Disposable, Initializable, Listener, Updater, ValidationErrors, TaskState, AppError, AsyncMethodKeys, ResourceAsyncMethodKeys, ChannelStatus } from 'mvc-kit';
11
11
 
12
12
  // React hooks
13
13
  import { useLocal, useSingleton, useInstance, useModel, useField, useEvent, useEmit, useResolve, useTeardown, Provider } from 'mvc-kit/react';
@@ -133,11 +133,50 @@ 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
 
139
145
  ---
140
146
 
147
+ ## Resource<T extends { id: string | number }>
148
+
149
+ Collection + async tracking toolkit. Extends Collection with lifecycle and automatic async method tracking.
150
+
151
+ ### Constructor
152
+ ```typescript
153
+ new MyResource() // Default: Resource IS the collection
154
+ new MyResource([item1, item2]) // With initial items
155
+ new MyResource(externalCollection) // Inject external Collection
156
+ ```
157
+
158
+ ### Lifecycle
159
+ - `initialized: boolean` — Whether `init()` has been called.
160
+ - `init(): void | Promise<void>` — Wraps methods for async tracking, calls `onInit()`. Idempotent.
161
+ - `onInit(): void | Promise<void>` — Override for initial data loading.
162
+ - `onDispose(): void` — Override for custom teardown.
163
+
164
+ ### Async Tracking
165
+ After `init()`, every subclass method is wrapped. Async methods get automatic loading/error tracking.
166
+ - `async: Record<string, TaskState>` — `resource.async.methodName` returns `{ loading, error, errorCode }`.
167
+ - `subscribeAsync(listener: () => void): () => void` — Subscribe to async state changes.
168
+
169
+ ### External Collection Injection
170
+ When constructor receives a Collection instance, ALL inherited Collection methods (add, upsert, reset, items, subscribe, etc.) transparently delegate to the external collection. Resource disposal does NOT dispose the shared collection.
171
+
172
+ ### Inherits All Collection API
173
+ CRUD, query, subscribe, optimistic, MAX_SIZE, TTL — all from Collection.
174
+
175
+ ### Static
176
+ - `static GHOST_TIMEOUT = 3000` — DEV-only ghost detection delay.
177
+
178
+ ---
179
+
141
180
  ## Service
142
181
 
143
182
  Stateless infrastructure adapter. Singleton-scoped.
@@ -229,6 +229,32 @@ Thin subclass for singleton identity. No custom methods — query logic goes in
229
229
 
230
230
  ---
231
231
 
232
+ ## Resource Pattern
233
+
234
+ ```typescript
235
+ class UsersResource extends Resource<UserState> {
236
+ private api = singleton(UserService);
237
+
238
+ async loadAll() {
239
+ const data = await this.api.getAll(this.disposeSignal);
240
+ this.reset(data);
241
+ }
242
+ }
243
+ ```
244
+
245
+ Use Resource when you need a Collection with built-in async tracking. Define your own async methods; use inherited Collection mutations. Each method gets independent tracking: `resource.async.loadAll.loading`.
246
+
247
+ For shared data (Resource + Channel feeding same store), inject an external Collection:
248
+ ```typescript
249
+ class UsersResource extends Resource<UserState> {
250
+ constructor() {
251
+ super(singleton(SharedUsersCollection));
252
+ }
253
+ }
254
+ ```
255
+
256
+ ---
257
+
232
258
  ## Optimistic Updates
233
259
 
234
260
  ```typescript
@@ -9,6 +9,7 @@ This project uses **mvc-kit**, a zero-dependency TypeScript-first reactive state
9
9
  | `ViewModel<S, E?>` | Reactive state + computed getters + async tracking + typed events | Component-scoped (`useLocal`) |
10
10
  | `Model<S>` | Entity with validation + dirty tracking + commit/rollback | Component-scoped (`useModel`) |
11
11
  | `Collection<T>` | Reactive typed array, shared data cache, optimistic updates | Singleton |
12
+ | `Resource<T>` | Collection + async tracking, transparent Collection injection | Singleton |
12
13
  | `Service` | Stateless infrastructure adapter (HTTP, storage) | Singleton |
13
14
  | `EventBus<E>` | Typed pub/sub for cross-cutting events | Singleton |
14
15
  | `Channel<M>` | Persistent connection (WebSocket/SSE) with auto-reconnect | Singleton |
@@ -17,7 +18,7 @@ This project uses **mvc-kit**, a zero-dependency TypeScript-first reactive state
17
18
  ## Imports
18
19
 
19
20
  ```typescript
20
- import { ViewModel, Model, Collection, Controller, Service, EventBus, Channel } from 'mvc-kit';
21
+ import { ViewModel, Model, Collection, Resource, Controller, Service, EventBus, Channel } from 'mvc-kit';
21
22
  import { singleton, teardownAll, HttpError, isAbortError, classifyError } from 'mvc-kit';
22
23
  import { useLocal, useSingleton, useInstance, useModel, useField, useEvent, useEmit, useResolve, useTeardown, Provider } from 'mvc-kit/react';
23
24
  ```
@@ -130,6 +131,15 @@ class UserService extends Service {
130
131
  class UsersCollection extends Collection<UserState> {}
131
132
  // Thin subclass. Query logic in ViewModel getters.
132
133
  // Use reset() for full loads, upsert() for paginated/incremental loads.
134
+
135
+ // Optional eviction (zero-cost when not configured):
136
+ class MessagesCollection extends Collection<Message> {
137
+ static MAX_SIZE = 500; // FIFO eviction when exceeded
138
+ static TTL = 5 * 60_000; // Auto-expire after 5 min
139
+ protected onEvict(items: Message[], reason: 'capacity' | 'ttl') {
140
+ return items.filter(m => !m.pinned); // keep pinned
141
+ }
142
+ }
133
143
  ```
134
144
 
135
145
  ## Model Pattern
@@ -9,6 +9,7 @@ This project uses **mvc-kit**, a zero-dependency TypeScript-first reactive state
9
9
  | `ViewModel<S, E?>` | Reactive state + computed getters + async tracking + typed events | Component-scoped (`useLocal`) |
10
10
  | `Model<S>` | Entity with validation + dirty tracking + commit/rollback | Component-scoped (`useModel`) |
11
11
  | `Collection<T>` | Reactive typed array, shared data cache, optimistic updates | Singleton |
12
+ | `Resource<T>` | Collection + async tracking, transparent Collection injection | Singleton |
12
13
  | `Service` | Stateless infrastructure adapter (HTTP, storage) | Singleton |
13
14
  | `EventBus<E>` | Typed pub/sub for cross-cutting events | Singleton |
14
15
  | `Channel<M>` | Persistent connection (WebSocket/SSE) with auto-reconnect | Singleton |
@@ -17,7 +18,7 @@ This project uses **mvc-kit**, a zero-dependency TypeScript-first reactive state
17
18
  ## Imports
18
19
 
19
20
  ```typescript
20
- import { ViewModel, Model, Collection, Controller, Service, EventBus, Channel } from 'mvc-kit';
21
+ import { ViewModel, Model, Collection, Resource, Controller, Service, EventBus, Channel } from 'mvc-kit';
21
22
  import { singleton, teardownAll, HttpError, isAbortError, classifyError } from 'mvc-kit';
22
23
  import { useLocal, useSingleton, useInstance, useModel, useField, useEvent, useEmit, useResolve, useTeardown, Provider } from 'mvc-kit/react';
23
24
  ```
@@ -130,6 +131,15 @@ class UserService extends Service {
130
131
  class UsersCollection extends Collection<UserState> {}
131
132
  // Thin subclass. Query logic in ViewModel getters.
132
133
  // Use reset() for full loads, upsert() for paginated/incremental loads.
134
+
135
+ // Optional eviction (zero-cost when not configured):
136
+ class MessagesCollection extends Collection<Message> {
137
+ static MAX_SIZE = 500; // FIFO eviction when exceeded
138
+ static TTL = 5 * 60_000; // Auto-expire after 5 min
139
+ protected onEvict(items: Message[], reason: 'capacity' | 'ttl') {
140
+ return items.filter(m => !m.pinned); // keep pinned
141
+ }
142
+ }
133
143
  ```
134
144
 
135
145
  ## Model Pattern
@@ -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
@@ -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;AAEtD,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,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;gBAEpC,YAAY,GAAE,CAAC,EAAO;IAKlC;;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;;OAEG;IACH,GAAG,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI;IA6BxB;;;;;OAKG;IACH,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI;IA8C3B;;OAEG;IACH,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI;IA0B/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;IAYvB;;OAEG;IACH,KAAK,IAAI,IAAI;IAgBb;;;OAGG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAsB5C;;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;IAgBf,uEAAuE;IACvE,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAO1C,uFAAuF;IACvF,SAAS,CAAC,SAAS,CAAC,IAAI,IAAI;IAE5B,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,YAAY;CAMrB"}
1
+ {"version":3,"file":"Collection.d.ts","sourceRoot":"","sources":["../src/Collection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import { Collection } from './Collection';
2
+ import type { Listener, TaskState } from './types';
3
+ export type ResourceAsyncMethodKeys<T> = {
4
+ [K in Exclude<keyof T, keyof Resource<any>>]: T[K] extends (...args: any[]) => Promise<any> ? K : never;
5
+ }[Exclude<keyof T, keyof Resource<any>>];
6
+ type ResourceAsyncMap<T> = {
7
+ readonly [K in ResourceAsyncMethodKeys<T>]: TaskState;
8
+ };
9
+ /**
10
+ * Collection + async tracking toolkit. Extends Collection with lifecycle
11
+ * (init/dispose) and automatic async method tracking. Optionally delegates
12
+ * to an external Collection for shared data scenarios.
13
+ */
14
+ export declare class Resource<T extends {
15
+ id: string | number;
16
+ }> extends Collection<T> {
17
+ private _external;
18
+ private _initialized;
19
+ private _asyncStates;
20
+ private _asyncSnapshots;
21
+ private _asyncListeners;
22
+ private _asyncProxy;
23
+ private _activeOps;
24
+ /** DEV-only timeout (ms) for detecting ghost async operations after dispose. */
25
+ static GHOST_TIMEOUT: number;
26
+ constructor(collectionOrItems?: Collection<T> | T[]);
27
+ /** Whether init() has been called. */
28
+ get initialized(): boolean;
29
+ /** Initializes the instance. Called automatically by React hooks after mount. */
30
+ init(): void | Promise<void>;
31
+ /** Lifecycle hook called at the end of init(). Override to load initial data. @protected */
32
+ protected onInit?(): void | Promise<void>;
33
+ get state(): readonly T[];
34
+ get items(): readonly T[];
35
+ get length(): number;
36
+ add(...items: T[]): void;
37
+ upsert(...items: T[]): void;
38
+ update(id: T['id'], changes: Partial<T>): void;
39
+ remove(...ids: T['id'][]): void;
40
+ reset(items: T[]): void;
41
+ clear(): void;
42
+ optimistic(callback: () => void): () => void;
43
+ get(id: T['id']): T | undefined;
44
+ has(id: T['id']): boolean;
45
+ find(predicate: (item: T) => boolean): T | undefined;
46
+ filter(predicate: (item: T) => boolean): readonly T[];
47
+ sorted(compareFn: (a: T, b: T) => number): readonly T[];
48
+ map<U>(fn: (item: T) => U): readonly U[];
49
+ subscribe(listener: Listener<readonly T[]>): () => void;
50
+ /** Proxy providing `TaskState` (loading, error, errorCode) per async method. */
51
+ get async(): ResourceAsyncMap<this>;
52
+ /** Subscribes to async state changes. Used by `useAsync` and `useInstance` for React integration. */
53
+ subscribeAsync(listener: () => void): () => void;
54
+ private _notifyAsync;
55
+ private _guardReservedKeys;
56
+ private _wrapMethods;
57
+ private _scheduleGhostCheck;
58
+ }
59
+ export {};
60
+ //# sourceMappingURL=Resource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Resource.d.ts","sourceRoot":"","sources":["../src/Resource.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  export type { Listener, Updater, Subscribable, Disposable, Initializable, ValidationErrors, TaskState, } from './types';
2
2
  export type { AsyncMethodKeys } from './ViewModel';
3
+ export type { ResourceAsyncMethodKeys } from './Resource';
3
4
  export { ViewModel } from './ViewModel';
4
5
  export { Model } from './Model';
5
6
  export { Collection } from './Collection';
7
+ export { Resource } from './Resource';
6
8
  export { Controller } from './Controller';
7
9
  export { Service } from './Service';
8
10
  export { EventBus } from './EventBus';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,QAAQ,EACR,OAAO,EACP,YAAY,EACZ,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,SAAS,GACV,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG/C,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGlE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,QAAQ,EACR,OAAO,EACP,YAAY,EACZ,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,SAAS,GACV,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAG1D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG/C,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGlE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
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 g=require("./singleton-L-u2W_lX.cjs");class x extends Error{constructor(t,s){super(s??`HTTP ${t}`),this.status=t,this.name="HttpError"}}function w(o){return o instanceof Error&&o.name==="AbortError"}function T(o){return o===401?"unauthorized":o===403?"forbidden":o===404?"not_found":o===422?"validation":o===429?"rate_limited":o>=500?"server_error":"unknown"}function A(o){return typeof o=="object"&&o!==null&&typeof o.status=="number"&&typeof o.statusText=="string"&&!(o instanceof Error)}function C(o){return o instanceof Error&&o.name==="AbortError"?{code:"abort",message:"Request was aborted",original:o}:o instanceof x?{code:T(o.status),message:o.message,status:o.status,original:o}:A(o)?{code:T(o.status),message:o.statusText||`HTTP ${o.status}`,status:o.status,original:o}:o instanceof TypeError&&o.message.toLowerCase().includes("fetch")?{code:"network",message:o.message,original:o}:o instanceof Error&&o.name==="TimeoutError"?{code:"timeout",message:o.message,original:o}:o instanceof Error?{code:"unknown",message:o.message,original:o}:{code:"unknown",message:String(o),original:o}}const p=typeof __MVC_KIT_DEV__<"u"&&__MVC_KIT_DEV__;function M(o){return o!==null&&typeof o=="object"&&typeof o.subscribe=="function"}function b(o,t,s){let e=Object.getPrototypeOf(o);for(;e&&e!==t;){const i=Object.getOwnPropertyDescriptors(e);for(const[n,c]of Object.entries(i))n!=="constructor"&&s(n,c,e);e=Object.getPrototypeOf(e)}}const j=Object.freeze({loading:!1,error:null,errorCode:null}),E=["async","subscribeAsync"],D=new Set(["onInit","onSet","onDispose"]);class v{_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 g.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(p&&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(a=>s[a]!==this._state[a]))return;const n=this._state,c=Object.freeze({...n,...s});this._state=c,this._revision++,this.onSet?.(n,c);for(const a of this._listeners)a(c,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)??j},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(){b(this,v.prototype,t=>{if(E.includes(t))throw new Error(`[mvc-kit] "${t}" is a reserved property on ViewModel and cannot be overridden.`)})}_wrapMethods(){for(const i of E)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=[];p&&(this._activeOps=new Map),b(this,v.prototype,(i,n)=>{if(n.get||n.set||typeof n.value!="function"||i.startsWith("_")||D.has(i)||s.has(i))return;s.add(i);const c=n.value;let a=!1;const l=function(..._){if(t._disposed){p&&console.warn(`[mvc-kit] "${i}" called after dispose — ignored.`);return}p&&!t._initialized&&console.warn(`[mvc-kit] "${i}" called before init(). Async tracking is active only after init().`);let u;try{u=c.apply(t,_)}catch(h){throw h}if(!u||typeof u.then!="function")return a||(a=!0,t._asyncStates.delete(i),t._asyncSnapshots.delete(i),t[i]=c.bind(t)),u;let r=t._asyncStates.get(i);return r||(r={loading:!1,error:null,errorCode:null,count:0},t._asyncStates.set(i,r)),r.count++,r.loading=!0,r.error=null,r.errorCode=null,t._asyncSnapshots.set(i,Object.freeze({loading:!0,error:null,errorCode:null})),t._notifyAsync(),p&&t._activeOps&&t._activeOps.set(i,(t._activeOps.get(i)??0)+1),u.then(h=>{if(t._disposed)return h;if(r.count--,r.loading=r.count>0,t._asyncSnapshots.set(i,Object.freeze({loading:r.loading,error:r.error,errorCode:r.errorCode})),t._notifyAsync(),p&&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(w(h)){if(t._disposed||(r.count--,r.loading=r.count>0,t._asyncSnapshots.set(i,Object.freeze({loading:r.loading,error:r.error,errorCode:r.errorCode})),t._notifyAsync()),p&&t._activeOps){const f=(t._activeOps.get(i)??1)-1;f<=0?t._activeOps.delete(i):t._activeOps.set(i,f)}return}if(t._disposed)return;r.count--,r.loading=r.count>0;const d=C(h);if(r.error=d.message,r.errorCode=d.code,t._asyncSnapshots.set(i,Object.freeze({loading:r.loading,error:d.message,errorCode:d.code})),t._notifyAsync(),p&&t._activeOps){const f=(t._activeOps.get(i)??1)-1;f<=0?t._activeOps.delete(i):t._activeOps.set(i,f)}throw h})};e.push(i),t[i]=l}),e.length>0&&this.addCleanup(()=>{const i=p&&t._activeOps?new Map(t._activeOps):null;for(const n of e)p?t[n]=()=>{console.warn(`[mvc-kit] "${n}" called after dispose — ignored.`)}:t[n]=()=>{};t._asyncListeners.clear(),t._asyncStates.clear(),t._asyncSnapshots.clear(),p&&i&&i.size>0&&t._scheduleGhostCheck(i)})}_scheduleGhostCheck(t){p&&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(!M(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;b(this,v.prototype,(s,e)=>{!e.get||t.has(s)||(t.add(s),this._wrapGetter(s,e.get))})}_wrapGetter(t,s){let e,i=-1,n,c,a;Object.defineProperty(this,t,{get:()=>{if(this._disposed||i===this._revision)return e;if(n&&c){let r=!0;for(const[h,d]of c)if(this._state[h]!==d){r=!1;break}if(r&&a)for(const[h,d]of a){const f=this._trackedSources.get(h);if(f&&f.revision!==d){r=!1;break}}if(r)return i=this._revision,e}const l=this._stateTracking,_=this._sourceTracking;this._stateTracking=new Set,this._sourceTracking=new Map;try{e=s.call(this)}catch(r){throw this._stateTracking=l,this._sourceTracking=_,r}n=this._stateTracking;const u=this._sourceTracking;if(this._stateTracking=l,this._sourceTracking=_,l)for(const r of n)l.add(r);if(_)for(const[r,h]of u)_.set(r,h);c=new Map;for(const r of n)c.set(r,this._state[r]);a=new Map;for(const[r,h]of u)a.set(r,h.revision);return i=this._revision,e},configurable:!0,enumerable:!0})}}class I{_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(a=>s[a]!==this._state[a]))return;const n=this._state,c=Object.freeze({...n,...s});this._state=c,this.onSet?.(n,c);for(const a of this._listeners)a(c,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 P=typeof __MVC_KIT_DEV__<"u"&&__MVC_KIT_DEV__;class z{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 c of t)!this._index.has(c.id)&&!s.has(c.id)&&(e.push(c),s.add(c.id));if(e.length===0)return;const i=this._items;let n=[...i,...e];for(const c of e)this._index.set(c.id,c);if(this._timestamps){const c=Date.now();for(const a of e)this._timestamps.set(a.id,c)}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,c=[];for(const l of e)if(s.has(l.id)){const _=s.get(l.id);_!==l&&(i=!0),c.push(_),n.add(l.id)}else c.push(l);for(const[l,_]of s)n.has(l)||(c.push(_),i=!0);if(!i)return;if(this._timestamps){const l=Date.now();for(const[_]of s)this._timestamps.set(_,l)}for(const[l,_]of s)this._index.set(l,_);let a=c;this._maxSize>0&&a.length>this._maxSize&&(a=this._evictForCapacity(a)),this._items=Object.freeze(a),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,_=[...l];_[e]=n,this._items=Object.freeze(_),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(a=>a.id)),c=t.filter(a=>!n.has(a.id));for(const a of i)this._index.delete(a.id),this._timestamps?.delete(a.id);return c}_applyOnEvict(t,s){if(!this.onEvict)return t;const e=this.onEvict(t,s);if(e===!1){if(P&&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 a of this._items){const l=this._timestamps.get(a.id);l!==void 0&&t-l>=s&&e.push(a)}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(a=>a.id)),c=this._items;this._items=Object.freeze(c.filter(a=>!n.has(a.id)));for(const a of i)this._index.delete(a.id),this._timestamps.delete(a.id);this.notify(c),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)}}const m=typeof __MVC_KIT_DEV__<"u"&&__MVC_KIT_DEV__,k=Object.freeze({loading:!1,error:null,errorCode:null}),O=["async","subscribeAsync"],R=new Set(["onInit","onDispose"]);class S extends z{_external=null;_initialized=!1;_asyncStates=new Map;_asyncSnapshots=new Map;_asyncListeners=new Set;_asyncProxy=null;_activeOps=null;static GHOST_TIMEOUT=3e3;constructor(t){const s=t!=null&&!Array.isArray(t);if(super(s?[]:t??[]),s&&(this._external=t,m)){const e=this.constructor;(e.MAX_SIZE>0||e.TTL>0)&&console.warn(`[mvc-kit] Resource "${e.name}" has MAX_SIZE or TTL set but uses an injected Collection. Configure these on the Collection instead.`)}this._guardReservedKeys()}get initialized(){return this._initialized}init(){if(!(this._initialized||this.disposed))return this._initialized=!0,this._wrapMethods(),this.onInit?.()}get state(){return this._external?this._external.state:super.state}get items(){return this._external?this._external.items:super.items}get length(){return this._external?this._external.length:super.length}add(...t){this._external?this._external.add(...t):super.add(...t)}upsert(...t){this._external?this._external.upsert(...t):super.upsert(...t)}update(t,s){this._external?this._external.update(t,s):super.update(t,s)}remove(...t){this._external?this._external.remove(...t):super.remove(...t)}reset(t){this._external?this._external.reset(t):super.reset(t)}clear(){this._external?this._external.clear():super.clear()}optimistic(t){return this._external?this._external.optimistic(t):super.optimistic(t)}get(t){return this._external?this._external.get(t):super.get(t)}has(t){return this._external?this._external.has(t):super.has(t)}find(t){return this._external?this._external.find(t):super.find(t)}filter(t){return this._external?this._external.filter(t):super.filter(t)}sorted(t){return this._external?this._external.sorted(t):super.sorted(t)}map(t){return this._external?this._external.map(t):super.map(t)}subscribe(t){return this.disposed?()=>{}:this._external?this._external.subscribe(t):super.subscribe(t)}get async(){if(!this._asyncProxy){const t=this;this._asyncProxy=new Proxy({},{get(s,e){return t._asyncSnapshots.get(e)??k},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()}_guardReservedKeys(){b(this,S.prototype,t=>{if(O.includes(t))throw new Error(`[mvc-kit] "${t}" is a reserved property on Resource and cannot be overridden.`)})}_wrapMethods(){for(const i of O)if(Object.getOwnPropertyDescriptor(this,i)?.value!==void 0)throw new Error(`[mvc-kit] "${i}" is a reserved property on Resource and cannot be overridden.`);const t=this,s=new Set,e=[];m&&(this._activeOps=new Map),b(this,S.prototype,(i,n)=>{if(n.get||n.set||typeof n.value!="function"||i.startsWith("_")||R.has(i)||s.has(i))return;s.add(i);const c=n.value;let a=!1;const l=function(..._){if(t.disposed){m&&console.warn(`[mvc-kit] "${i}" called after dispose — ignored.`);return}m&&!t._initialized&&console.warn(`[mvc-kit] "${i}" called before init(). Async tracking is active only after init().`);let u;try{u=c.apply(t,_)}catch(h){throw h}if(!u||typeof u.then!="function")return a||(a=!0,t._asyncStates.delete(i),t._asyncSnapshots.delete(i),t[i]=c.bind(t)),u;let r=t._asyncStates.get(i);return r||(r={loading:!1,error:null,errorCode:null,count:0},t._asyncStates.set(i,r)),r.count++,r.loading=!0,r.error=null,r.errorCode=null,t._asyncSnapshots.set(i,Object.freeze({loading:!0,error:null,errorCode:null})),t._notifyAsync(),m&&t._activeOps&&t._activeOps.set(i,(t._activeOps.get(i)??0)+1),u.then(h=>{if(t.disposed)return h;if(r.count--,r.loading=r.count>0,t._asyncSnapshots.set(i,Object.freeze({loading:r.loading,error:r.error,errorCode:r.errorCode})),t._notifyAsync(),m&&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(w(h)){if(t.disposed||(r.count--,r.loading=r.count>0,t._asyncSnapshots.set(i,Object.freeze({loading:r.loading,error:r.error,errorCode:r.errorCode})),t._notifyAsync()),m&&t._activeOps){const f=(t._activeOps.get(i)??1)-1;f<=0?t._activeOps.delete(i):t._activeOps.set(i,f)}return}if(t.disposed)return;r.count--,r.loading=r.count>0;const d=C(h);if(r.error=d.message,r.errorCode=d.code,t._asyncSnapshots.set(i,Object.freeze({loading:r.loading,error:d.message,errorCode:d.code})),t._notifyAsync(),m&&t._activeOps){const f=(t._activeOps.get(i)??1)-1;f<=0?t._activeOps.delete(i):t._activeOps.set(i,f)}throw h})};e.push(i),t[i]=l}),e.length>0&&this.addCleanup(()=>{const i=m&&t._activeOps?new Map(t._activeOps):null;for(const n of e)m?t[n]=()=>{console.warn(`[mvc-kit] "${n}" called after dispose — ignored.`)}:t[n]=()=>{};t._asyncListeners.clear(),t._asyncStates.clear(),t._asyncSnapshots.clear(),m&&i&&i.size>0&&t._scheduleGhostCheck(i)})}_scheduleGhostCheck(t){m&&setTimeout(()=>{for(const[s,e]of t)console.warn(`[mvc-kit] Ghost async operation detected: "${s}" had ${e} pending call(s) when the Resource was disposed. Consider using disposeSignal to cancel in-flight work.`)},this.constructor.GHOST_TIMEOUT)}}class K{_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 ${_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 y=typeof __MVC_KIT_DEV__<"u"&&__MVC_KIT_DEV__,V=Object.freeze({connected:!1,reconnecting:!1,attempt:0,error:null});class L{static RECONNECT_BASE=1e3;static RECONNECT_MAX=3e4;static RECONNECT_FACTOR=2;static MAX_ATTEMPTS=1/0;_status=V;_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){y&&console.warn("[mvc-kit] connect() called after dispose — ignored.");return}y&&!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){y&&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=g.EventBus;exports.hasSingleton=g.hasSingleton;exports.singleton=g.singleton;exports.teardown=g.teardown;exports.teardownAll=g.teardownAll;exports.Channel=L;exports.Collection=z;exports.Controller=K;exports.HttpError=x;exports.Model=I;exports.Resource=S;exports.Service=$;exports.ViewModel=v;exports.classifyError=C;exports.isAbortError=w;
2
2
  //# sourceMappingURL=mvc-kit.cjs.map