mvc-kit 2.2.1 → 2.2.3
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 +14 -9
- package/agent-config/claude-code/agents/mvc-kit-architect.md +2 -3
- package/agent-config/claude-code/skills/guide/SKILL.md +2 -2
- package/agent-config/claude-code/skills/guide/anti-patterns.md +44 -7
- package/agent-config/claude-code/skills/guide/api-reference.md +5 -4
- package/agent-config/claude-code/skills/guide/patterns.md +22 -18
- package/agent-config/claude-code/skills/review/checklist.md +2 -2
- package/agent-config/claude-code/skills/scaffold/SKILL.md +1 -1
- package/agent-config/claude-code/skills/scaffold/templates/collection.md +7 -14
- package/agent-config/claude-code/skills/scaffold/templates/viewmodel.md +13 -42
- package/agent-config/copilot/copilot-instructions.md +18 -18
- package/agent-config/cursor/cursorrules +18 -18
- package/dist/Channel.d.ts +29 -0
- package/dist/Channel.d.ts.map +1 -1
- package/dist/Collection.d.ts +16 -1
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Controller.d.ts +9 -0
- package/dist/Controller.d.ts.map +1 -1
- package/dist/EventBus.d.ts +5 -0
- package/dist/EventBus.d.ts.map +1 -1
- package/dist/Model.d.ts +16 -0
- package/dist/Model.d.ts.map +1 -1
- package/dist/Service.d.ts +8 -0
- package/dist/Service.d.ts.map +1 -1
- package/dist/ViewModel.d.ts +35 -1
- package/dist/ViewModel.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 +226 -111
- package/dist/mvc-kit.js.map +1 -1
- package/dist/react/provider.d.ts +1 -0
- package/dist/react/provider.d.ts.map +1 -1
- package/dist/react/use-model.d.ts +2 -0
- package/dist/react/use-model.d.ts.map +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +1 -1
- package/dist/react.js.map +1 -1
- package/dist/{singleton-C8_FRbA7.js → singleton-CaEXSbYg.js} +5 -1
- package/dist/singleton-CaEXSbYg.js.map +1 -0
- package/dist/singleton-L-u2W_lX.cjs.map +1 -1
- package/dist/singleton.d.ts +10 -0
- package/dist/singleton.d.ts.map +1 -1
- package/mvc-kit-logo.jpg +0 -0
- package/package.json +2 -1
- package/dist/singleton-C8_FRbA7.js.map +0 -1
|
@@ -30,7 +30,7 @@ import { useLocal, useSingleton, useInstance, useModel, useField, useEvent, useE
|
|
|
30
30
|
4. **One ViewModel per component** via `useLocal`. No `useEffect` for data loading — use `onInit()`.
|
|
31
31
|
5. **No `useState`/`useMemo`/`useCallback`** — the ViewModel is the hook.
|
|
32
32
|
6. **Components are declarative.** Read `state.x` for raw, `vm.x` for computed, `vm.async.x` for loading/error.
|
|
33
|
-
7. **Collections are encapsulated.**
|
|
33
|
+
7. **Collections are encapsulated.** ViewModel getters read from collections directly — auto-tracking handles reactivity. Use `subscribeTo()` only for imperative side effects. Components never import Collections.
|
|
34
34
|
8. **Services are stateless.** Accept `AbortSignal`, throw `HttpError`, no knowledge of ViewModels.
|
|
35
35
|
9. **Pass `this.disposeSignal`** to every async call.
|
|
36
36
|
10. **Lifecycle**: `construct → init() → use → dispose()`. Hooks auto-call `init()`.
|
|
@@ -39,35 +39,31 @@ import { useLocal, useSingleton, useInstance, useModel, useField, useEvent, useE
|
|
|
39
39
|
|
|
40
40
|
```typescript
|
|
41
41
|
interface ItemState {
|
|
42
|
-
items: Item[];
|
|
43
42
|
search: string;
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
class ItemsViewModel extends ViewModel<ItemState> {
|
|
47
46
|
// --- Private fields ---
|
|
48
47
|
private service = singleton(ItemService);
|
|
49
|
-
|
|
48
|
+
collection = singleton(ItemsCollection);
|
|
50
49
|
|
|
51
50
|
// --- Computed getters ---
|
|
51
|
+
get items(): Item[] {
|
|
52
|
+
return this.collection.items as Item[];
|
|
53
|
+
}
|
|
54
|
+
|
|
52
55
|
get filtered(): Item[] {
|
|
53
|
-
const {
|
|
54
|
-
if (!search) return items;
|
|
56
|
+
const { search } = this.state;
|
|
57
|
+
if (!search) return this.items;
|
|
55
58
|
const q = search.toLowerCase();
|
|
56
|
-
return items.filter(i => i.name.toLowerCase().includes(q));
|
|
59
|
+
return this.items.filter(i => i.name.toLowerCase().includes(q));
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
get total(): number { return this.
|
|
62
|
+
get total(): number { return this.items.length; }
|
|
60
63
|
|
|
61
64
|
// --- Lifecycle ---
|
|
62
65
|
protected onInit() {
|
|
63
|
-
|
|
64
|
-
this.set({ items: this.collection.items });
|
|
65
|
-
});
|
|
66
|
-
if (this.collection.length > 0) {
|
|
67
|
-
this.set({ items: this.collection.items });
|
|
68
|
-
} else {
|
|
69
|
-
this.load();
|
|
70
|
-
}
|
|
66
|
+
if (this.collection.length === 0) this.load();
|
|
71
67
|
}
|
|
72
68
|
|
|
73
69
|
// --- Actions ---
|
|
@@ -87,7 +83,7 @@ class ItemsViewModel extends ViewModel<ItemState> {
|
|
|
87
83
|
|
|
88
84
|
```tsx
|
|
89
85
|
function ItemsPage() {
|
|
90
|
-
const [state, vm] = useLocal(ItemsViewModel, {
|
|
86
|
+
const [state, vm] = useLocal(ItemsViewModel, { search: '' });
|
|
91
87
|
const { loading, error } = vm.async.load;
|
|
92
88
|
|
|
93
89
|
return (
|
|
@@ -133,6 +129,7 @@ class UserService extends Service {
|
|
|
133
129
|
```typescript
|
|
134
130
|
class UsersCollection extends Collection<UserState> {}
|
|
135
131
|
// Thin subclass. Query logic in ViewModel getters.
|
|
132
|
+
// Use reset() for full loads, upsert() for paginated/incremental loads.
|
|
136
133
|
```
|
|
137
134
|
|
|
138
135
|
## Model Pattern
|
|
@@ -181,7 +178,7 @@ async toggleStatus(id: string) {
|
|
|
181
178
|
try {
|
|
182
179
|
await this.service.update(id, { status: 'done' }, this.disposeSignal);
|
|
183
180
|
} catch (e) {
|
|
184
|
-
if (!isAbortError(e)) rollback();
|
|
181
|
+
if (!isAbortError(e)) rollback(); // guard needed — rollback affects shared Collection
|
|
185
182
|
throw e;
|
|
186
183
|
}
|
|
187
184
|
}
|
|
@@ -190,9 +187,11 @@ async toggleStatus(id: string) {
|
|
|
190
187
|
## Error Handling Layers
|
|
191
188
|
|
|
192
189
|
1. **Async tracking** (automatic): Write happy path, read `vm.async.method.error`
|
|
193
|
-
2. **Imperative events** (explicit): try/catch + `emit()` + **re-throw
|
|
190
|
+
2. **Imperative events** (explicit): try/catch + `emit()` + **re-throw**. No `isAbortError()` guard needed — `emit()` and `set()` are no-ops after dispose.
|
|
194
191
|
3. **Error classification** (services): `throw HttpError`, `classifyError()`
|
|
195
192
|
|
|
193
|
+
**`isAbortError()` rule:** Only needed in catch blocks that affect shared state (Collection rollbacks). Not needed for `set()`/`emit()` or methods without try/catch.
|
|
194
|
+
|
|
196
195
|
## Testing
|
|
197
196
|
|
|
198
197
|
```typescript
|
|
@@ -220,6 +219,7 @@ test('example', () => {
|
|
|
220
219
|
- Manual try/catch for standard loads → async tracking handles it
|
|
221
220
|
- Two-step setters with refilter calls → getters auto-recompute
|
|
222
221
|
- Manual optimistic snapshot/restore → use `collection.optimistic()`
|
|
222
|
+
- `reset()` for paginated/incremental loads → use `upsert()` to accumulate data
|
|
223
223
|
- `useState`/`useMemo`/`useCallback` in connected components → ViewModel handles it
|
|
224
224
|
|
|
225
225
|
## Decision Framework
|
package/dist/Channel.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Listener, Subscribable, Disposable, Initializable } from './types';
|
|
2
|
+
/** Describes the current connection state of a Channel. */
|
|
2
3
|
export interface ChannelStatus {
|
|
3
4
|
readonly connected: boolean;
|
|
4
5
|
readonly reconnecting: boolean;
|
|
@@ -6,10 +7,18 @@ export interface ChannelStatus {
|
|
|
6
7
|
readonly error: string | null;
|
|
7
8
|
}
|
|
8
9
|
type Handler<T> = (payload: T) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Abstract persistent connection with automatic reconnection and exponential backoff.
|
|
12
|
+
* Subclass to implement WebSocket, SSE, or other transport protocols.
|
|
13
|
+
*/
|
|
9
14
|
export declare abstract class Channel<M extends Record<string, any>> implements Subscribable<ChannelStatus>, Initializable, Disposable {
|
|
15
|
+
/** Base delay (ms) for reconnection backoff. */
|
|
10
16
|
static RECONNECT_BASE: number;
|
|
17
|
+
/** Maximum delay cap (ms) for reconnection backoff. */
|
|
11
18
|
static RECONNECT_MAX: number;
|
|
19
|
+
/** Exponential backoff multiplier for reconnection delay. */
|
|
12
20
|
static RECONNECT_FACTOR: number;
|
|
21
|
+
/** Maximum number of reconnection attempts before giving up. */
|
|
13
22
|
static MAX_ATTEMPTS: number;
|
|
14
23
|
private _status;
|
|
15
24
|
private _connState;
|
|
@@ -21,25 +30,45 @@ export declare abstract class Channel<M extends Record<string, any>> implements
|
|
|
21
30
|
private _connectAbort;
|
|
22
31
|
private _reconnectTimer;
|
|
23
32
|
private _cleanups;
|
|
33
|
+
/** Current connection status. */
|
|
24
34
|
get state(): Readonly<ChannelStatus>;
|
|
35
|
+
/** Subscribes to connection status changes. Returns an unsubscribe function. */
|
|
25
36
|
subscribe(listener: Listener<ChannelStatus>): () => void;
|
|
37
|
+
/** Whether this instance has been disposed. */
|
|
26
38
|
get disposed(): boolean;
|
|
39
|
+
/** Whether init() has been called. */
|
|
27
40
|
get initialized(): boolean;
|
|
41
|
+
/** AbortSignal that fires when this instance is disposed. Lazily created. */
|
|
28
42
|
get disposeSignal(): AbortSignal;
|
|
43
|
+
/** Initializes the instance. Called automatically by React hooks after mount. */
|
|
29
44
|
init(): void | Promise<void>;
|
|
45
|
+
/** Tears down the instance, releasing all subscriptions and resources. */
|
|
30
46
|
dispose(): void;
|
|
47
|
+
/** Establishes the underlying connection. Called internally by connect(). @protected */
|
|
31
48
|
protected abstract open(signal: AbortSignal): void | Promise<void>;
|
|
49
|
+
/** Tears down the underlying connection. Called internally by disconnect() and dispose(). @protected */
|
|
32
50
|
protected abstract close(): void;
|
|
51
|
+
/** Initiates a connection with automatic reconnection on failure. */
|
|
33
52
|
connect(): void;
|
|
53
|
+
/** Closes the connection and cancels any pending reconnection. */
|
|
34
54
|
disconnect(): void;
|
|
55
|
+
/** Call from subclass when a message arrives from the transport. @protected */
|
|
35
56
|
protected receive<K extends keyof M>(type: K, payload: M[K]): void;
|
|
57
|
+
/** Call from subclass when the transport connection drops unexpectedly. Triggers reconnection. @protected */
|
|
36
58
|
protected disconnected(): void;
|
|
59
|
+
/** Subscribes to a specific message type. Returns an unsubscribe function. */
|
|
37
60
|
on<K extends keyof M>(type: K, handler: Handler<M[K]>): () => void;
|
|
61
|
+
/** Subscribes to a message type, auto-removing the handler after the first invocation. */
|
|
38
62
|
once<K extends keyof M>(type: K, handler: Handler<M[K]>): () => void;
|
|
63
|
+
/** Registers a cleanup function to be called on dispose. @protected */
|
|
39
64
|
protected addCleanup(fn: () => void): void;
|
|
65
|
+
/** Subscribes to an external Subscribable with automatic cleanup on dispose. @protected */
|
|
40
66
|
protected subscribeTo<T>(source: Subscribable<T>, listener: Listener<T>): () => void;
|
|
67
|
+
/** Lifecycle hook called at the end of init(). Override to load initial data. @protected */
|
|
41
68
|
protected onInit?(): void | Promise<void>;
|
|
69
|
+
/** Lifecycle hook called during dispose(). Override for custom teardown. @protected */
|
|
42
70
|
protected onDispose?(): void;
|
|
71
|
+
/** Computes the reconnect backoff delay with jitter for the given attempt number. @protected */
|
|
43
72
|
protected _calculateDelay(attempt: number): number;
|
|
44
73
|
private _setStatus;
|
|
45
74
|
private _attemptConnect;
|
package/dist/Channel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Channel.d.ts","sourceRoot":"","sources":["../src/Channel.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Channel.d.ts","sourceRoot":"","sources":["../src/Channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAMjF,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,CAAC;AAmBvC;;;GAGG;AACH,8BAAsB,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CACzD,YAAW,YAAY,CAAC,aAAa,CAAC,EAAE,aAAa,EAAE,UAAU;IAGjE,gDAAgD;IAChD,MAAM,CAAC,cAAc,SAAQ;IAC7B,uDAAuD;IACvD,MAAM,CAAC,aAAa,SAAS;IAC7B,6DAA6D;IAC7D,MAAM,CAAC,gBAAgB,SAAK;IAC5B,gEAAgE;IAChE,MAAM,CAAC,YAAY,SAAY;IAG/B,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,UAAU,CAAyC;IAC3D,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,UAAU,CAAsC;IACxD,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,aAAa,CAAgC;IACrD,OAAO,CAAC,eAAe,CAA8C;IACrE,OAAO,CAAC,SAAS,CAA+B;IAIhD,iCAAiC;IACjC,IAAI,KAAK,IAAI,QAAQ,CAAC,aAAa,CAAC,CAEnC;IAED,gFAAgF;IAChF,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,MAAM,IAAI;IAQxD,+CAA+C;IAC/C,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,sCAAsC;IACtC,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,6EAA6E;IAC7E,IAAI,aAAa,IAAI,WAAW,CAK/B;IAED,iFAAiF;IACjF,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5B,0EAA0E;IAC1E,OAAO,IAAI,IAAI;IAkCf,wFAAwF;IACxF,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAClE,wGAAwG;IACxG,SAAS,CAAC,QAAQ,CAAC,KAAK,IAAI,IAAI;IAIhC,qEAAqE;IACrE,OAAO,IAAI,IAAI;IA0Bf,kEAAkE;IAClE,UAAU,IAAI,IAAI;IA6BlB,+EAA+E;IAC/E,SAAS,CAAC,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;IAgBlE,6GAA6G;IAC7G,SAAS,CAAC,YAAY,IAAI,IAAI;IAmB9B,8EAA8E;IAC9E,EAAE,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAalE,0FAA0F;IAC1F,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAUpE,uEAAuE;IACvE,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAO1C,2FAA2F;IAC3F,SAAS,CAAC,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAMpF,4FAA4F;IAC5F,SAAS,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IACzC,uFAAuF;IACvF,SAAS,CAAC,SAAS,CAAC,IAAI,IAAI;IAI5B,gGAAgG;IAChG,SAAS,CAAC,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAWlD,OAAO,CAAC,UAAU;IAiBlB,OAAO,CAAC,eAAe;IAsCvB,OAAO,CAAC,gBAAgB;IAcxB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,kBAAkB;CA6B3B"}
|
package/dist/Collection.d.ts
CHANGED
|
@@ -18,14 +18,25 @@ export declare class Collection<T extends {
|
|
|
18
18
|
* Alias for Subscribable compatibility.
|
|
19
19
|
*/
|
|
20
20
|
get state(): readonly T[];
|
|
21
|
+
/** The raw readonly array of items. */
|
|
21
22
|
get items(): readonly T[];
|
|
23
|
+
/** Number of items in the collection. */
|
|
22
24
|
get length(): number;
|
|
25
|
+
/** Whether this instance has been disposed. */
|
|
23
26
|
get disposed(): boolean;
|
|
27
|
+
/** AbortSignal that fires when this instance is disposed. Lazily created. */
|
|
24
28
|
get disposeSignal(): AbortSignal;
|
|
25
29
|
/**
|
|
26
|
-
* Add one or more items.
|
|
30
|
+
* Add one or more items. Items with existing IDs are silently skipped.
|
|
27
31
|
*/
|
|
28
32
|
add(...items: T[]): void;
|
|
33
|
+
/**
|
|
34
|
+
* Add or replace items by ID. Existing items are replaced in-place
|
|
35
|
+
* (preserving array position); new items are appended. Deduplicates
|
|
36
|
+
* input — last occurrence wins. No-op if nothing changed (reference
|
|
37
|
+
* comparison).
|
|
38
|
+
*/
|
|
39
|
+
upsert(...items: T[]): void;
|
|
29
40
|
/**
|
|
30
41
|
* Remove items by id(s).
|
|
31
42
|
*/
|
|
@@ -71,9 +82,13 @@ export declare class Collection<T extends {
|
|
|
71
82
|
* Map items to new array.
|
|
72
83
|
*/
|
|
73
84
|
map<U>(fn: (item: T) => U): readonly U[];
|
|
85
|
+
/** Subscribes to state changes. Returns an unsubscribe function. */
|
|
74
86
|
subscribe(listener: CollectionListener<T>): () => void;
|
|
87
|
+
/** Tears down the instance, releasing all subscriptions and resources. */
|
|
75
88
|
dispose(): void;
|
|
89
|
+
/** Registers a cleanup function to be called on dispose. @protected */
|
|
76
90
|
protected addCleanup(fn: () => void): void;
|
|
91
|
+
/** Lifecycle hook called during dispose(). Override for custom teardown. @protected */
|
|
77
92
|
protected onDispose?(): void;
|
|
78
93
|
private notify;
|
|
79
94
|
private rebuildIndex;
|
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;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,IAAI,KAAK,IAAI,SAAS,CAAC,EAAE,CAExB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,aAAa,IAAI,WAAW,CAK/B;IAID;;OAEG;IACH,GAAG,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI;
|
|
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"}
|
package/dist/Controller.d.ts
CHANGED
|
@@ -8,14 +8,23 @@ export declare abstract class Controller implements Disposable {
|
|
|
8
8
|
private _initialized;
|
|
9
9
|
private _abortController;
|
|
10
10
|
private _cleanups;
|
|
11
|
+
/** Whether this instance has been disposed. */
|
|
11
12
|
get disposed(): boolean;
|
|
13
|
+
/** Whether init() has been called. */
|
|
12
14
|
get initialized(): boolean;
|
|
15
|
+
/** AbortSignal that fires when this instance is disposed. Lazily created. */
|
|
13
16
|
get disposeSignal(): AbortSignal;
|
|
17
|
+
/** Initializes the instance. Called automatically by React hooks after mount. */
|
|
14
18
|
init(): void | Promise<void>;
|
|
19
|
+
/** Tears down the instance, releasing all subscriptions and resources. */
|
|
15
20
|
dispose(): void;
|
|
21
|
+
/** Registers a cleanup function to be called on dispose. @protected */
|
|
16
22
|
protected addCleanup(fn: () => void): void;
|
|
23
|
+
/** Subscribes to an external Subscribable with automatic cleanup on dispose. @protected */
|
|
17
24
|
protected subscribeTo<T>(source: Subscribable<T>, listener: Listener<T>): () => void;
|
|
25
|
+
/** Lifecycle hook called at the end of init(). Override to load initial data. @protected */
|
|
18
26
|
protected onInit?(): void | Promise<void>;
|
|
27
|
+
/** Lifecycle hook called during dispose(). Override for custom teardown. @protected */
|
|
19
28
|
protected onDispose?(): void;
|
|
20
29
|
}
|
|
21
30
|
//# sourceMappingURL=Controller.d.ts.map
|
package/dist/Controller.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Controller.d.ts","sourceRoot":"","sources":["../src/Controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAElE;;;GAGG;AACH,8BAAsB,UAAW,YAAW,UAAU;IACpD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,SAAS,CAA+B;IAEhD,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,IAAI,aAAa,IAAI,WAAW,CAK/B;IAED,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5B,OAAO,IAAI,IAAI;IAcf,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAO1C,SAAS,CAAC,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAMpF,SAAS,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IACzC,SAAS,CAAC,SAAS,CAAC,IAAI,IAAI;CAC7B"}
|
|
1
|
+
{"version":3,"file":"Controller.d.ts","sourceRoot":"","sources":["../src/Controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAElE;;;GAGG;AACH,8BAAsB,UAAW,YAAW,UAAU;IACpD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,SAAS,CAA+B;IAEhD,+CAA+C;IAC/C,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,sCAAsC;IACtC,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,6EAA6E;IAC7E,IAAI,aAAa,IAAI,WAAW,CAK/B;IAED,iFAAiF;IACjF,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5B,0EAA0E;IAC1E,OAAO,IAAI,IAAI;IAcf,uEAAuE;IACvE,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAO1C,2FAA2F;IAC3F,SAAS,CAAC,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAMpF,4FAA4F;IAC5F,SAAS,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IACzC,uFAAuF;IACvF,SAAS,CAAC,SAAS,CAAC,IAAI,IAAI;CAC7B"}
|
package/dist/EventBus.d.ts
CHANGED
|
@@ -10,7 +10,9 @@ export declare class EventBus<E extends Record<string, any>> implements Disposab
|
|
|
10
10
|
private _handlers;
|
|
11
11
|
private _abortController;
|
|
12
12
|
private _cleanups;
|
|
13
|
+
/** Whether this instance has been disposed. */
|
|
13
14
|
get disposed(): boolean;
|
|
15
|
+
/** AbortSignal that fires when this instance is disposed. Lazily created. */
|
|
14
16
|
get disposeSignal(): AbortSignal;
|
|
15
17
|
/**
|
|
16
18
|
* Emit an event with a payload.
|
|
@@ -24,8 +26,11 @@ export declare class EventBus<E extends Record<string, any>> implements Disposab
|
|
|
24
26
|
* Subscribe to an event once. Auto-unsubscribes after first invocation.
|
|
25
27
|
*/
|
|
26
28
|
once<K extends keyof E>(event: K, handler: Handler<E[K]>): () => void;
|
|
29
|
+
/** Tears down the instance, releasing all subscriptions and resources. */
|
|
27
30
|
dispose(): void;
|
|
31
|
+
/** Registers a cleanup function to be called on dispose. @protected */
|
|
28
32
|
protected addCleanup(fn: () => void): void;
|
|
33
|
+
/** Lifecycle hook called during dispose(). Override for custom teardown. @protected */
|
|
29
34
|
protected onDispose?(): void;
|
|
30
35
|
}
|
|
31
36
|
export {};
|
package/dist/EventBus.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventBus.d.ts","sourceRoot":"","sources":["../src/EventBus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,CAAC;AAEvC;;GAEG;AACH,qBAAa,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAE,YAAW,UAAU;IACxE,8FAA8F;IAC9F,SAAiB,MAAM,EAAE,CAAC,CAAC;IAE3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,SAAS,CAA+B;IAEhD,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,aAAa,IAAI,WAAW,CAK/B;IAED;;OAEG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;IAatD;;OAEG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAkBnE;;OAEG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAQrE,OAAO,IAAI,IAAI;IAef,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAO1C,SAAS,CAAC,SAAS,CAAC,IAAI,IAAI;CAC7B"}
|
|
1
|
+
{"version":3,"file":"EventBus.d.ts","sourceRoot":"","sources":["../src/EventBus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,CAAC;AAEvC;;GAEG;AACH,qBAAa,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAE,YAAW,UAAU;IACxE,8FAA8F;IAC9F,SAAiB,MAAM,EAAE,CAAC,CAAC;IAE3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,SAAS,CAA+B;IAEhD,+CAA+C;IAC/C,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,6EAA6E;IAC7E,IAAI,aAAa,IAAI,WAAW,CAK/B;IAED;;OAEG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;IAatD;;OAEG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAkBnE;;OAEG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAQrE,0EAA0E;IAC1E,OAAO,IAAI,IAAI;IAef,uEAAuE;IACvE,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAO1C,uFAAuF;IACvF,SAAS,CAAC,SAAS,CAAC,IAAI,IAAI;CAC7B"}
|
package/dist/Model.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare abstract class Model<S extends object> implements Subscribable<S>
|
|
|
11
11
|
private _abortController;
|
|
12
12
|
private _cleanups;
|
|
13
13
|
constructor(initialState: S);
|
|
14
|
+
/** Current frozen state object. */
|
|
14
15
|
get state(): Readonly<S>;
|
|
15
16
|
/**
|
|
16
17
|
* The baseline state for dirty tracking.
|
|
@@ -28,10 +29,18 @@ export declare abstract class Model<S extends object> implements Subscribable<S>
|
|
|
28
29
|
* True if there are no validation errors.
|
|
29
30
|
*/
|
|
30
31
|
get valid(): boolean;
|
|
32
|
+
/** Whether this instance has been disposed. */
|
|
31
33
|
get disposed(): boolean;
|
|
34
|
+
/** Whether init() has been called. */
|
|
32
35
|
get initialized(): boolean;
|
|
36
|
+
/** AbortSignal that fires when this instance is disposed. Lazily created. */
|
|
33
37
|
get disposeSignal(): AbortSignal;
|
|
38
|
+
/** Initializes the instance. Called automatically by React hooks after mount. */
|
|
34
39
|
init(): void | Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Merges partial state with validation. No-op if no values changed by reference.
|
|
42
|
+
* @protected
|
|
43
|
+
*/
|
|
35
44
|
protected set(partialOrUpdater: Partial<S> | Updater<S>): void;
|
|
36
45
|
/**
|
|
37
46
|
* Mark current state as the new baseline (not dirty).
|
|
@@ -41,17 +50,24 @@ export declare abstract class Model<S extends object> implements Subscribable<S>
|
|
|
41
50
|
* Revert state to committed baseline.
|
|
42
51
|
*/
|
|
43
52
|
rollback(): void;
|
|
53
|
+
/** Subscribes to state changes. Returns an unsubscribe function. */
|
|
44
54
|
subscribe(listener: Listener<S>): () => void;
|
|
55
|
+
/** Tears down the instance, releasing all subscriptions and resources. */
|
|
45
56
|
dispose(): void;
|
|
46
57
|
/**
|
|
47
58
|
* Override to provide validation logic.
|
|
48
59
|
* Return an object mapping field keys to error messages.
|
|
49
60
|
*/
|
|
50
61
|
protected validate(_state: Readonly<S>): ValidationErrors<S>;
|
|
62
|
+
/** Registers a cleanup function to be called on dispose. @protected */
|
|
51
63
|
protected addCleanup(fn: () => void): void;
|
|
64
|
+
/** Subscribes to an external Subscribable with automatic cleanup on dispose. @protected */
|
|
52
65
|
protected subscribeTo<T>(source: Subscribable<T>, listener: Listener<T>): () => void;
|
|
66
|
+
/** Lifecycle hook called after every set() with the previous state. @protected */
|
|
53
67
|
protected onSet?(prev: Readonly<S>, next: Readonly<S>): void;
|
|
68
|
+
/** Lifecycle hook called at the end of init(). Override to load initial data. @protected */
|
|
54
69
|
protected onInit?(): void | Promise<void>;
|
|
70
|
+
/** Lifecycle hook called during dispose(). Override for custom teardown. @protected */
|
|
55
71
|
protected onDispose?(): void;
|
|
56
72
|
private shallowEqual;
|
|
57
73
|
}
|
package/dist/Model.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Model.d.ts","sourceRoot":"","sources":["../src/Model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEjF;;GAEG;AACH,8BAAsB,KAAK,CAAC,CAAC,SAAS,MAAM,CAAE,YAAW,YAAY,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,SAAS,CAA+B;gBAEpC,YAAY,EAAE,CAAC;IAM3B,IAAI,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,CAEvB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,QAAQ,CAAC,CAAC,CAAC,CAE3B;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAEhC;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,IAAI,aAAa,IAAI,WAAW,CAK/B;IAED,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5B,SAAS,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IA+B9D;;OAEG;IACH,MAAM,IAAI,IAAI;IAOd;;OAEG;IACH,QAAQ,IAAI,IAAI;IAmBhB,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAY5C,OAAO,IAAI,IAAI;IAef;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC;IAI5D,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAO1C,SAAS,CAAC,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAMpF,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI;IAC5D,SAAS,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IACzC,SAAS,CAAC,SAAS,CAAC,IAAI,IAAI;IAE5B,OAAO,CAAC,YAAY;CAgBrB"}
|
|
1
|
+
{"version":3,"file":"Model.d.ts","sourceRoot":"","sources":["../src/Model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEjF;;GAEG;AACH,8BAAsB,KAAK,CAAC,CAAC,SAAS,MAAM,CAAE,YAAW,YAAY,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,SAAS,CAA+B;gBAEpC,YAAY,EAAE,CAAC;IAM3B,mCAAmC;IACnC,IAAI,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,CAEvB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,QAAQ,CAAC,CAAC,CAAC,CAE3B;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAEhC;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,+CAA+C;IAC/C,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,sCAAsC;IACtC,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,6EAA6E;IAC7E,IAAI,aAAa,IAAI,WAAW,CAK/B;IAED,iFAAiF;IACjF,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5B;;;OAGG;IACH,SAAS,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IA+B9D;;OAEG;IACH,MAAM,IAAI,IAAI;IAOd;;OAEG;IACH,QAAQ,IAAI,IAAI;IAmBhB,oEAAoE;IACpE,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAY5C,0EAA0E;IAC1E,OAAO,IAAI,IAAI;IAef;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC;IAI5D,uEAAuE;IACvE,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAO1C,2FAA2F;IAC3F,SAAS,CAAC,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAMpF,kFAAkF;IAClF,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI;IAC5D,4FAA4F;IAC5F,SAAS,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IACzC,uFAAuF;IACvF,SAAS,CAAC,SAAS,CAAC,IAAI,IAAI;IAE5B,OAAO,CAAC,YAAY;CAgBrB"}
|
package/dist/Service.d.ts
CHANGED
|
@@ -8,13 +8,21 @@ export declare abstract class Service implements Disposable {
|
|
|
8
8
|
private _initialized;
|
|
9
9
|
private _abortController;
|
|
10
10
|
private _cleanups;
|
|
11
|
+
/** Whether this instance has been disposed. */
|
|
11
12
|
get disposed(): boolean;
|
|
13
|
+
/** Whether init() has been called. */
|
|
12
14
|
get initialized(): boolean;
|
|
15
|
+
/** AbortSignal that fires when this instance is disposed. Lazily created. */
|
|
13
16
|
get disposeSignal(): AbortSignal;
|
|
17
|
+
/** Initializes the instance. Called automatically by React hooks after mount. */
|
|
14
18
|
init(): void | Promise<void>;
|
|
19
|
+
/** Tears down the instance, releasing all subscriptions and resources. */
|
|
15
20
|
dispose(): void;
|
|
21
|
+
/** Registers a cleanup function to be called on dispose. @protected */
|
|
16
22
|
protected addCleanup(fn: () => void): void;
|
|
23
|
+
/** Lifecycle hook called at the end of init(). Override to load initial data. @protected */
|
|
17
24
|
protected onInit?(): void | Promise<void>;
|
|
25
|
+
/** Lifecycle hook called during dispose(). Override for custom teardown. @protected */
|
|
18
26
|
protected onDispose?(): void;
|
|
19
27
|
}
|
|
20
28
|
//# sourceMappingURL=Service.d.ts.map
|
package/dist/Service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Service.d.ts","sourceRoot":"","sources":["../src/Service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;;GAGG;AACH,8BAAsB,OAAQ,YAAW,UAAU;IACjD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,SAAS,CAA+B;IAEhD,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,IAAI,aAAa,IAAI,WAAW,CAK/B;IAED,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5B,OAAO,IAAI,IAAI;IAcf,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAO1C,SAAS,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IACzC,SAAS,CAAC,SAAS,CAAC,IAAI,IAAI;CAC7B"}
|
|
1
|
+
{"version":3,"file":"Service.d.ts","sourceRoot":"","sources":["../src/Service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;;GAGG;AACH,8BAAsB,OAAQ,YAAW,UAAU;IACjD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,SAAS,CAA+B;IAEhD,+CAA+C;IAC/C,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,sCAAsC;IACtC,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,6EAA6E;IAC7E,IAAI,aAAa,IAAI,WAAW,CAK/B;IAED,iFAAiF;IACjF,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5B,0EAA0E;IAC1E,OAAO,IAAI,IAAI;IAcf,uEAAuE;IACvE,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAO1C,4FAA4F;IAC5F,SAAS,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IACzC,uFAAuF;IACvF,SAAS,CAAC,SAAS,CAAC,IAAI,IAAI;CAC7B"}
|
package/dist/ViewModel.d.ts
CHANGED
|
@@ -14,7 +14,11 @@ export type AsyncMethodKeys<T, Base = ViewModel<any, any>> = {
|
|
|
14
14
|
type AsyncMap<T> = {
|
|
15
15
|
readonly [K in AsyncMethodKeys<T>]: TaskState;
|
|
16
16
|
};
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Reactive state container with computed getters, automatic async tracking, and typed events.
|
|
19
|
+
* Subclass and define state shape, getters, and action methods. Use with `useLocal` in React.
|
|
20
|
+
*/
|
|
21
|
+
export declare abstract class ViewModel<S extends object = {}, E extends Record<string, any> = {}> implements Subscribable<S> {
|
|
18
22
|
private _state;
|
|
19
23
|
private _initialState;
|
|
20
24
|
private _disposed;
|
|
@@ -33,13 +37,20 @@ export declare abstract class ViewModel<S extends object, E extends Record<strin
|
|
|
33
37
|
private _asyncListeners;
|
|
34
38
|
private _asyncProxy;
|
|
35
39
|
private _activeOps;
|
|
40
|
+
/** DEV-only timeout (ms) for detecting ghost async operations after dispose. */
|
|
36
41
|
static GHOST_TIMEOUT: number;
|
|
37
42
|
constructor(initialState: S);
|
|
43
|
+
/** Current frozen state object. */
|
|
38
44
|
get state(): Readonly<S>;
|
|
45
|
+
/** Whether this instance has been disposed. */
|
|
39
46
|
get disposed(): boolean;
|
|
47
|
+
/** Whether init() has been called. */
|
|
40
48
|
get initialized(): boolean;
|
|
49
|
+
/** AbortSignal that fires when this instance is disposed. Lazily created. */
|
|
41
50
|
get disposeSignal(): AbortSignal;
|
|
51
|
+
/** Lazily-created typed EventBus for emitting and subscribing to events. */
|
|
42
52
|
get events(): EventBus<E>;
|
|
53
|
+
/** Initializes the instance. Called automatically by React hooks after mount. */
|
|
43
54
|
init(): void | Promise<void>;
|
|
44
55
|
/**
|
|
45
56
|
* Merges partial state into current state. If no values actually
|
|
@@ -52,16 +63,39 @@ export declare abstract class ViewModel<S extends object, E extends Record<strin
|
|
|
52
63
|
* a separate notification path (see _trackSubscribables).
|
|
53
64
|
*/
|
|
54
65
|
protected set(partialOrUpdater: Partial<S> | Updater<S>): void;
|
|
66
|
+
/**
|
|
67
|
+
* Emits a typed event via the internal EventBus.
|
|
68
|
+
* Safe to call during dispose cleanup callbacks.
|
|
69
|
+
* @protected
|
|
70
|
+
*/
|
|
55
71
|
protected emit<K extends keyof E>(event: K, payload: E[K]): void;
|
|
72
|
+
/** Subscribes to state changes. Returns an unsubscribe function. */
|
|
56
73
|
subscribe(listener: Listener<S>): () => void;
|
|
74
|
+
/** Tears down the instance, releasing all subscriptions and resources. */
|
|
57
75
|
dispose(): void;
|
|
76
|
+
/**
|
|
77
|
+
* Resets state to initial values (or provided state), aborts in-flight work,
|
|
78
|
+
* clears async tracking, and re-runs onInit().
|
|
79
|
+
*/
|
|
58
80
|
reset(newState?: S): void | Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Registers a cleanup function to be called on dispose. Used internally for things like method wrapper
|
|
83
|
+
* cleanup and event bus disposal, but can also be used by subclasses for custom cleanup logic.
|
|
84
|
+
* @param fn
|
|
85
|
+
* @protected
|
|
86
|
+
*/
|
|
59
87
|
protected addCleanup(fn: () => void): void;
|
|
88
|
+
/** Subscribes to an external Subscribable with automatic cleanup on dispose. @protected */
|
|
60
89
|
protected subscribeTo<T>(source: Subscribable<T>, listener: Listener<T>): () => void;
|
|
90
|
+
/** Lifecycle hook called after every set() with the previous state. @protected */
|
|
61
91
|
protected onSet?(prev: Readonly<S>, next: Readonly<S>): void;
|
|
92
|
+
/** Lifecycle hook called at the end of init(). Override to load initial data. @protected */
|
|
62
93
|
protected onInit?(): void | Promise<void>;
|
|
94
|
+
/** Lifecycle hook called during dispose(). Override for custom teardown. @protected */
|
|
63
95
|
protected onDispose?(): void;
|
|
96
|
+
/** Proxy providing `TaskState` (loading, error, errorCode) per async method. */
|
|
64
97
|
get async(): AsyncMap<this>;
|
|
98
|
+
/** Subscribes to async state changes. Used by `useAsync` for React integration. */
|
|
65
99
|
subscribeAsync(listener: () => void): () => void;
|
|
66
100
|
private _notifyAsync;
|
|
67
101
|
private _teardownSubscriptions;
|
package/dist/ViewModel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ViewModel.d.ts","sourceRoot":"","sources":["../src/ViewModel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAG1E,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAsBjD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GACtE,IAAI,CAUN;AAMD,MAAM,MAAM,eAAe,CAAC,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI;KAC1D,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK;CAC/F,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC;AAEhC,KAAK,QAAQ,CAAC,CAAC,IAAI;IACjB,QAAQ,EAAE,CAAC,IAAI,eAAe,CAAC,CAAC,CAAC,GAAG,SAAS;CAC9C,CAAC;AAcF,8BAAsB,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,EAAE,CAAE,YAAW,YAAY,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"ViewModel.d.ts","sourceRoot":"","sources":["../src/ViewModel.ts"],"names":[],"mappings":"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"}
|
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 w 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 w?{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 _=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[o,a]of Object.entries(i))o!=="constructor"&&e(o,a,s);s=Object.getPrototypeOf(s)}}const A=Object.freeze({loading:!1,error:null,errorCode:null}),y=["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(_&&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(c=>e[c]!==this._state[c]))return;const o=this._state,a=Object.freeze({...o,...e});this._state=a,this._revision++,this.onSet?.(o,a);for(const c of this._listeners)c(a,o)}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(y.includes(t))throw new Error(`[mvc-kit] "${t}" is a reserved property on ViewModel and cannot be overridden.`)})}_wrapMethods(){for(const i of y)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=[];_&&(this._activeOps=new Map),m(this,g.prototype,(i,o)=>{if(o.get||o.set||typeof o.value!="function"||i.startsWith("_")||z.has(i)||e.has(i))return;e.add(i);const a=o.value;let c=!1;const d=function(...f){if(t._disposed){_&&console.warn(`[mvc-kit] "${i}" called after dispose — ignored.`);return}_&&!t._initialized&&console.warn(`[mvc-kit] "${i}" called before init(). Async tracking is active only after init().`);let h;try{h=a.apply(t,f)}catch(l){throw l}if(!h||typeof h.then!="function")return c||(c=!0,t._asyncStates.delete(i),t._asyncSnapshots.delete(i),t[i]=a.bind(t)),h;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(),_&&t._activeOps&&t._activeOps.set(i,(t._activeOps.get(i)??0)+1),h.then(l=>{if(t._disposed)return l;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(),_&&t._activeOps){const u=(t._activeOps.get(i)??1)-1;u<=0?t._activeOps.delete(i):t._activeOps.set(i,u)}return l},l=>{if(v(l)){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()),_&&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;r.count--,r.loading=r.count>0;const u=T(l);if(r.error=u.message,r.errorCode=u.code,t._asyncSnapshots.set(i,Object.freeze({loading:r.loading,error:u.message,errorCode:u.code})),t._notifyAsync(),_&&t._activeOps){const p=(t._activeOps.get(i)??1)-1;p<=0?t._activeOps.delete(i):t._activeOps.set(i,p)}throw l})};s.push(i),t[i]=d}),s.length>0&&this.addCleanup(()=>{const i=_&&t._activeOps?new Map(t._activeOps):null;for(const o of s)_?t[o]=()=>{console.warn(`[mvc-kit] "${o}" called after dispose — ignored.`)}:t[o]=()=>{};t._asyncListeners.clear(),t._asyncStates.clear(),t._asyncSnapshots.clear(),_&&i&&i.size>0&&t._scheduleGhostCheck(i)})}_scheduleGhostCheck(t){_&&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,o,a,c;Object.defineProperty(this,t,{get:()=>{if(this._disposed||i===this._revision)return s;if(o&&a){let r=!0;for(const[l,u]of a)if(this._state[l]!==u){r=!1;break}if(r&&c)for(const[l,u]of c){const p=this._trackedSources.get(l);if(p&&p.revision!==u){r=!1;break}}if(r)return i=this._revision,s}const d=this._stateTracking,f=this._sourceTracking;this._stateTracking=new Set,this._sourceTracking=new Map;try{s=e.call(this)}catch(r){throw this._stateTracking=d,this._sourceTracking=f,r}o=this._stateTracking;const h=this._sourceTracking;if(this._stateTracking=d,this._sourceTracking=f,d)for(const r of o)d.add(r);if(f)for(const[r,l]of h)f.set(r,l);a=new Map;for(const r of o)a.set(r,this._state[r]);c=new Map;for(const[r,l]of h)c.set(r,l.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(c=>e[c]!==this._state[c]))return;const o=this._state,a=Object.freeze({...o,...e});this._state=a,this.onSet?.(o,a);for(const c of this._listeners)c(a,o)}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 o of s)if(t[o]!==e[o])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=this._items;this._items=Object.freeze([...e,...t]);for(const s of t)this._index.set(s.id,s);this.notify(e)}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(o=>!e.has(o.id));if(s.length===this._items.length)return;const i=this._items;this._items=Object.freeze(s);for(const o of t)this._index.delete(o);this.notify(i)}update(t,e){if(this._disposed)throw new Error("Cannot update disposed Collection");const s=this._items.findIndex(h=>h.id===t);if(s===-1)return;const i=this._items[s],o={...i,...e,id:t};if(!Object.keys(e).some(h=>e[h]!==i[h]))return;const d=this._items,f=[...d];f[s]=o,this._items=Object.freeze(f),this._index.set(t,o),this.notify(d)}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 o=this._calculateDelay(t-1);this._reconnectTimer=setTimeout(()=>{this._reconnectTimer=null,this._attemptConnect(t)},o)}}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=w;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 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;
|
|
2
2
|
//# sourceMappingURL=mvc-kit.cjs.map
|