mvc-kit 2.9.0 → 2.10.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 +2 -0
- package/agent-config/claude-code/skills/guide/anti-patterns.md +64 -0
- package/agent-config/claude-code/skills/guide/api-reference.md +33 -2
- package/agent-config/claude-code/skills/guide/patterns.md +38 -0
- package/agent-config/copilot/copilot-instructions.md +5 -1
- package/agent-config/cursor/cursorrules +5 -1
- package/dist/Channel.cjs +5 -0
- package/dist/Channel.cjs.map +1 -1
- package/dist/Channel.d.ts +1 -0
- package/dist/Channel.d.ts.map +1 -1
- package/dist/Channel.js +5 -0
- package/dist/Channel.js.map +1 -1
- package/dist/Collection.cjs +20 -17
- package/dist/Collection.cjs.map +1 -1
- package/dist/Collection.d.ts +2 -2
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Collection.js +20 -17
- package/dist/Collection.js.map +1 -1
- package/dist/Controller.cjs +5 -0
- package/dist/Controller.cjs.map +1 -1
- package/dist/Controller.d.ts +1 -0
- package/dist/Controller.d.ts.map +1 -1
- package/dist/Controller.js +5 -0
- package/dist/Controller.js.map +1 -1
- package/dist/EventBus.cjs +5 -0
- package/dist/EventBus.cjs.map +1 -1
- package/dist/EventBus.d.ts +1 -0
- package/dist/EventBus.d.ts.map +1 -1
- package/dist/EventBus.js +5 -0
- package/dist/EventBus.js.map +1 -1
- package/dist/Feed.cjs +4 -0
- package/dist/Feed.cjs.map +1 -1
- package/dist/Feed.d.ts +1 -0
- package/dist/Feed.d.ts.map +1 -1
- package/dist/Feed.js +4 -0
- package/dist/Feed.js.map +1 -1
- package/dist/Model.cjs +6 -3
- package/dist/Model.cjs.map +1 -1
- package/dist/Model.d.ts +1 -1
- package/dist/Model.d.ts.map +1 -1
- package/dist/Model.js +6 -3
- package/dist/Model.js.map +1 -1
- package/dist/Pagination.cjs +2 -0
- package/dist/Pagination.cjs.map +1 -1
- package/dist/Pagination.d.ts.map +1 -1
- package/dist/Pagination.js +2 -0
- package/dist/Pagination.js.map +1 -1
- package/dist/Pending.cjs +301 -0
- package/dist/Pending.cjs.map +1 -0
- package/dist/Pending.d.ts +91 -0
- package/dist/Pending.d.ts.map +1 -0
- package/dist/Pending.js +301 -0
- package/dist/Pending.js.map +1 -0
- package/dist/PersistentCollection.cjs +1 -1
- package/dist/PersistentCollection.cjs.map +1 -1
- package/dist/PersistentCollection.d.ts.map +1 -1
- package/dist/PersistentCollection.js +1 -1
- package/dist/PersistentCollection.js.map +1 -1
- package/dist/Selection.cjs +4 -0
- package/dist/Selection.cjs.map +1 -1
- package/dist/Selection.d.ts +1 -0
- package/dist/Selection.d.ts.map +1 -1
- package/dist/Selection.js +4 -0
- package/dist/Selection.js.map +1 -1
- package/dist/Service.cjs +5 -0
- package/dist/Service.cjs.map +1 -1
- package/dist/Service.d.ts +1 -0
- package/dist/Service.d.ts.map +1 -1
- package/dist/Service.js +5 -0
- package/dist/Service.js.map +1 -1
- package/dist/Sorting.cjs +2 -0
- package/dist/Sorting.cjs.map +1 -1
- package/dist/Sorting.d.ts.map +1 -1
- package/dist/Sorting.js +2 -0
- package/dist/Sorting.js.map +1 -1
- package/dist/ViewModel.cjs +45 -17
- package/dist/ViewModel.cjs.map +1 -1
- package/dist/ViewModel.d.ts +13 -4
- package/dist/ViewModel.d.ts.map +1 -1
- package/dist/ViewModel.js +45 -17
- package/dist/ViewModel.js.map +1 -1
- package/dist/bindPublicMethods.cjs +27 -0
- package/dist/bindPublicMethods.cjs.map +1 -0
- package/dist/bindPublicMethods.d.ts +18 -0
- package/dist/bindPublicMethods.d.ts.map +1 -0
- package/dist/bindPublicMethods.js +27 -0
- package/dist/bindPublicMethods.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/mvc-kit.cjs +2 -0
- package/dist/mvc-kit.cjs.map +1 -1
- package/dist/mvc-kit.js +2 -0
- package/dist/mvc-kit.js.map +1 -1
- package/dist/walkPrototypeChain.cjs.map +1 -1
- package/dist/walkPrototypeChain.d.ts +1 -1
- package/dist/walkPrototypeChain.js.map +1 -1
- package/dist/wrapAsyncMethods.cjs +15 -3
- package/dist/wrapAsyncMethods.cjs.map +1 -1
- package/dist/wrapAsyncMethods.d.ts.map +1 -1
- package/dist/wrapAsyncMethods.js +15 -3
- package/dist/wrapAsyncMethods.js.map +1 -1
- package/package.json +2 -1
package/dist/Selection.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Selection.js","sources":["../src/Selection.ts"],"sourcesContent":["/**\n * Key-based selection set with toggle and select-all support.\n * Tracks which items are selected by their key (id).\n * Subscribable — auto-tracked when used as a ViewModel property.\n */\nexport class Selection<K extends string | number = string | number> {\n private _selected: Set<K> = new Set();\n private _listeners = new Set<() => void>();\n private _readonlyView: ReadonlySet<K> = this._selected;\n\n // ── Readable state ──\n\n /** Read-only view of currently selected keys. */\n get selected(): ReadonlySet<K> {\n return this._readonlyView;\n }\n\n /** Number of currently selected items. */\n get count(): number {\n return this._selected.size;\n }\n\n /** Whether any items are currently selected. */\n get hasSelection(): boolean {\n return this._selected.size > 0;\n }\n\n // ── Query ──\n\n /** Check whether a specific key is selected. */\n isSelected(key: K): boolean {\n return this._selected.has(key);\n }\n\n // ── Actions ──\n\n /** Toggle a key's selection state (select if unselected, deselect if selected). */\n toggle(key: K): void {\n if (this._selected.has(key)) {\n this._selected.delete(key);\n } else {\n this._selected.add(key);\n }\n this._publish();\n }\n\n /** Add one or more keys to the selection. */\n select(...keys: K[]): void {\n let changed = false;\n for (const key of keys) {\n if (!this._selected.has(key)) {\n this._selected.add(key);\n changed = true;\n }\n }\n if (changed) this._publish();\n }\n\n /** Remove one or more keys from the selection. */\n deselect(...keys: K[]): void {\n let changed = false;\n for (const key of keys) {\n if (this._selected.has(key)) {\n this._selected.delete(key);\n changed = true;\n }\n }\n if (changed) this._publish();\n }\n\n /** If all selected → deselect all, else select all. */\n toggleAll(allKeys: K[]): void {\n const allSelected = allKeys.length > 0 && allKeys.every(k => this._selected.has(k));\n if (allSelected) {\n this._selected.clear();\n } else {\n for (const key of allKeys) this._selected.add(key);\n }\n this._publish();\n }\n\n /** Replace the entire selection atomically. Single notification. */\n set(...keys: K[]): void {\n // Check if anything actually changed\n if (keys.length === this._selected.size && keys.every(k => this._selected.has(k))) return;\n this._selected.clear();\n for (const key of keys) this._selected.add(key);\n this._publish();\n }\n\n /** Remove all items from the selection. */\n clear(): void {\n if (this._selected.size === 0) return;\n this._selected.clear();\n this._publish();\n }\n\n // ── Utility ──\n\n /** Filter an array to only items whose key is in the selection. */\n selectedFrom<T>(items: T[], keyOf: (item: T) => K): T[] {\n return items.filter(item => this._selected.has(keyOf(item)));\n }\n\n // ── Subscribable interface ──\n\n /** Subscribe to selection changes. Returns an unsubscribe function. */\n subscribe(cb: () => void): () => void {\n this._listeners.add(cb);\n return () => { this._listeners.delete(cb); };\n }\n\n private _publish(): void {\n // Replace readonlyView so reference equality changes (needed for React)\n this._readonlyView = new Set(this._selected);\n for (const cb of this._listeners) cb();\n }\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Selection.js","sources":["../src/Selection.ts"],"sourcesContent":["import { bindPublicMethods } from './bindPublicMethods';\n\n/**\n * Key-based selection set with toggle and select-all support.\n * Tracks which items are selected by their key (id).\n * Subscribable — auto-tracked when used as a ViewModel property.\n */\nexport class Selection<K extends string | number = string | number> {\n private _selected: Set<K> = new Set();\n private _listeners = new Set<() => void>();\n private _readonlyView: ReadonlySet<K> = this._selected;\n\n constructor() {\n bindPublicMethods(this);\n }\n\n // ── Readable state ──\n\n /** Read-only view of currently selected keys. */\n get selected(): ReadonlySet<K> {\n return this._readonlyView;\n }\n\n /** Number of currently selected items. */\n get count(): number {\n return this._selected.size;\n }\n\n /** Whether any items are currently selected. */\n get hasSelection(): boolean {\n return this._selected.size > 0;\n }\n\n // ── Query ──\n\n /** Check whether a specific key is selected. */\n isSelected(key: K): boolean {\n return this._selected.has(key);\n }\n\n // ── Actions ──\n\n /** Toggle a key's selection state (select if unselected, deselect if selected). */\n toggle(key: K): void {\n if (this._selected.has(key)) {\n this._selected.delete(key);\n } else {\n this._selected.add(key);\n }\n this._publish();\n }\n\n /** Add one or more keys to the selection. */\n select(...keys: K[]): void {\n let changed = false;\n for (const key of keys) {\n if (!this._selected.has(key)) {\n this._selected.add(key);\n changed = true;\n }\n }\n if (changed) this._publish();\n }\n\n /** Remove one or more keys from the selection. */\n deselect(...keys: K[]): void {\n let changed = false;\n for (const key of keys) {\n if (this._selected.has(key)) {\n this._selected.delete(key);\n changed = true;\n }\n }\n if (changed) this._publish();\n }\n\n /** If all selected → deselect all, else select all. */\n toggleAll(allKeys: K[]): void {\n const allSelected = allKeys.length > 0 && allKeys.every(k => this._selected.has(k));\n if (allSelected) {\n this._selected.clear();\n } else {\n for (const key of allKeys) this._selected.add(key);\n }\n this._publish();\n }\n\n /** Replace the entire selection atomically. Single notification. */\n set(...keys: K[]): void {\n // Check if anything actually changed\n if (keys.length === this._selected.size && keys.every(k => this._selected.has(k))) return;\n this._selected.clear();\n for (const key of keys) this._selected.add(key);\n this._publish();\n }\n\n /** Remove all items from the selection. */\n clear(): void {\n if (this._selected.size === 0) return;\n this._selected.clear();\n this._publish();\n }\n\n // ── Utility ──\n\n /** Filter an array to only items whose key is in the selection. */\n selectedFrom<T>(items: T[], keyOf: (item: T) => K): T[] {\n return items.filter(item => this._selected.has(keyOf(item)));\n }\n\n // ── Subscribable interface ──\n\n /** Subscribe to selection changes. Returns an unsubscribe function. */\n subscribe(cb: () => void): () => void {\n this._listeners.add(cb);\n return () => { this._listeners.delete(cb); };\n }\n\n private _publish(): void {\n // Replace readonlyView so reference equality changes (needed for React)\n this._readonlyView = new Set(this._selected);\n for (const cb of this._listeners) cb();\n }\n}\n"],"names":[],"mappings":";AAOO,MAAM,UAAuD;AAAA,EAC1D,gCAAwB,IAAA;AAAA,EACxB,iCAAiB,IAAA;AAAA,EACjB,gBAAgC,KAAK;AAAA,EAE7C,cAAc;AACZ,sBAAkB,IAAI;AAAA,EACxB;AAAA;AAAA;AAAA,EAKA,IAAI,WAA2B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,QAAgB;AAClB,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA;AAAA,EAGA,IAAI,eAAwB;AAC1B,WAAO,KAAK,UAAU,OAAO;AAAA,EAC/B;AAAA;AAAA;AAAA,EAKA,WAAW,KAAiB;AAC1B,WAAO,KAAK,UAAU,IAAI,GAAG;AAAA,EAC/B;AAAA;AAAA;AAAA,EAKA,OAAO,KAAc;AACnB,QAAI,KAAK,UAAU,IAAI,GAAG,GAAG;AAC3B,WAAK,UAAU,OAAO,GAAG;AAAA,IAC3B,OAAO;AACL,WAAK,UAAU,IAAI,GAAG;AAAA,IACxB;AACA,SAAK,SAAA;AAAA,EACP;AAAA;AAAA,EAGA,UAAU,MAAiB;AACzB,QAAI,UAAU;AACd,eAAW,OAAO,MAAM;AACtB,UAAI,CAAC,KAAK,UAAU,IAAI,GAAG,GAAG;AAC5B,aAAK,UAAU,IAAI,GAAG;AACtB,kBAAU;AAAA,MACZ;AAAA,IACF;AACA,QAAI,cAAc,SAAA;AAAA,EACpB;AAAA;AAAA,EAGA,YAAY,MAAiB;AAC3B,QAAI,UAAU;AACd,eAAW,OAAO,MAAM;AACtB,UAAI,KAAK,UAAU,IAAI,GAAG,GAAG;AAC3B,aAAK,UAAU,OAAO,GAAG;AACzB,kBAAU;AAAA,MACZ;AAAA,IACF;AACA,QAAI,cAAc,SAAA;AAAA,EACpB;AAAA;AAAA,EAGA,UAAU,SAAoB;AAC5B,UAAM,cAAc,QAAQ,SAAS,KAAK,QAAQ,MAAM,CAAA,MAAK,KAAK,UAAU,IAAI,CAAC,CAAC;AAClF,QAAI,aAAa;AACf,WAAK,UAAU,MAAA;AAAA,IACjB,OAAO;AACL,iBAAW,OAAO,QAAS,MAAK,UAAU,IAAI,GAAG;AAAA,IACnD;AACA,SAAK,SAAA;AAAA,EACP;AAAA;AAAA,EAGA,OAAO,MAAiB;AAEtB,QAAI,KAAK,WAAW,KAAK,UAAU,QAAQ,KAAK,MAAM,CAAA,MAAK,KAAK,UAAU,IAAI,CAAC,CAAC,EAAG;AACnF,SAAK,UAAU,MAAA;AACf,eAAW,OAAO,KAAM,MAAK,UAAU,IAAI,GAAG;AAC9C,SAAK,SAAA;AAAA,EACP;AAAA;AAAA,EAGA,QAAc;AACZ,QAAI,KAAK,UAAU,SAAS,EAAG;AAC/B,SAAK,UAAU,MAAA;AACf,SAAK,SAAA;AAAA,EACP;AAAA;AAAA;AAAA,EAKA,aAAgB,OAAY,OAA4B;AACtD,WAAO,MAAM,OAAO,CAAA,SAAQ,KAAK,UAAU,IAAI,MAAM,IAAI,CAAC,CAAC;AAAA,EAC7D;AAAA;AAAA;AAAA,EAKA,UAAU,IAA4B;AACpC,SAAK,WAAW,IAAI,EAAE;AACtB,WAAO,MAAM;AAAE,WAAK,WAAW,OAAO,EAAE;AAAA,IAAG;AAAA,EAC7C;AAAA,EAEQ,WAAiB;AAEvB,SAAK,gBAAgB,IAAI,IAAI,KAAK,SAAS;AAC3C,eAAW,MAAM,KAAK,WAAY,IAAA;AAAA,EACpC;AACF;"}
|
package/dist/Service.cjs
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const bindPublicMethods = require("./bindPublicMethods.cjs");
|
|
4
|
+
const PROTECTED_KEYS = /* @__PURE__ */ new Set(["addCleanup"]);
|
|
3
5
|
class Service {
|
|
4
6
|
_disposed = false;
|
|
5
7
|
_initialized = false;
|
|
6
8
|
_abortController = null;
|
|
7
9
|
_cleanups = null;
|
|
10
|
+
constructor() {
|
|
11
|
+
bindPublicMethods.bindPublicMethods(this, Object.prototype, PROTECTED_KEYS);
|
|
12
|
+
}
|
|
8
13
|
/** Whether this instance has been disposed. */
|
|
9
14
|
get disposed() {
|
|
10
15
|
return this._disposed;
|
package/dist/Service.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Service.cjs","sources":["../src/Service.ts"],"sourcesContent":["import type { Disposable } from './types';\n\n/**\n * Base class for non-reactive infrastructure services.\n * Services encapsulate external dependencies like APIs, storage, etc.\n */\nexport abstract class Service implements Disposable {\n private _disposed = false;\n private _initialized = false;\n private _abortController: AbortController | null = null;\n private _cleanups: (() => void)[] | null = null;\n\n /** Whether this instance has been disposed. */\n get disposed(): boolean {\n return this._disposed;\n }\n\n /** Whether init() has been called. */\n get initialized(): boolean {\n return this._initialized;\n }\n\n /** AbortSignal that fires when this instance is disposed. Lazily created. */\n get disposeSignal(): AbortSignal {\n if (!this._abortController) {\n this._abortController = new AbortController();\n }\n return this._abortController.signal;\n }\n\n /** Initializes the instance. Called automatically by React hooks after mount. */\n init(): void | Promise<void> {\n if (this._initialized || this._disposed) return;\n this._initialized = true;\n return this.onInit?.();\n }\n\n /** Tears down the instance, releasing all subscriptions and resources. */\n dispose(): void {\n if (this._disposed) {\n return;\n }\n\n this._disposed = true;\n this._abortController?.abort();\n if (this._cleanups) {\n for (const fn of this._cleanups) fn();\n this._cleanups = null;\n }\n this.onDispose?.();\n }\n\n /** Registers a cleanup function to be called on dispose. @protected */\n protected addCleanup(fn: () => void): void {\n if (!this._cleanups) {\n this._cleanups = [];\n }\n this._cleanups.push(fn);\n }\n\n /** Lifecycle hook called at the end of init(). Override to load initial data. @protected */\n protected onInit?(): void | Promise<void>;\n /** Lifecycle hook called during dispose(). Override for custom teardown. @protected */\n protected onDispose?(): void;\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Service.cjs","sources":["../src/Service.ts"],"sourcesContent":["import type { Disposable } from './types';\nimport { bindPublicMethods } from './bindPublicMethods';\n\nconst PROTECTED_KEYS = new Set(['addCleanup']);\n\n/**\n * Base class for non-reactive infrastructure services.\n * Services encapsulate external dependencies like APIs, storage, etc.\n */\nexport abstract class Service implements Disposable {\n private _disposed = false;\n private _initialized = false;\n private _abortController: AbortController | null = null;\n private _cleanups: (() => void)[] | null = null;\n\n constructor() {\n bindPublicMethods(this, Object.prototype, PROTECTED_KEYS);\n }\n\n /** Whether this instance has been disposed. */\n get disposed(): boolean {\n return this._disposed;\n }\n\n /** Whether init() has been called. */\n get initialized(): boolean {\n return this._initialized;\n }\n\n /** AbortSignal that fires when this instance is disposed. Lazily created. */\n get disposeSignal(): AbortSignal {\n if (!this._abortController) {\n this._abortController = new AbortController();\n }\n return this._abortController.signal;\n }\n\n /** Initializes the instance. Called automatically by React hooks after mount. */\n init(): void | Promise<void> {\n if (this._initialized || this._disposed) return;\n this._initialized = true;\n return this.onInit?.();\n }\n\n /** Tears down the instance, releasing all subscriptions and resources. */\n dispose(): void {\n if (this._disposed) {\n return;\n }\n\n this._disposed = true;\n this._abortController?.abort();\n if (this._cleanups) {\n for (const fn of this._cleanups) fn();\n this._cleanups = null;\n }\n this.onDispose?.();\n }\n\n /** Registers a cleanup function to be called on dispose. @protected */\n protected addCleanup(fn: () => void): void {\n if (!this._cleanups) {\n this._cleanups = [];\n }\n this._cleanups.push(fn);\n }\n\n /** Lifecycle hook called at the end of init(). Override to load initial data. @protected */\n protected onInit?(): void | Promise<void>;\n /** Lifecycle hook called during dispose(). Override for custom teardown. @protected */\n protected onDispose?(): void;\n}\n"],"names":["bindPublicMethods"],"mappings":";;;AAGA,MAAM,iBAAiB,oBAAI,IAAI,CAAC,YAAY,CAAC;AAMtC,MAAe,QAA8B;AAAA,EAC1C,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,mBAA2C;AAAA,EAC3C,YAAmC;AAAA,EAE3C,cAAc;AACZA,sBAAAA,kBAAkB,MAAM,OAAO,WAAW,cAAc;AAAA,EAC1D;AAAA;AAAA,EAGA,IAAI,WAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,cAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,gBAA6B;AAC/B,QAAI,CAAC,KAAK,kBAAkB;AAC1B,WAAK,mBAAmB,IAAI,gBAAA;AAAA,IAC9B;AACA,WAAO,KAAK,iBAAiB;AAAA,EAC/B;AAAA;AAAA,EAGA,OAA6B;AAC3B,QAAI,KAAK,gBAAgB,KAAK,UAAW;AACzC,SAAK,eAAe;AACpB,WAAO,KAAK,SAAA;AAAA,EACd;AAAA;AAAA,EAGA,UAAgB;AACd,QAAI,KAAK,WAAW;AAClB;AAAA,IACF;AAEA,SAAK,YAAY;AACjB,SAAK,kBAAkB,MAAA;AACvB,QAAI,KAAK,WAAW;AAClB,iBAAW,MAAM,KAAK,UAAW,IAAA;AACjC,WAAK,YAAY;AAAA,IACnB;AACA,SAAK,YAAA;AAAA,EACP;AAAA;AAAA,EAGU,WAAW,IAAsB;AACzC,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,YAAY,CAAA;AAAA,IACnB;AACA,SAAK,UAAU,KAAK,EAAE;AAAA,EACxB;AAMF;;"}
|
package/dist/Service.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare abstract class Service implements Disposable {
|
|
|
8
8
|
private _initialized;
|
|
9
9
|
private _abortController;
|
|
10
10
|
private _cleanups;
|
|
11
|
+
constructor();
|
|
11
12
|
/** Whether this instance has been disposed. */
|
|
12
13
|
get disposed(): boolean;
|
|
13
14
|
/** Whether init() has been called. */
|
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;
|
|
1
|
+
{"version":3,"file":"Service.d.ts","sourceRoot":"","sources":["../src/Service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAK1C;;;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;;IAMhD,+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/Service.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { bindPublicMethods } from "./bindPublicMethods.js";
|
|
2
|
+
const PROTECTED_KEYS = /* @__PURE__ */ new Set(["addCleanup"]);
|
|
1
3
|
class Service {
|
|
2
4
|
_disposed = false;
|
|
3
5
|
_initialized = false;
|
|
4
6
|
_abortController = null;
|
|
5
7
|
_cleanups = null;
|
|
8
|
+
constructor() {
|
|
9
|
+
bindPublicMethods(this, Object.prototype, PROTECTED_KEYS);
|
|
10
|
+
}
|
|
6
11
|
/** Whether this instance has been disposed. */
|
|
7
12
|
get disposed() {
|
|
8
13
|
return this._disposed;
|
package/dist/Service.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Service.js","sources":["../src/Service.ts"],"sourcesContent":["import type { Disposable } from './types';\n\n/**\n * Base class for non-reactive infrastructure services.\n * Services encapsulate external dependencies like APIs, storage, etc.\n */\nexport abstract class Service implements Disposable {\n private _disposed = false;\n private _initialized = false;\n private _abortController: AbortController | null = null;\n private _cleanups: (() => void)[] | null = null;\n\n /** Whether this instance has been disposed. */\n get disposed(): boolean {\n return this._disposed;\n }\n\n /** Whether init() has been called. */\n get initialized(): boolean {\n return this._initialized;\n }\n\n /** AbortSignal that fires when this instance is disposed. Lazily created. */\n get disposeSignal(): AbortSignal {\n if (!this._abortController) {\n this._abortController = new AbortController();\n }\n return this._abortController.signal;\n }\n\n /** Initializes the instance. Called automatically by React hooks after mount. */\n init(): void | Promise<void> {\n if (this._initialized || this._disposed) return;\n this._initialized = true;\n return this.onInit?.();\n }\n\n /** Tears down the instance, releasing all subscriptions and resources. */\n dispose(): void {\n if (this._disposed) {\n return;\n }\n\n this._disposed = true;\n this._abortController?.abort();\n if (this._cleanups) {\n for (const fn of this._cleanups) fn();\n this._cleanups = null;\n }\n this.onDispose?.();\n }\n\n /** Registers a cleanup function to be called on dispose. @protected */\n protected addCleanup(fn: () => void): void {\n if (!this._cleanups) {\n this._cleanups = [];\n }\n this._cleanups.push(fn);\n }\n\n /** Lifecycle hook called at the end of init(). Override to load initial data. @protected */\n protected onInit?(): void | Promise<void>;\n /** Lifecycle hook called during dispose(). Override for custom teardown. @protected */\n protected onDispose?(): void;\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Service.js","sources":["../src/Service.ts"],"sourcesContent":["import type { Disposable } from './types';\nimport { bindPublicMethods } from './bindPublicMethods';\n\nconst PROTECTED_KEYS = new Set(['addCleanup']);\n\n/**\n * Base class for non-reactive infrastructure services.\n * Services encapsulate external dependencies like APIs, storage, etc.\n */\nexport abstract class Service implements Disposable {\n private _disposed = false;\n private _initialized = false;\n private _abortController: AbortController | null = null;\n private _cleanups: (() => void)[] | null = null;\n\n constructor() {\n bindPublicMethods(this, Object.prototype, PROTECTED_KEYS);\n }\n\n /** Whether this instance has been disposed. */\n get disposed(): boolean {\n return this._disposed;\n }\n\n /** Whether init() has been called. */\n get initialized(): boolean {\n return this._initialized;\n }\n\n /** AbortSignal that fires when this instance is disposed. Lazily created. */\n get disposeSignal(): AbortSignal {\n if (!this._abortController) {\n this._abortController = new AbortController();\n }\n return this._abortController.signal;\n }\n\n /** Initializes the instance. Called automatically by React hooks after mount. */\n init(): void | Promise<void> {\n if (this._initialized || this._disposed) return;\n this._initialized = true;\n return this.onInit?.();\n }\n\n /** Tears down the instance, releasing all subscriptions and resources. */\n dispose(): void {\n if (this._disposed) {\n return;\n }\n\n this._disposed = true;\n this._abortController?.abort();\n if (this._cleanups) {\n for (const fn of this._cleanups) fn();\n this._cleanups = null;\n }\n this.onDispose?.();\n }\n\n /** Registers a cleanup function to be called on dispose. @protected */\n protected addCleanup(fn: () => void): void {\n if (!this._cleanups) {\n this._cleanups = [];\n }\n this._cleanups.push(fn);\n }\n\n /** Lifecycle hook called at the end of init(). Override to load initial data. @protected */\n protected onInit?(): void | Promise<void>;\n /** Lifecycle hook called during dispose(). Override for custom teardown. @protected */\n protected onDispose?(): void;\n}\n"],"names":[],"mappings":";AAGA,MAAM,iBAAiB,oBAAI,IAAI,CAAC,YAAY,CAAC;AAMtC,MAAe,QAA8B;AAAA,EAC1C,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,mBAA2C;AAAA,EAC3C,YAAmC;AAAA,EAE3C,cAAc;AACZ,sBAAkB,MAAM,OAAO,WAAW,cAAc;AAAA,EAC1D;AAAA;AAAA,EAGA,IAAI,WAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,cAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,gBAA6B;AAC/B,QAAI,CAAC,KAAK,kBAAkB;AAC1B,WAAK,mBAAmB,IAAI,gBAAA;AAAA,IAC9B;AACA,WAAO,KAAK,iBAAiB;AAAA,EAC/B;AAAA;AAAA,EAGA,OAA6B;AAC3B,QAAI,KAAK,gBAAgB,KAAK,UAAW;AACzC,SAAK,eAAe;AACpB,WAAO,KAAK,SAAA;AAAA,EACd;AAAA;AAAA,EAGA,UAAgB;AACd,QAAI,KAAK,WAAW;AAClB;AAAA,IACF;AAEA,SAAK,YAAY;AACjB,SAAK,kBAAkB,MAAA;AACvB,QAAI,KAAK,WAAW;AAClB,iBAAW,MAAM,KAAK,UAAW,IAAA;AACjC,WAAK,YAAY;AAAA,IACnB;AACA,SAAK,YAAA;AAAA,EACP;AAAA;AAAA,EAGU,WAAW,IAAsB;AACzC,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,YAAY,CAAA;AAAA,IACnB;AACA,SAAK,UAAU,KAAK,EAAE;AAAA,EACxB;AAMF;"}
|
package/dist/Sorting.cjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const bindPublicMethods = require("./bindPublicMethods.cjs");
|
|
3
4
|
class Sorting {
|
|
4
5
|
_sorts;
|
|
5
6
|
_listeners = /* @__PURE__ */ new Set();
|
|
6
7
|
constructor(options) {
|
|
7
8
|
this._sorts = Object.freeze(options?.sorts?.map((s) => ({ ...s })) ?? []);
|
|
9
|
+
bindPublicMethods.bindPublicMethods(this);
|
|
8
10
|
}
|
|
9
11
|
// ── Readable state ──
|
|
10
12
|
/** Current list of active sort descriptors, in priority order. */
|
package/dist/Sorting.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sorting.cjs","sources":["../src/Sorting.ts"],"sourcesContent":["/** Describes a single sort column with key and direction. */\nexport interface SortDescriptor {\n key: string;\n direction: 'asc' | 'desc';\n}\n\n/**\n * Multi-column sort state manager with a comparator pipeline.\n * Maintains an ordered list of sort descriptors and applies them to arrays.\n * Subscribable — auto-tracked when used as a ViewModel property.\n */\nexport class Sorting<T = any> {\n private _sorts: readonly SortDescriptor[];\n private _listeners = new Set<() => void>();\n\n constructor(options?: { sorts?: SortDescriptor[] }) {\n this._sorts = Object.freeze(options?.sorts?.map(s => ({ ...s })) ?? []);\n }\n\n // ── Readable state ──\n\n /** Current list of active sort descriptors, in priority order. */\n get sorts(): readonly SortDescriptor[] {\n return this._sorts;\n }\n\n /** Primary sort key (first descriptor), or null when empty. */\n get key(): string | null {\n return this._sorts.length > 0 ? this._sorts[0].key : null;\n }\n\n /** Primary sort direction. Defaults to 'asc' when empty. */\n get direction(): 'asc' | 'desc' {\n return this._sorts.length > 0 ? this._sorts[0].direction : 'asc';\n }\n\n // ── Query ──\n\n /** Whether the given key is currently sorted. */\n isSorted(key: string): boolean {\n return this._sorts.some(s => s.key === key);\n }\n\n /** Returns the sort direction for a key, or null if not sorted. */\n directionOf(key: string): 'asc' | 'desc' | null {\n const found = this._sorts.find(s => s.key === key);\n return found ? found.direction : null;\n }\n\n /** Returns the priority index of a sorted key, or -1 if not sorted. */\n indexOf(key: string): number {\n return this._sorts.findIndex(s => s.key === key);\n }\n\n // ── Actions ──\n\n /** 3-click cycle: not sorted → asc → desc → removed. */\n toggle(key: string): void {\n const idx = this.indexOf(key);\n if (idx === -1) {\n // Add as asc\n this._sorts = Object.freeze([...this._sorts, { key, direction: 'asc' as const }]);\n } else if (this._sorts[idx].direction === 'asc') {\n // Flip to desc\n const next = this._sorts.map((s, i) =>\n i === idx ? { key: s.key, direction: 'desc' as const } : s\n );\n this._sorts = Object.freeze(next);\n } else {\n // Remove\n this._sorts = Object.freeze(this._sorts.filter((_, i) => i !== idx));\n }\n this._notify();\n }\n\n /** Replace all with a single sort. */\n setSort(key: string, direction: 'asc' | 'desc'): void {\n this._sorts = Object.freeze([{ key, direction }]);\n this._notify();\n }\n\n /** Replace all sorts. */\n setSorts(sorts: SortDescriptor[]): void {\n this._sorts = Object.freeze(sorts.map(s => ({ ...s })));\n this._notify();\n }\n\n /** Clear all sort descriptors. */\n reset(): void {\n this._sorts = Object.freeze([]);\n this._notify();\n }\n\n // ── Pipeline ──\n\n /** Sort an array using the current descriptors. Returns a new sorted array. */\n apply(\n items: T[],\n compareFn?: (a: T, b: T, key: string, dir: 'asc' | 'desc') => number,\n ): T[] {\n if (this._sorts.length === 0) return items;\n const sorted = items.slice();\n const sorts = this._sorts;\n sorted.sort((a, b) => {\n for (const { key, direction } of sorts) {\n let cmp: number;\n if (compareFn) {\n cmp = compareFn(a, b, key, direction);\n } else {\n cmp = defaultCompare(a, b, key);\n }\n if (direction === 'desc') cmp = -cmp;\n if (cmp !== 0) return cmp;\n }\n return 0;\n });\n return sorted;\n }\n\n // ── Subscribable interface ──\n\n /** Subscribe to sort state changes. Returns an unsubscribe function. */\n subscribe(cb: () => void): () => void {\n this._listeners.add(cb);\n return () => { this._listeners.delete(cb); };\n }\n\n private _notify(): void {\n for (const cb of this._listeners) cb();\n }\n}\n\nfunction defaultCompare(a: any, b: any, key: string): number {\n const aVal = a[key];\n const bVal = b[key];\n if (aVal == null && bVal == null) return 0;\n if (aVal == null) return -1;\n if (bVal == null) return 1;\n if (typeof aVal === 'string' && typeof bVal === 'string') {\n return aVal.localeCompare(bVal);\n }\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Sorting.cjs","sources":["../src/Sorting.ts"],"sourcesContent":["import { bindPublicMethods } from './bindPublicMethods';\n\n/** Describes a single sort column with key and direction. */\nexport interface SortDescriptor {\n key: string;\n direction: 'asc' | 'desc';\n}\n\n/**\n * Multi-column sort state manager with a comparator pipeline.\n * Maintains an ordered list of sort descriptors and applies them to arrays.\n * Subscribable — auto-tracked when used as a ViewModel property.\n */\nexport class Sorting<T = any> {\n private _sorts: readonly SortDescriptor[];\n private _listeners = new Set<() => void>();\n\n constructor(options?: { sorts?: SortDescriptor[] }) {\n this._sorts = Object.freeze(options?.sorts?.map(s => ({ ...s })) ?? []);\n bindPublicMethods(this);\n }\n\n // ── Readable state ──\n\n /** Current list of active sort descriptors, in priority order. */\n get sorts(): readonly SortDescriptor[] {\n return this._sorts;\n }\n\n /** Primary sort key (first descriptor), or null when empty. */\n get key(): string | null {\n return this._sorts.length > 0 ? this._sorts[0].key : null;\n }\n\n /** Primary sort direction. Defaults to 'asc' when empty. */\n get direction(): 'asc' | 'desc' {\n return this._sorts.length > 0 ? this._sorts[0].direction : 'asc';\n }\n\n // ── Query ──\n\n /** Whether the given key is currently sorted. */\n isSorted(key: string): boolean {\n return this._sorts.some(s => s.key === key);\n }\n\n /** Returns the sort direction for a key, or null if not sorted. */\n directionOf(key: string): 'asc' | 'desc' | null {\n const found = this._sorts.find(s => s.key === key);\n return found ? found.direction : null;\n }\n\n /** Returns the priority index of a sorted key, or -1 if not sorted. */\n indexOf(key: string): number {\n return this._sorts.findIndex(s => s.key === key);\n }\n\n // ── Actions ──\n\n /** 3-click cycle: not sorted → asc → desc → removed. */\n toggle(key: string): void {\n const idx = this.indexOf(key);\n if (idx === -1) {\n // Add as asc\n this._sorts = Object.freeze([...this._sorts, { key, direction: 'asc' as const }]);\n } else if (this._sorts[idx].direction === 'asc') {\n // Flip to desc\n const next = this._sorts.map((s, i) =>\n i === idx ? { key: s.key, direction: 'desc' as const } : s\n );\n this._sorts = Object.freeze(next);\n } else {\n // Remove\n this._sorts = Object.freeze(this._sorts.filter((_, i) => i !== idx));\n }\n this._notify();\n }\n\n /** Replace all with a single sort. */\n setSort(key: string, direction: 'asc' | 'desc'): void {\n this._sorts = Object.freeze([{ key, direction }]);\n this._notify();\n }\n\n /** Replace all sorts. */\n setSorts(sorts: SortDescriptor[]): void {\n this._sorts = Object.freeze(sorts.map(s => ({ ...s })));\n this._notify();\n }\n\n /** Clear all sort descriptors. */\n reset(): void {\n this._sorts = Object.freeze([]);\n this._notify();\n }\n\n // ── Pipeline ──\n\n /** Sort an array using the current descriptors. Returns a new sorted array. */\n apply(\n items: T[],\n compareFn?: (a: T, b: T, key: string, dir: 'asc' | 'desc') => number,\n ): T[] {\n if (this._sorts.length === 0) return items;\n const sorted = items.slice();\n const sorts = this._sorts;\n sorted.sort((a, b) => {\n for (const { key, direction } of sorts) {\n let cmp: number;\n if (compareFn) {\n cmp = compareFn(a, b, key, direction);\n } else {\n cmp = defaultCompare(a, b, key);\n }\n if (direction === 'desc') cmp = -cmp;\n if (cmp !== 0) return cmp;\n }\n return 0;\n });\n return sorted;\n }\n\n // ── Subscribable interface ──\n\n /** Subscribe to sort state changes. Returns an unsubscribe function. */\n subscribe(cb: () => void): () => void {\n this._listeners.add(cb);\n return () => { this._listeners.delete(cb); };\n }\n\n private _notify(): void {\n for (const cb of this._listeners) cb();\n }\n}\n\nfunction defaultCompare(a: any, b: any, key: string): number {\n const aVal = a[key];\n const bVal = b[key];\n if (aVal == null && bVal == null) return 0;\n if (aVal == null) return -1;\n if (bVal == null) return 1;\n if (typeof aVal === 'string' && typeof bVal === 'string') {\n return aVal.localeCompare(bVal);\n }\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n}\n"],"names":["bindPublicMethods"],"mappings":"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"}
|
package/dist/Sorting.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sorting.d.ts","sourceRoot":"","sources":["../src/Sorting.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Sorting.d.ts","sourceRoot":"","sources":["../src/Sorting.ts"],"names":[],"mappings":"AAEA,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,KAAK,GAAG,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,qBAAa,OAAO,CAAC,CAAC,GAAG,GAAG;IAC1B,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,UAAU,CAAyB;gBAE/B,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,cAAc,EAAE,CAAA;KAAE;IAOlD,kEAAkE;IAClE,IAAI,KAAK,IAAI,SAAS,cAAc,EAAE,CAErC;IAED,+DAA+D;IAC/D,IAAI,GAAG,IAAI,MAAM,GAAG,IAAI,CAEvB;IAED,4DAA4D;IAC5D,IAAI,SAAS,IAAI,KAAK,GAAG,MAAM,CAE9B;IAID,iDAAiD;IACjD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI9B,mEAAmE;IACnE,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI;IAK/C,uEAAuE;IACvE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAM5B,wDAAwD;IACxD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAkBzB,sCAAsC;IACtC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI;IAKrD,yBAAyB;IACzB,QAAQ,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,IAAI;IAKvC,kCAAkC;IAClC,KAAK,IAAI,IAAI;IAOb,+EAA+E;IAC/E,KAAK,CACH,KAAK,EAAE,CAAC,EAAE,EACV,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,GAAG,MAAM,KAAK,MAAM,GACnE,CAAC,EAAE;IAsBN,wEAAwE;IACxE,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAKrC,OAAO,CAAC,OAAO;CAGhB"}
|
package/dist/Sorting.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { bindPublicMethods } from "./bindPublicMethods.js";
|
|
1
2
|
class Sorting {
|
|
2
3
|
_sorts;
|
|
3
4
|
_listeners = /* @__PURE__ */ new Set();
|
|
4
5
|
constructor(options) {
|
|
5
6
|
this._sorts = Object.freeze(options?.sorts?.map((s) => ({ ...s })) ?? []);
|
|
7
|
+
bindPublicMethods(this);
|
|
6
8
|
}
|
|
7
9
|
// ── Readable state ──
|
|
8
10
|
/** Current list of active sort descriptors, in priority order. */
|
package/dist/Sorting.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sorting.js","sources":["../src/Sorting.ts"],"sourcesContent":["/** Describes a single sort column with key and direction. */\nexport interface SortDescriptor {\n key: string;\n direction: 'asc' | 'desc';\n}\n\n/**\n * Multi-column sort state manager with a comparator pipeline.\n * Maintains an ordered list of sort descriptors and applies them to arrays.\n * Subscribable — auto-tracked when used as a ViewModel property.\n */\nexport class Sorting<T = any> {\n private _sorts: readonly SortDescriptor[];\n private _listeners = new Set<() => void>();\n\n constructor(options?: { sorts?: SortDescriptor[] }) {\n this._sorts = Object.freeze(options?.sorts?.map(s => ({ ...s })) ?? []);\n }\n\n // ── Readable state ──\n\n /** Current list of active sort descriptors, in priority order. */\n get sorts(): readonly SortDescriptor[] {\n return this._sorts;\n }\n\n /** Primary sort key (first descriptor), or null when empty. */\n get key(): string | null {\n return this._sorts.length > 0 ? this._sorts[0].key : null;\n }\n\n /** Primary sort direction. Defaults to 'asc' when empty. */\n get direction(): 'asc' | 'desc' {\n return this._sorts.length > 0 ? this._sorts[0].direction : 'asc';\n }\n\n // ── Query ──\n\n /** Whether the given key is currently sorted. */\n isSorted(key: string): boolean {\n return this._sorts.some(s => s.key === key);\n }\n\n /** Returns the sort direction for a key, or null if not sorted. */\n directionOf(key: string): 'asc' | 'desc' | null {\n const found = this._sorts.find(s => s.key === key);\n return found ? found.direction : null;\n }\n\n /** Returns the priority index of a sorted key, or -1 if not sorted. */\n indexOf(key: string): number {\n return this._sorts.findIndex(s => s.key === key);\n }\n\n // ── Actions ──\n\n /** 3-click cycle: not sorted → asc → desc → removed. */\n toggle(key: string): void {\n const idx = this.indexOf(key);\n if (idx === -1) {\n // Add as asc\n this._sorts = Object.freeze([...this._sorts, { key, direction: 'asc' as const }]);\n } else if (this._sorts[idx].direction === 'asc') {\n // Flip to desc\n const next = this._sorts.map((s, i) =>\n i === idx ? { key: s.key, direction: 'desc' as const } : s\n );\n this._sorts = Object.freeze(next);\n } else {\n // Remove\n this._sorts = Object.freeze(this._sorts.filter((_, i) => i !== idx));\n }\n this._notify();\n }\n\n /** Replace all with a single sort. */\n setSort(key: string, direction: 'asc' | 'desc'): void {\n this._sorts = Object.freeze([{ key, direction }]);\n this._notify();\n }\n\n /** Replace all sorts. */\n setSorts(sorts: SortDescriptor[]): void {\n this._sorts = Object.freeze(sorts.map(s => ({ ...s })));\n this._notify();\n }\n\n /** Clear all sort descriptors. */\n reset(): void {\n this._sorts = Object.freeze([]);\n this._notify();\n }\n\n // ── Pipeline ──\n\n /** Sort an array using the current descriptors. Returns a new sorted array. */\n apply(\n items: T[],\n compareFn?: (a: T, b: T, key: string, dir: 'asc' | 'desc') => number,\n ): T[] {\n if (this._sorts.length === 0) return items;\n const sorted = items.slice();\n const sorts = this._sorts;\n sorted.sort((a, b) => {\n for (const { key, direction } of sorts) {\n let cmp: number;\n if (compareFn) {\n cmp = compareFn(a, b, key, direction);\n } else {\n cmp = defaultCompare(a, b, key);\n }\n if (direction === 'desc') cmp = -cmp;\n if (cmp !== 0) return cmp;\n }\n return 0;\n });\n return sorted;\n }\n\n // ── Subscribable interface ──\n\n /** Subscribe to sort state changes. Returns an unsubscribe function. */\n subscribe(cb: () => void): () => void {\n this._listeners.add(cb);\n return () => { this._listeners.delete(cb); };\n }\n\n private _notify(): void {\n for (const cb of this._listeners) cb();\n }\n}\n\nfunction defaultCompare(a: any, b: any, key: string): number {\n const aVal = a[key];\n const bVal = b[key];\n if (aVal == null && bVal == null) return 0;\n if (aVal == null) return -1;\n if (bVal == null) return 1;\n if (typeof aVal === 'string' && typeof bVal === 'string') {\n return aVal.localeCompare(bVal);\n }\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Sorting.js","sources":["../src/Sorting.ts"],"sourcesContent":["import { bindPublicMethods } from './bindPublicMethods';\n\n/** Describes a single sort column with key and direction. */\nexport interface SortDescriptor {\n key: string;\n direction: 'asc' | 'desc';\n}\n\n/**\n * Multi-column sort state manager with a comparator pipeline.\n * Maintains an ordered list of sort descriptors and applies them to arrays.\n * Subscribable — auto-tracked when used as a ViewModel property.\n */\nexport class Sorting<T = any> {\n private _sorts: readonly SortDescriptor[];\n private _listeners = new Set<() => void>();\n\n constructor(options?: { sorts?: SortDescriptor[] }) {\n this._sorts = Object.freeze(options?.sorts?.map(s => ({ ...s })) ?? []);\n bindPublicMethods(this);\n }\n\n // ── Readable state ──\n\n /** Current list of active sort descriptors, in priority order. */\n get sorts(): readonly SortDescriptor[] {\n return this._sorts;\n }\n\n /** Primary sort key (first descriptor), or null when empty. */\n get key(): string | null {\n return this._sorts.length > 0 ? this._sorts[0].key : null;\n }\n\n /** Primary sort direction. Defaults to 'asc' when empty. */\n get direction(): 'asc' | 'desc' {\n return this._sorts.length > 0 ? this._sorts[0].direction : 'asc';\n }\n\n // ── Query ──\n\n /** Whether the given key is currently sorted. */\n isSorted(key: string): boolean {\n return this._sorts.some(s => s.key === key);\n }\n\n /** Returns the sort direction for a key, or null if not sorted. */\n directionOf(key: string): 'asc' | 'desc' | null {\n const found = this._sorts.find(s => s.key === key);\n return found ? found.direction : null;\n }\n\n /** Returns the priority index of a sorted key, or -1 if not sorted. */\n indexOf(key: string): number {\n return this._sorts.findIndex(s => s.key === key);\n }\n\n // ── Actions ──\n\n /** 3-click cycle: not sorted → asc → desc → removed. */\n toggle(key: string): void {\n const idx = this.indexOf(key);\n if (idx === -1) {\n // Add as asc\n this._sorts = Object.freeze([...this._sorts, { key, direction: 'asc' as const }]);\n } else if (this._sorts[idx].direction === 'asc') {\n // Flip to desc\n const next = this._sorts.map((s, i) =>\n i === idx ? { key: s.key, direction: 'desc' as const } : s\n );\n this._sorts = Object.freeze(next);\n } else {\n // Remove\n this._sorts = Object.freeze(this._sorts.filter((_, i) => i !== idx));\n }\n this._notify();\n }\n\n /** Replace all with a single sort. */\n setSort(key: string, direction: 'asc' | 'desc'): void {\n this._sorts = Object.freeze([{ key, direction }]);\n this._notify();\n }\n\n /** Replace all sorts. */\n setSorts(sorts: SortDescriptor[]): void {\n this._sorts = Object.freeze(sorts.map(s => ({ ...s })));\n this._notify();\n }\n\n /** Clear all sort descriptors. */\n reset(): void {\n this._sorts = Object.freeze([]);\n this._notify();\n }\n\n // ── Pipeline ──\n\n /** Sort an array using the current descriptors. Returns a new sorted array. */\n apply(\n items: T[],\n compareFn?: (a: T, b: T, key: string, dir: 'asc' | 'desc') => number,\n ): T[] {\n if (this._sorts.length === 0) return items;\n const sorted = items.slice();\n const sorts = this._sorts;\n sorted.sort((a, b) => {\n for (const { key, direction } of sorts) {\n let cmp: number;\n if (compareFn) {\n cmp = compareFn(a, b, key, direction);\n } else {\n cmp = defaultCompare(a, b, key);\n }\n if (direction === 'desc') cmp = -cmp;\n if (cmp !== 0) return cmp;\n }\n return 0;\n });\n return sorted;\n }\n\n // ── Subscribable interface ──\n\n /** Subscribe to sort state changes. Returns an unsubscribe function. */\n subscribe(cb: () => void): () => void {\n this._listeners.add(cb);\n return () => { this._listeners.delete(cb); };\n }\n\n private _notify(): void {\n for (const cb of this._listeners) cb();\n }\n}\n\nfunction defaultCompare(a: any, b: any, key: string): number {\n const aVal = a[key];\n const bVal = b[key];\n if (aVal == null && bVal == null) return 0;\n if (aVal == null) return -1;\n if (bVal == null) return 1;\n if (typeof aVal === 'string' && typeof bVal === 'string') {\n return aVal.localeCompare(bVal);\n }\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n}\n"],"names":[],"mappings":"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"}
|
package/dist/ViewModel.cjs
CHANGED
|
@@ -34,6 +34,8 @@ function getClassMemberInfo(instance, stopPrototype, reservedKeys, lifecycleHook
|
|
|
34
34
|
classMembers.set(ctor, info);
|
|
35
35
|
return info;
|
|
36
36
|
}
|
|
37
|
+
let _activeStateTracking = null;
|
|
38
|
+
let _activeSourceTracking = null;
|
|
37
39
|
function isAutoTrackable(value) {
|
|
38
40
|
return value !== null && typeof value === "object" && typeof value.subscribe === "function";
|
|
39
41
|
}
|
|
@@ -52,8 +54,6 @@ class ViewModel {
|
|
|
52
54
|
_eventBus = null;
|
|
53
55
|
// ── Reactive derived state (RFC 1) ─────────────────────────────
|
|
54
56
|
_revision = 0;
|
|
55
|
-
_stateTracking = null;
|
|
56
|
-
_sourceTracking = null;
|
|
57
57
|
_trackedSources = /* @__PURE__ */ new Map();
|
|
58
58
|
// ── Async tracking (RFC 2) ──────────────────────────────────────
|
|
59
59
|
// Lazily allocated on first async method wrap to keep construction fast.
|
|
@@ -68,7 +68,7 @@ class ViewModel {
|
|
|
68
68
|
const initialState = args[0] ?? {};
|
|
69
69
|
this._state = freeze({ ...initialState });
|
|
70
70
|
this._initialState = this._state;
|
|
71
|
-
this.
|
|
71
|
+
this._guardAndBind();
|
|
72
72
|
}
|
|
73
73
|
/** Current frozen state object. */
|
|
74
74
|
get state() {
|
|
@@ -117,7 +117,7 @@ class ViewModel {
|
|
|
117
117
|
*/
|
|
118
118
|
set(partialOrUpdater) {
|
|
119
119
|
if (this._disposed) return;
|
|
120
|
-
if (__DEV__ &&
|
|
120
|
+
if (__DEV__ && _activeStateTracking) {
|
|
121
121
|
console.error(
|
|
122
122
|
"[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."
|
|
123
123
|
);
|
|
@@ -283,13 +283,20 @@ class ViewModel {
|
|
|
283
283
|
this._subscriptionCleanups = null;
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
|
-
|
|
286
|
+
/**
|
|
287
|
+
* Guards reserved keys and auto-binds subclass methods in a single pass
|
|
288
|
+
* using the cached class metadata from getClassMemberInfo.
|
|
289
|
+
*/
|
|
290
|
+
_guardAndBind() {
|
|
287
291
|
const info = getClassMemberInfo(this, ViewModel.prototype, RESERVED_ASYNC_KEYS, LIFECYCLE_HOOKS);
|
|
288
292
|
if (info.reservedKeys.length > 0) {
|
|
289
293
|
throw new Error(
|
|
290
294
|
`[mvc-kit] "${info.reservedKeys[0]}" is a reserved property on ViewModel and cannot be overridden.`
|
|
291
295
|
);
|
|
292
296
|
}
|
|
297
|
+
for (let i = 0; i < info.methods.length; i++) {
|
|
298
|
+
this[info.methods[i].key] = info.methods[i].fn.bind(this);
|
|
299
|
+
}
|
|
293
300
|
}
|
|
294
301
|
// ── Member processing (merged getter memoization + async method wrapping) ──
|
|
295
302
|
/**
|
|
@@ -341,7 +348,7 @@ class ViewModel {
|
|
|
341
348
|
/**
|
|
342
349
|
* Installs a context-sensitive state getter on the instance.
|
|
343
350
|
*
|
|
344
|
-
* During getter tracking (
|
|
351
|
+
* During getter tracking (_activeStateTracking is active): returns a Proxy
|
|
345
352
|
* that records which state properties are accessed. The Proxy is created
|
|
346
353
|
* lazily on first tracking access to keep init() fast.
|
|
347
354
|
*
|
|
@@ -353,11 +360,11 @@ class ViewModel {
|
|
|
353
360
|
let stateProxy = null;
|
|
354
361
|
Object.defineProperty(this, "state", {
|
|
355
362
|
get: () => {
|
|
356
|
-
if (
|
|
363
|
+
if (_activeStateTracking) {
|
|
357
364
|
if (!stateProxy) {
|
|
358
365
|
stateProxy = new Proxy({}, {
|
|
359
366
|
get: (_, prop) => {
|
|
360
|
-
|
|
367
|
+
_activeStateTracking?.add(prop);
|
|
361
368
|
return this._state[prop];
|
|
362
369
|
},
|
|
363
370
|
ownKeys: () => Reflect.ownKeys(this._state),
|
|
@@ -417,7 +424,7 @@ class ViewModel {
|
|
|
417
424
|
this._trackedSources.set(key, tracked);
|
|
418
425
|
Object.defineProperty(this, key, {
|
|
419
426
|
get: () => {
|
|
420
|
-
|
|
427
|
+
_activeSourceTracking?.set(key, tracked);
|
|
421
428
|
return value;
|
|
422
429
|
},
|
|
423
430
|
configurable: true,
|
|
@@ -425,6 +432,25 @@ class ViewModel {
|
|
|
425
432
|
});
|
|
426
433
|
}
|
|
427
434
|
}
|
|
435
|
+
/**
|
|
436
|
+
* Bubbles cached dependency records to the active parent tracking context.
|
|
437
|
+
* Called from Tier 1/Tier 2 cache hits during nested getter composition
|
|
438
|
+
* so the parent getter records the full transitive dependency set.
|
|
439
|
+
* Extracted to keep the getter closure small for V8 inlining.
|
|
440
|
+
*/
|
|
441
|
+
_bubbleDeps(stateDepKeys, sourceDepKeys) {
|
|
442
|
+
const st = _activeStateTracking;
|
|
443
|
+
if (stateDepKeys) {
|
|
444
|
+
for (let i = 0; i < stateDepKeys.length; i++) st.add(stateDepKeys[i]);
|
|
445
|
+
}
|
|
446
|
+
if (sourceDepKeys) {
|
|
447
|
+
const srt = _activeSourceTracking;
|
|
448
|
+
for (let i = 0; i < sourceDepKeys.length; i++) {
|
|
449
|
+
const ts = this._trackedSources.get(sourceDepKeys[i]);
|
|
450
|
+
if (ts) srt.set(sourceDepKeys[i], ts);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
428
454
|
/**
|
|
429
455
|
* Replaces a single prototype getter with a memoized version on this
|
|
430
456
|
* instance. The memoized getter tracks both state dependencies and
|
|
@@ -447,6 +473,7 @@ class ViewModel {
|
|
|
447
473
|
Object.defineProperty(this, key, {
|
|
448
474
|
get: () => {
|
|
449
475
|
if (validatedAtRevision === this._revision) {
|
|
476
|
+
if (_activeStateTracking) this._bubbleDeps(stateDepKeys, sourceDepKeys);
|
|
450
477
|
return cached;
|
|
451
478
|
}
|
|
452
479
|
if (this._disposed) return cached;
|
|
@@ -469,12 +496,13 @@ class ViewModel {
|
|
|
469
496
|
}
|
|
470
497
|
}
|
|
471
498
|
if (fresh) {
|
|
499
|
+
if (_activeStateTracking) this._bubbleDeps(stateDepKeys, sourceDepKeys);
|
|
472
500
|
validatedAtRevision = this._revision;
|
|
473
501
|
return cached;
|
|
474
502
|
}
|
|
475
503
|
}
|
|
476
|
-
const parentStateTracking =
|
|
477
|
-
const parentSourceTracking =
|
|
504
|
+
const parentStateTracking = _activeStateTracking;
|
|
505
|
+
const parentSourceTracking = _activeSourceTracking;
|
|
478
506
|
if (trackingSet) {
|
|
479
507
|
trackingSet.clear();
|
|
480
508
|
} else {
|
|
@@ -485,17 +513,17 @@ class ViewModel {
|
|
|
485
513
|
} else {
|
|
486
514
|
trackingMap = /* @__PURE__ */ new Map();
|
|
487
515
|
}
|
|
488
|
-
|
|
489
|
-
|
|
516
|
+
_activeStateTracking = trackingSet;
|
|
517
|
+
_activeSourceTracking = trackingMap;
|
|
490
518
|
try {
|
|
491
519
|
cached = original.call(this);
|
|
492
520
|
} catch (e) {
|
|
493
|
-
|
|
494
|
-
|
|
521
|
+
_activeStateTracking = parentStateTracking;
|
|
522
|
+
_activeSourceTracking = parentSourceTracking;
|
|
495
523
|
throw e;
|
|
496
524
|
}
|
|
497
|
-
|
|
498
|
-
|
|
525
|
+
_activeStateTracking = parentStateTracking;
|
|
526
|
+
_activeSourceTracking = parentSourceTracking;
|
|
499
527
|
if (parentStateTracking) {
|
|
500
528
|
for (const d of trackingSet) parentStateTracking.add(d);
|
|
501
529
|
}
|
package/dist/ViewModel.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ViewModel.cjs","sources":["../src/ViewModel.ts"],"sourcesContent":["import { EventBus } from './EventBus';\nimport { walkPrototypeChain } from './walkPrototypeChain';\nimport { wrapAsyncMethods } from './wrapAsyncMethods';\nimport type { InternalTaskState } from './wrapAsyncMethods';\nimport type { Listener, Updater, Subscribable, TaskState, EventPayload } from './types';\n\n// Re-export for backwards compatibility\nexport type { Listener, Updater } from './types';\nexport { walkPrototypeChain } from './walkPrototypeChain';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\nfunction freeze<T>(obj: T): T {\n return __DEV__ ? Object.freeze(obj) as T : obj;\n}\n\n// ── Class metadata cache ─────────────────────────────────────────\n// Caches prototype walk results per class to avoid repeated Object.getOwnPropertyDescriptors\n// on every init(). Single walk extracts getters, methods, and reserved key violations.\n\ninterface ClassMemberInfo {\n getters: Array<{ key: string; get: () => unknown }>;\n methods: Array<{ key: string; fn: Function }>;\n reservedKeys: string[];\n}\n\nconst classMembers = new WeakMap<Function, ClassMemberInfo>();\n\nfunction getClassMemberInfo(\n instance: object,\n stopPrototype: object,\n reservedKeys: readonly string[],\n lifecycleHooks: Set<string>,\n): ClassMemberInfo {\n const ctor = instance.constructor;\n let info = classMembers.get(ctor);\n if (info) return info;\n\n const getters: ClassMemberInfo['getters'] = [];\n const methods: ClassMemberInfo['methods'] = [];\n const found: string[] = [];\n const processedGetters = new Set<string>();\n const processedMethods = new Set<string>();\n\n walkPrototypeChain(instance, stopPrototype, (key, desc) => {\n // Check reserved keys\n if (reservedKeys.includes(key as any)) {\n found.push(key);\n }\n\n // Collect getters (most-derived wins)\n if (desc.get && !processedGetters.has(key)) {\n processedGetters.add(key);\n getters.push({ key, get: desc.get });\n }\n\n // Collect wrappable methods (most-derived wins)\n if (!desc.get && !desc.set && typeof desc.value === 'function' &&\n !key.startsWith('_') && !lifecycleHooks.has(key) && !processedMethods.has(key)) {\n processedMethods.add(key);\n methods.push({ key, fn: desc.value });\n }\n });\n\n info = { getters, methods, reservedKeys: found };\n classMembers.set(ctor, info);\n return info;\n}\n\n// ── Auto-tracking types ──────────────────────────────────────────\n\ninterface TrackedSource {\n source: { subscribe(cb: () => void): () => void };\n revision: number;\n unsubscribe: () => void;\n}\n\n// ── Auto-tracking utilities ──────────────────────────────────────\n\nfunction isAutoTrackable(value: unknown): boolean {\n return (\n value !== null &&\n typeof value === 'object' &&\n typeof (value as any).subscribe === 'function'\n );\n}\n\n// ── Async tracking types ─────────────────────────────────────────\n\nconst DEFAULT_TASK_STATE: TaskState = Object.freeze({ loading: false, error: null, errorCode: null });\n\nexport type AsyncMethodKeys<T, Base = ViewModel<any, any>> = {\n [K in Exclude<keyof T, keyof Base>]: T[K] extends (...args: any[]) => Promise<any> ? K : never;\n}[Exclude<keyof T, keyof Base>];\n\ntype AsyncMap<T> = {\n readonly [K in AsyncMethodKeys<T>]: TaskState;\n};\n\nconst RESERVED_ASYNC_KEYS = ['async', 'subscribeAsync'] as const;\nconst LIFECYCLE_HOOKS = new Set(['onInit', 'onSet', 'onDispose']);\n\n// ── ViewModel ────────────────────────────────────────────────────\n\ntype EmptyViewModelState = { __brand: 'EmptyViewModelState'}\n\n/**\n * Reactive state container with computed getters, automatic async tracking, and typed events.\n * Subclass and define state shape, getters, and action methods. Use with `useLocal` in React.\n */\nexport abstract class ViewModel<S extends object = EmptyViewModelState, E extends Record<string, any> = {}> implements Subscribable<S> {\n private _state: Readonly<S>;\n private _initialState: Readonly<S>;\n private _disposed = false;\n private _initialized = false;\n private _listeners = new Set<Listener<S>>();\n private _abortController: AbortController | null = null;\n private _cleanups: (() => void)[] | null = null;\n private _subscriptionCleanups: (() => void)[] | null = null;\n private _eventBus: EventBus<E> | null = null;\n\n // ── Reactive derived state (RFC 1) ─────────────────────────────\n private _revision = 0;\n private _stateTracking: Set<string> | null = null;\n private _sourceTracking: Map<string, TrackedSource> | null = null;\n private _trackedSources = new Map<string, TrackedSource>();\n\n // ── Async tracking (RFC 2) ──────────────────────────────────────\n // Lazily allocated on first async method wrap to keep construction fast.\n private _asyncStates: Map<string, InternalTaskState> | null = null;\n private _asyncSnapshots: Map<string, TaskState> | null = null;\n private _asyncListeners: Set<() => void> | null = null;\n private _asyncProxy: AsyncMap<this> | null = null;\n private _activeOps: Map<string, number> | null = null;\n\n /** DEV-only timeout (ms) for detecting ghost async operations after dispose. */\n static GHOST_TIMEOUT = 3000;\n\n constructor(...args: EmptyViewModelState extends S ? [] | [initialState: S] : [initialState: S]) {\n const initialState = (args[0] ?? {} as S);\n this._state = freeze({ ...initialState });\n this._initialState = this._state;\n this._guardReservedKeys();\n }\n\n /** Current frozen state object. */\n get state(): S {\n return this._state;\n }\n\n /** Whether this instance has been disposed. */\n get disposed(): boolean {\n return this._disposed;\n }\n\n /** Whether init() has been called. */\n get initialized(): boolean {\n return this._initialized;\n }\n\n /** AbortSignal that fires when this instance is disposed. Lazily created. */\n get disposeSignal(): AbortSignal {\n if (!this._abortController) {\n this._abortController = new AbortController();\n }\n return this._abortController.signal;\n }\n\n /** Lazily-created typed EventBus for emitting and subscribing to events. */\n get events(): EventBus<E> {\n if (!this._eventBus) {\n this._eventBus = new EventBus<E>();\n }\n return this._eventBus;\n }\n\n /** Initializes the instance. Called automatically by React hooks after mount. */\n init(): void | Promise<void> {\n if (this._initialized || this._disposed) return;\n this._initialized = true;\n this._trackSubscribables();\n this._installStateProxy();\n this._processMembers();\n return this.onInit?.();\n }\n\n /**\n * Merges partial state into current state. If no values actually\n * changed by reference, this is a no-op.\n *\n * Triggers React re-render via listener notification. Called when:\n * - User code calls set() to update source state\n *\n * NOT called for subscribable member notifications — those use\n * a separate notification path (see _trackSubscribables).\n */\n protected set(partialOrUpdater: Partial<S> | Updater<S>): void {\n if (this._disposed) return;\n\n // __DEV__ guard: set() inside a getter would cause infinite loops.\n // After init(), getters are auto-memoized; set() → notify → recompute → set() → ∞\n if (__DEV__ && this._stateTracking) {\n console.error(\n '[mvc-kit] set() called inside a getter. ' +\n 'Getters must be pure — they read state and return a value. ' +\n 'They must never call set(), which would cause an infinite ' +\n 'render loop. Move this logic to an action method.'\n );\n return;\n }\n\n const partial =\n typeof partialOrUpdater === 'function'\n ? partialOrUpdater(this._state)\n : partialOrUpdater;\n\n // Check if any values actually changed (shallow equality).\n // Uses for..in to avoid Object.keys() array allocation.\n let hasChanges = false;\n const current = this._state;\n for (const key in partial) {\n if ((partial as any)[key] !== (current as any)[key]) {\n hasChanges = true;\n break;\n }\n }\n\n if (!hasChanges) {\n return;\n }\n\n const prev = this._state;\n const next = freeze({ ...prev, ...partial });\n this._state = next;\n this._revision++;\n\n this.onSet?.(prev, next);\n\n for (const listener of this._listeners) {\n listener(next, prev);\n }\n }\n\n /**\n * Emits a typed event via the internal EventBus.\n * Safe to call during dispose cleanup callbacks.\n * @protected\n */\n protected emit<K extends keyof E>(event: K, payload: E[K]): void {\n // During dispose sequence: _disposed is true but eventBus not yet disposed.\n // Cleanup callbacks can still emit. After eventBus.dispose(), this is a no-op.\n // If eventBus was never created, fall back to _disposed check.\n if (this._eventBus?.disposed ?? this._disposed) return;\n this.events.emit(event, payload);\n }\n\n /** Subscribes to state changes. Returns an unsubscribe function. */\n subscribe(listener: Listener<S>): () => void {\n if (this._disposed) {\n return () => {};\n }\n\n this._listeners.add(listener);\n\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n /** Tears down the instance, releasing all subscriptions and resources. */\n dispose(): void {\n if (this._disposed) {\n return;\n }\n\n this._disposed = true;\n\n this._teardownSubscriptions();\n\n // Async tracking cleanup — handled by addCleanup registered in _processMembers()\n this._abortController?.abort();\n if (this._cleanups) {\n for (const fn of this._cleanups) fn();\n this._cleanups = null;\n }\n this._eventBus?.dispose();\n this.onDispose?.();\n this._listeners.clear();\n }\n\n /**\n * Resets state to initial values (or provided state), aborts in-flight work,\n * clears async tracking, and re-runs onInit().\n */\n reset(newState?: S): void | Promise<void> {\n if (this._disposed) return;\n\n // 1. Abort in-flight, lazy-recreate on next disposeSignal access\n this._abortController?.abort();\n this._abortController = null;\n\n this._teardownSubscriptions();\n\n // 2. Reset state\n this._state = newState ? freeze({ ...newState }) : this._initialState;\n this._revision++;\n\n // 3. Clear async tracking (preserve listeners — React still subscribed)\n this._asyncStates?.clear();\n this._asyncSnapshots?.clear();\n this._notifyAsync();\n\n // 4. Re-track subscribable members (fresh subscriptions)\n this._trackSubscribables();\n\n // 5. Notify state listeners (React re-renders with new state)\n for (const listener of this._listeners) {\n listener(this._state, this._state);\n }\n\n // 6. Re-run onInit\n return this.onInit?.();\n }\n\n /**\n * Registers a cleanup function to be called on dispose. Used internally for things like method wrapper\n * cleanup and event bus disposal, but can also be used by subclasses for custom cleanup logic.\n * @param fn\n * @protected\n */\n protected addCleanup(fn: () => void): void {\n if (!this._cleanups) {\n this._cleanups = [];\n }\n this._cleanups.push(fn);\n }\n\n /** Subscribes to an external Subscribable with automatic cleanup on dispose. @protected */\n protected subscribeTo<T>(source: Subscribable<T>, listener: Listener<T>): () => void {\n const unsubscribe = source.subscribe(listener);\n if (!this._subscriptionCleanups) this._subscriptionCleanups = [];\n this._subscriptionCleanups.push(unsubscribe);\n return unsubscribe;\n }\n\n /** Subscribes to a typed event on a Channel or EventBus with automatic cleanup on dispose and reset. @protected */\n protected listenTo<K extends string, S extends { on(event: K, handler: (payload: any) => void): () => void }>(\n source: S,\n event: K,\n handler: (payload: EventPayload<S, K>) => void,\n ): () => void {\n const unsubscribe = source.on(event, handler);\n if (!this._subscriptionCleanups) this._subscriptionCleanups = [];\n this._subscriptionCleanups.push(unsubscribe);\n return unsubscribe;\n }\n\n /** Pipes a Channel event into a Collection via upsert. Calls channel.init() and registers auto-cleanup on dispose and reset. @protected */\n protected pipeChannel<\n K extends string,\n C extends { init(): void | Promise<void>; on(event: K, handler: (payload: any) => void): () => void },\n >(\n channel: C,\n type: K,\n target: { upsert(item: EventPayload<C, K>): void },\n ): () => void {\n channel.init();\n return this.listenTo(channel, type, (payload) => {\n target.upsert(payload);\n });\n }\n\n /** Lifecycle hook called after every set() with the previous state. @protected */\n protected onSet?(prev: S, next: S): void;\n /** Lifecycle hook called at the end of init(). Override to load initial data. @protected */\n protected onInit?(): void | Promise<void>;\n /** Lifecycle hook called during dispose(). Override for custom teardown. @protected */\n protected onDispose?(): void;\n\n // ── Async tracking API ──────────────────────────────────────────\n\n /** Proxy providing `TaskState` (loading, error, errorCode) per async method. */\n get async(): AsyncMap<this> {\n if (!this._asyncProxy) {\n const self = this;\n this._asyncProxy = new Proxy({} as AsyncMap<this>, {\n get(_, prop: string) {\n return self._asyncSnapshots?.get(prop) ?? DEFAULT_TASK_STATE;\n },\n has(_, prop: string) {\n return self._asyncSnapshots?.has(prop) ?? false;\n },\n ownKeys() {\n return self._asyncSnapshots ? Array.from(self._asyncSnapshots.keys()) : [];\n },\n getOwnPropertyDescriptor(_, prop: string) {\n if (self._asyncSnapshots?.has(prop)) {\n return { configurable: true, enumerable: true, value: self._asyncSnapshots.get(prop) };\n }\n return undefined;\n },\n });\n }\n return this._asyncProxy;\n }\n\n /** Subscribes to async state changes. Used for React integration. */\n subscribeAsync(listener: () => void): () => void {\n if (this._disposed) return () => {};\n if (!this._asyncListeners) this._asyncListeners = new Set();\n this._asyncListeners.add(listener);\n return () => { this._asyncListeners!.delete(listener); };\n }\n\n private _notifyAsync(): void {\n if (!this._asyncListeners) return;\n for (const listener of this._asyncListeners) {\n listener();\n }\n }\n\n // ── Async tracking internals ────────────────────────────────────\n\n private _teardownSubscriptions(): void {\n for (const tracked of this._trackedSources.values()) tracked.unsubscribe();\n this._trackedSources.clear();\n\n if (this._subscriptionCleanups) {\n for (const fn of this._subscriptionCleanups) fn();\n this._subscriptionCleanups = null;\n }\n }\n\n private _guardReservedKeys(): void {\n // Prototype check (runs in constructor — catches method/getter definitions)\n const info = getClassMemberInfo(this, ViewModel.prototype, RESERVED_ASYNC_KEYS, LIFECYCLE_HOOKS);\n if (info.reservedKeys.length > 0) {\n throw new Error(\n `[mvc-kit] \"${info.reservedKeys[0]}\" is a reserved property on ViewModel and cannot be overridden.`\n );\n }\n }\n\n // ── Member processing (merged getter memoization + async method wrapping) ──\n\n /**\n * Uses cached class metadata to memoize getters (RFC 1) and delegates\n * async method wrapping to the shared wrapAsyncMethods helper (RFC 2).\n * Class metadata is computed once per class via getClassMemberInfo() and reused\n * across all instances — avoids repeated prototype walks.\n */\n private _processMembers(): void {\n const info = getClassMemberInfo(this, ViewModel.prototype, RESERVED_ASYNC_KEYS, LIFECYCLE_HOOKS);\n\n // Memoize getters\n for (let i = 0; i < info.getters.length; i++) {\n this._wrapGetter(info.getters[i].key, info.getters[i].get);\n }\n\n // DEV: Instance property reserved key check (must run even if no methods to wrap)\n if (__DEV__) {\n for (const key of RESERVED_ASYNC_KEYS) {\n if (Object.getOwnPropertyDescriptor(this, key)?.value !== undefined) {\n throw new Error(\n `[mvc-kit] \"${key}\" is a reserved property on ViewModel and cannot be overridden.`\n );\n }\n }\n }\n\n // Skip async wrapping if no methods to wrap\n if (info.methods.length === 0) return;\n\n // Lazily allocate async tracking collections\n if (!this._asyncStates) this._asyncStates = new Map();\n if (!this._asyncSnapshots) this._asyncSnapshots = new Map();\n if (!this._asyncListeners) this._asyncListeners = new Set();\n\n // Initialize DEV-only active ops tracking\n if (__DEV__) {\n this._activeOps = new Map();\n }\n\n // Wrap async methods (shared with Resource)\n wrapAsyncMethods({\n instance: this,\n stopPrototype: ViewModel.prototype,\n reservedKeys: RESERVED_ASYNC_KEYS,\n lifecycleHooks: LIFECYCLE_HOOKS,\n isDisposed: () => this._disposed,\n isInitialized: () => this._initialized,\n asyncStates: this._asyncStates,\n asyncSnapshots: this._asyncSnapshots,\n asyncListeners: this._asyncListeners,\n notifyAsync: () => this._notifyAsync(),\n addCleanup: (fn) => this.addCleanup(fn),\n ghostTimeout: (this.constructor as typeof ViewModel).GHOST_TIMEOUT,\n className: 'ViewModel',\n activeOps: this._activeOps,\n methods: info.methods,\n });\n }\n\n // ── Auto-tracking internals ────────────────────────────────────\n\n /**\n * Installs a context-sensitive state getter on the instance.\n *\n * During getter tracking (_stateTracking is active): returns a Proxy\n * that records which state properties are accessed. The Proxy is created\n * lazily on first tracking access to keep init() fast.\n *\n * Otherwise: returns the frozen state object directly. This is critical\n * for React's useSyncExternalStore — it needs a changing reference to\n * detect state updates and trigger re-renders.\n */\n private _installStateProxy(): void {\n let stateProxy: S | null = null;\n\n Object.defineProperty(this, 'state', {\n get: () => {\n if (this._stateTracking) {\n if (!stateProxy) {\n stateProxy = new Proxy({} as S, {\n get: (_, prop: string) => {\n this._stateTracking?.add(prop);\n return (this._state as any)[prop];\n },\n ownKeys: () => Reflect.ownKeys(this._state as object),\n getOwnPropertyDescriptor: (_, prop) =>\n Reflect.getOwnPropertyDescriptor(this._state as object, prop),\n set: () => {\n throw new Error('Cannot mutate state directly. Use set() instead.');\n },\n has: (_, prop) => prop in (this._state as object),\n });\n }\n return stateProxy;\n }\n return this._state;\n },\n configurable: true,\n enumerable: true,\n });\n }\n\n /**\n * Scans own instance properties for Subscribable objects and sets up\n * automatic dependency tracking for each one found.\n *\n * For each subscribable member:\n * 1. Subscribe to it. On notification: bump its tracked revision\n * AND the VM's global revision, then force a new state reference\n * and notify listeners so React re-renders.\n * 2. Replace the instance property with a getter that participates\n * in dependency tracking.\n * 3. Register unsubscribe in the dispose chain.\n *\n * Called during init(), AFTER all subclass property initializers\n * have run (they execute during the constructor, before init()).\n */\n private _trackSubscribables(): void {\n for (const key of Object.getOwnPropertyNames(this)) {\n const value = (this as any)[key];\n if (!isAutoTrackable(value)) continue;\n\n let tracked: TrackedSource;\n\n const onSourceNotify = () => {\n if (this._disposed) return;\n\n // Source notified — bump revisions for getter invalidation\n tracked.revision++;\n this._revision++;\n\n for (const listener of this._listeners) {\n listener(this._state, this._state);\n }\n };\n\n const unsubState = value.subscribe(onSourceNotify);\n const unsubAsync =\n typeof value.subscribeAsync === 'function'\n ? value.subscribeAsync(onSourceNotify)\n : undefined;\n\n tracked = {\n source: value,\n revision: 0,\n unsubscribe: unsubAsync\n ? () => { unsubState(); unsubAsync(); }\n : unsubState,\n };\n\n this._trackedSources.set(key, tracked);\n\n // Replace the instance property with a tracking getter.\n // The original value is captured in the closure.\n Object.defineProperty(this, key, {\n get: () => {\n this._sourceTracking?.set(key, tracked);\n return value;\n },\n configurable: true,\n enumerable: false,\n });\n }\n }\n\n /**\n * Replaces a single prototype getter with a memoized version on this\n * instance. The memoized getter tracks both state dependencies and\n * subscribable member dependencies, caching its result and\n * revalidating through a three-tier strategy:\n *\n * Tier 1 (fast): revision unchanged → return cached (1 int compare)\n * Tier 2 (medium): revision changed but this getter's deps didn't → return cached\n * Tier 3 (slow): at least one dep changed → full recompute with tracking\n */\n private _wrapGetter(key: string, original: () => unknown): void {\n // Per-getter cache state, private to this getter on this instance.\n let cached: unknown;\n let validatedAtRevision = -1;\n\n // Array-based dep tracking — avoids Map iterator allocation in Tier 2\n let stateDepKeys: string[] | undefined;\n let stateDepValues: unknown[] | undefined;\n let sourceDepKeys: string[] | undefined;\n let sourceDepRevisions: number[] | undefined;\n\n // Reusable tracking containers — allocated on first Tier 3, reused via clear()\n let trackingSet: Set<string> | undefined;\n let trackingMap: Map<string, TrackedSource> | undefined;\n\n Object.defineProperty(this, key, {\n get: () => {\n // ── Tier 1: Fast path (1 integer compare) ───────────────\n if (validatedAtRevision === this._revision) {\n return cached;\n }\n\n // After dispose, revision never changes so Tier 1 hits if\n // getter was ever called. Guard the uncalled-before-dispose edge case.\n if (this._disposed) return cached;\n\n // ── Tier 2: Medium path — array-based dep check ─────────\n if (stateDepKeys !== undefined) {\n let fresh = true;\n\n // Check state deps by reference (array iteration, no iterator alloc)\n const state = this._state as any;\n for (let i = 0; i < stateDepKeys.length; i++) {\n if (state[stateDepKeys[i]] !== stateDepValues![i]) {\n fresh = false;\n break;\n }\n }\n\n // Check subscribable deps by revision\n if (fresh && sourceDepKeys !== undefined && sourceDepKeys.length > 0) {\n for (let i = 0; i < sourceDepKeys.length; i++) {\n const ts = this._trackedSources.get(sourceDepKeys[i]);\n if (ts && ts.revision !== sourceDepRevisions![i]) {\n fresh = false;\n break;\n }\n }\n }\n\n if (fresh) {\n validatedAtRevision = this._revision;\n return cached;\n }\n }\n\n // ── Tier 3: Slow path — full recompute ─────────────────\n // Save parent tracking context for nested getter composition\n const parentStateTracking = this._stateTracking;\n const parentSourceTracking = this._sourceTracking;\n\n // Reuse tracking containers (clear instead of allocate)\n if (trackingSet) {\n trackingSet.clear();\n } else {\n trackingSet = new Set();\n }\n if (trackingMap) {\n trackingMap.clear();\n } else {\n trackingMap = new Map();\n }\n\n this._stateTracking = trackingSet;\n this._sourceTracking = trackingMap;\n\n try {\n cached = original.call(this);\n } catch (e) {\n // Don't cache failed computations\n this._stateTracking = parentStateTracking;\n this._sourceTracking = parentSourceTracking;\n throw e;\n }\n\n // Restore parent tracking context\n this._stateTracking = parentStateTracking;\n this._sourceTracking = parentSourceTracking;\n\n // Bubble deps up to parent getter if nested\n if (parentStateTracking) {\n for (const d of trackingSet) parentStateTracking.add(d);\n }\n if (parentSourceTracking) {\n for (const [k, v] of trackingMap) {\n parentSourceTracking.set(k, v);\n }\n }\n\n // Snapshot state dep values into arrays for Tier 2\n const depCount = trackingSet.size;\n if (!stateDepKeys || stateDepKeys.length !== depCount) {\n stateDepKeys = new Array(depCount);\n stateDepValues = new Array(depCount);\n }\n {\n let i = 0;\n const state = this._state as any;\n for (const d of trackingSet) {\n stateDepKeys[i] = d;\n stateDepValues![i] = state[d];\n i++;\n }\n }\n\n // Snapshot subscribable revisions into arrays\n const sourceCount = trackingMap.size;\n if (sourceCount > 0) {\n if (!sourceDepKeys || sourceDepKeys.length !== sourceCount) {\n sourceDepKeys = new Array(sourceCount);\n sourceDepRevisions = new Array(sourceCount);\n }\n let i = 0;\n for (const [memberKey, tracked] of trackingMap) {\n sourceDepKeys[i] = memberKey;\n sourceDepRevisions![i] = tracked.revision;\n i++;\n }\n } else {\n sourceDepKeys = undefined;\n sourceDepRevisions = undefined;\n }\n\n validatedAtRevision = this._revision;\n\n return cached;\n },\n configurable: true,\n enumerable: true,\n });\n }\n}\n"],"names":["walkPrototypeChain","EventBus","wrapAsyncMethods"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ViewModel.cjs","sources":["../src/ViewModel.ts"],"sourcesContent":["import { EventBus } from './EventBus';\nimport { walkPrototypeChain } from './walkPrototypeChain';\nimport { wrapAsyncMethods } from './wrapAsyncMethods';\nimport type { InternalTaskState } from './wrapAsyncMethods';\nimport type { Listener, Updater, Subscribable, TaskState, EventPayload } from './types';\n\n// Re-export for backwards compatibility\nexport type { Listener, Updater } from './types';\nexport { walkPrototypeChain } from './walkPrototypeChain';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\nfunction freeze<T>(obj: T): T {\n return __DEV__ ? Object.freeze(obj) as T : obj;\n}\n\n// ── Class metadata cache ─────────────────────────────────────────\n// Caches prototype walk results per class to avoid repeated Object.getOwnPropertyDescriptors\n// on every init(). Single walk extracts getters, methods, and reserved key violations.\n\ninterface ClassMemberInfo {\n getters: Array<{ key: string; get: () => unknown }>;\n methods: Array<{ key: string; fn: Function }>;\n reservedKeys: string[];\n}\n\nconst classMembers = new WeakMap<Function, ClassMemberInfo>();\n\nfunction getClassMemberInfo(\n instance: object,\n stopPrototype: object,\n reservedKeys: readonly string[],\n lifecycleHooks: Set<string>,\n): ClassMemberInfo {\n const ctor = instance.constructor;\n let info = classMembers.get(ctor);\n if (info) return info;\n\n const getters: ClassMemberInfo['getters'] = [];\n const methods: ClassMemberInfo['methods'] = [];\n const found: string[] = [];\n const processedGetters = new Set<string>();\n const processedMethods = new Set<string>();\n\n walkPrototypeChain(instance, stopPrototype, (key, desc) => {\n // Check reserved keys\n if (reservedKeys.includes(key as any)) {\n found.push(key);\n }\n\n // Collect getters (most-derived wins)\n if (desc.get && !processedGetters.has(key)) {\n processedGetters.add(key);\n getters.push({ key, get: desc.get });\n }\n\n // Collect wrappable methods (most-derived wins)\n if (!desc.get && !desc.set && typeof desc.value === 'function' &&\n !key.startsWith('_') && !lifecycleHooks.has(key) && !processedMethods.has(key)) {\n processedMethods.add(key);\n methods.push({ key, fn: desc.value });\n }\n });\n\n info = { getters, methods, reservedKeys: found };\n classMembers.set(ctor, info);\n return info;\n}\n\n// ── Auto-tracking types ──────────────────────────────────────────\n\ninterface TrackedSource {\n source: { subscribe(cb: () => void): () => void };\n revision: number;\n unsubscribe: () => void;\n}\n\n// ── Module-scoped tracking context ──────────────────────────────\n// Active during a memoized getter's Tier 3 recompute. Child getters\n// check these to bubble their cached deps to the parent. Module-scoped\n// (like Vue's activeEffect / Solid's Listener) for zero-cost Tier 1 checks.\n\nlet _activeStateTracking: Set<string> | null = null;\nlet _activeSourceTracking: Map<string, TrackedSource> | null = null;\n\n// ── Auto-tracking utilities ──────────────────────────────────────\n\nfunction isAutoTrackable(value: unknown): boolean {\n return (\n value !== null &&\n typeof value === 'object' &&\n typeof (value as any).subscribe === 'function'\n );\n}\n\n// ── Async tracking types ─────────────────────────────────────────\n\nconst DEFAULT_TASK_STATE: TaskState = Object.freeze({ loading: false, error: null, errorCode: null });\n\nexport type AsyncMethodKeys<T, Base = ViewModel<any, any>> = {\n [K in Exclude<keyof T, keyof Base>]: T[K] extends (...args: any[]) => Promise<any> ? K : never;\n}[Exclude<keyof T, keyof Base>];\n\ntype AsyncMap<T> = {\n readonly [K in AsyncMethodKeys<T>]: TaskState;\n};\n\nconst RESERVED_ASYNC_KEYS = ['async', 'subscribeAsync'] as const;\nconst LIFECYCLE_HOOKS = new Set(['onInit', 'onSet', 'onDispose']);\n\n// ── ViewModel ────────────────────────────────────────────────────\n\ntype EmptyViewModelState = { __brand: 'EmptyViewModelState'}\n\n/**\n * Reactive state container with computed getters, automatic async tracking, and typed events.\n * Subclass and define state shape, getters, and action methods. Use with `useLocal` in React.\n */\nexport abstract class ViewModel<S extends object = EmptyViewModelState, E extends Record<string, any> = {}> implements Subscribable<S> {\n private _state: Readonly<S>;\n private _initialState: Readonly<S>;\n private _disposed = false;\n private _initialized = false;\n private _listeners = new Set<Listener<S>>();\n private _abortController: AbortController | null = null;\n private _cleanups: (() => void)[] | null = null;\n private _subscriptionCleanups: (() => void)[] | null = null;\n private _eventBus: EventBus<E> | null = null;\n\n // ── Reactive derived state (RFC 1) ─────────────────────────────\n private _revision = 0;\n private _trackedSources = new Map<string, TrackedSource>();\n\n // ── Async tracking (RFC 2) ──────────────────────────────────────\n // Lazily allocated on first async method wrap to keep construction fast.\n private _asyncStates: Map<string, InternalTaskState> | null = null;\n private _asyncSnapshots: Map<string, TaskState> | null = null;\n private _asyncListeners: Set<() => void> | null = null;\n private _asyncProxy: AsyncMap<this> | null = null;\n private _activeOps: Map<string, number> | null = null;\n\n /** DEV-only timeout (ms) for detecting ghost async operations after dispose. */\n static GHOST_TIMEOUT = 3000;\n\n constructor(...args: EmptyViewModelState extends S ? [] | [initialState: S] : [initialState: S]) {\n const initialState = (args[0] ?? {} as S);\n this._state = freeze({ ...initialState });\n this._initialState = this._state;\n this._guardAndBind();\n }\n\n /** Current frozen state object. */\n get state(): S {\n return this._state;\n }\n\n /** Whether this instance has been disposed. */\n get disposed(): boolean {\n return this._disposed;\n }\n\n /** Whether init() has been called. */\n get initialized(): boolean {\n return this._initialized;\n }\n\n /** AbortSignal that fires when this instance is disposed. Lazily created. */\n get disposeSignal(): AbortSignal {\n if (!this._abortController) {\n this._abortController = new AbortController();\n }\n return this._abortController.signal;\n }\n\n /** Lazily-created typed EventBus for emitting and subscribing to events. */\n get events(): EventBus<E> {\n if (!this._eventBus) {\n this._eventBus = new EventBus<E>();\n }\n return this._eventBus;\n }\n\n /** Initializes the instance. Called automatically by React hooks after mount. */\n init(): void | Promise<void> {\n if (this._initialized || this._disposed) return;\n this._initialized = true;\n this._trackSubscribables();\n this._installStateProxy();\n this._processMembers();\n return this.onInit?.();\n }\n\n /**\n * Merges partial state into current state. If no values actually\n * changed by reference, this is a no-op.\n *\n * Triggers React re-render via listener notification. Called when:\n * - User code calls set() to update source state\n *\n * NOT called for subscribable member notifications — those use\n * a separate notification path (see _trackSubscribables).\n */\n protected set(partialOrUpdater: Partial<S> | Updater<S>): void {\n if (this._disposed) return;\n\n // __DEV__ guard: set() inside a getter would cause infinite loops.\n // After init(), getters are auto-memoized; set() → notify → recompute → set() → ∞\n if (__DEV__ && _activeStateTracking) {\n console.error(\n '[mvc-kit] set() called inside a getter. ' +\n 'Getters must be pure — they read state and return a value. ' +\n 'They must never call set(), which would cause an infinite ' +\n 'render loop. Move this logic to an action method.'\n );\n return;\n }\n\n const partial =\n typeof partialOrUpdater === 'function'\n ? partialOrUpdater(this._state)\n : partialOrUpdater;\n\n // Check if any values actually changed (shallow equality).\n // Uses for..in to avoid Object.keys() array allocation.\n let hasChanges = false;\n const current = this._state;\n for (const key in partial) {\n if ((partial as any)[key] !== (current as any)[key]) {\n hasChanges = true;\n break;\n }\n }\n\n if (!hasChanges) {\n return;\n }\n\n const prev = this._state;\n const next = freeze({ ...prev, ...partial });\n this._state = next;\n this._revision++;\n\n this.onSet?.(prev, next);\n\n for (const listener of this._listeners) {\n listener(next, prev);\n }\n }\n\n /**\n * Emits a typed event via the internal EventBus.\n * Safe to call during dispose cleanup callbacks.\n * @protected\n */\n protected emit<K extends keyof E>(event: K, payload: E[K]): void {\n // During dispose sequence: _disposed is true but eventBus not yet disposed.\n // Cleanup callbacks can still emit. After eventBus.dispose(), this is a no-op.\n // If eventBus was never created, fall back to _disposed check.\n if (this._eventBus?.disposed ?? this._disposed) return;\n this.events.emit(event, payload);\n }\n\n /** Subscribes to state changes. Returns an unsubscribe function. */\n subscribe(listener: Listener<S>): () => void {\n if (this._disposed) {\n return () => {};\n }\n\n this._listeners.add(listener);\n\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n /** Tears down the instance, releasing all subscriptions and resources. */\n dispose(): void {\n if (this._disposed) {\n return;\n }\n\n this._disposed = true;\n\n this._teardownSubscriptions();\n\n // Async tracking cleanup — handled by addCleanup registered in _processMembers()\n this._abortController?.abort();\n if (this._cleanups) {\n for (const fn of this._cleanups) fn();\n this._cleanups = null;\n }\n this._eventBus?.dispose();\n this.onDispose?.();\n this._listeners.clear();\n }\n\n /**\n * Resets state to initial values (or provided state), aborts in-flight work,\n * clears async tracking, and re-runs onInit().\n */\n reset(newState?: S): void | Promise<void> {\n if (this._disposed) return;\n\n // 1. Abort in-flight, lazy-recreate on next disposeSignal access\n this._abortController?.abort();\n this._abortController = null;\n\n this._teardownSubscriptions();\n\n // 2. Reset state\n this._state = newState ? freeze({ ...newState }) : this._initialState;\n this._revision++;\n\n // 3. Clear async tracking (preserve listeners — React still subscribed)\n this._asyncStates?.clear();\n this._asyncSnapshots?.clear();\n this._notifyAsync();\n\n // 4. Re-track subscribable members (fresh subscriptions)\n this._trackSubscribables();\n\n // 5. Notify state listeners (React re-renders with new state)\n for (const listener of this._listeners) {\n listener(this._state, this._state);\n }\n\n // 6. Re-run onInit\n return this.onInit?.();\n }\n\n /**\n * Registers a cleanup function to be called on dispose. Used internally for things like method wrapper\n * cleanup and event bus disposal, but can also be used by subclasses for custom cleanup logic.\n * @param fn\n * @protected\n */\n protected addCleanup(fn: () => void): void {\n if (!this._cleanups) {\n this._cleanups = [];\n }\n this._cleanups.push(fn);\n }\n\n /** Subscribes to an external Subscribable with automatic cleanup on dispose. @protected */\n protected subscribeTo<T>(source: Subscribable<T>, listener: Listener<T>): () => void {\n const unsubscribe = source.subscribe(listener);\n if (!this._subscriptionCleanups) this._subscriptionCleanups = [];\n this._subscriptionCleanups.push(unsubscribe);\n return unsubscribe;\n }\n\n /** Subscribes to a typed event on a Channel or EventBus with automatic cleanup on dispose and reset. @protected */\n protected listenTo<K extends string, S extends { on(event: K, handler: (payload: any) => void): () => void }>(\n source: S,\n event: K,\n handler: (payload: EventPayload<S, K>) => void,\n ): () => void {\n const unsubscribe = source.on(event, handler);\n if (!this._subscriptionCleanups) this._subscriptionCleanups = [];\n this._subscriptionCleanups.push(unsubscribe);\n return unsubscribe;\n }\n\n /** Pipes a Channel event into a Collection via upsert. Calls channel.init() and registers auto-cleanup on dispose and reset. @protected */\n protected pipeChannel<\n K extends string,\n C extends { init(): void | Promise<void>; on(event: K, handler: (payload: any) => void): () => void },\n >(\n channel: C,\n type: K,\n target: { upsert(item: EventPayload<C, K>): void },\n ): () => void {\n channel.init();\n return this.listenTo(channel, type, (payload) => {\n target.upsert(payload);\n });\n }\n\n /** Lifecycle hook called after every set() with the previous state. @protected */\n protected onSet?(prev: S, next: S): void;\n /** Lifecycle hook called at the end of init(). Override to load initial data. @protected */\n protected onInit?(): void | Promise<void>;\n /** Lifecycle hook called during dispose(). Override for custom teardown. @protected */\n protected onDispose?(): void;\n\n // ── Async tracking API ──────────────────────────────────────────\n\n /** Proxy providing `TaskState` (loading, error, errorCode) per async method. */\n get async(): AsyncMap<this> {\n if (!this._asyncProxy) {\n const self = this;\n this._asyncProxy = new Proxy({} as AsyncMap<this>, {\n get(_, prop: string) {\n return self._asyncSnapshots?.get(prop) ?? DEFAULT_TASK_STATE;\n },\n has(_, prop: string) {\n return self._asyncSnapshots?.has(prop) ?? false;\n },\n ownKeys() {\n return self._asyncSnapshots ? Array.from(self._asyncSnapshots.keys()) : [];\n },\n getOwnPropertyDescriptor(_, prop: string) {\n if (self._asyncSnapshots?.has(prop)) {\n return { configurable: true, enumerable: true, value: self._asyncSnapshots.get(prop) };\n }\n return undefined;\n },\n });\n }\n return this._asyncProxy;\n }\n\n /** Subscribes to async state changes. Used for React integration. */\n subscribeAsync(listener: () => void): () => void {\n if (this._disposed) return () => {};\n if (!this._asyncListeners) this._asyncListeners = new Set();\n this._asyncListeners.add(listener);\n return () => { this._asyncListeners!.delete(listener); };\n }\n\n private _notifyAsync(): void {\n if (!this._asyncListeners) return;\n for (const listener of this._asyncListeners) {\n listener();\n }\n }\n\n // ── Async tracking internals ────────────────────────────────────\n\n private _teardownSubscriptions(): void {\n for (const tracked of this._trackedSources.values()) tracked.unsubscribe();\n this._trackedSources.clear();\n\n if (this._subscriptionCleanups) {\n for (const fn of this._subscriptionCleanups) fn();\n this._subscriptionCleanups = null;\n }\n }\n\n /**\n * Guards reserved keys and auto-binds subclass methods in a single pass\n * using the cached class metadata from getClassMemberInfo.\n */\n private _guardAndBind(): void {\n const info = getClassMemberInfo(this, ViewModel.prototype, RESERVED_ASYNC_KEYS, LIFECYCLE_HOOKS);\n if (info.reservedKeys.length > 0) {\n throw new Error(\n `[mvc-kit] \"${info.reservedKeys[0]}\" is a reserved property on ViewModel and cannot be overridden.`\n );\n }\n for (let i = 0; i < info.methods.length; i++) {\n (this as any)[info.methods[i].key] = info.methods[i].fn.bind(this);\n }\n }\n\n // ── Member processing (merged getter memoization + async method wrapping) ──\n\n /**\n * Uses cached class metadata to memoize getters (RFC 1) and delegates\n * async method wrapping to the shared wrapAsyncMethods helper (RFC 2).\n * Class metadata is computed once per class via getClassMemberInfo() and reused\n * across all instances — avoids repeated prototype walks.\n */\n private _processMembers(): void {\n const info = getClassMemberInfo(this, ViewModel.prototype, RESERVED_ASYNC_KEYS, LIFECYCLE_HOOKS);\n\n // Memoize getters\n for (let i = 0; i < info.getters.length; i++) {\n this._wrapGetter(info.getters[i].key, info.getters[i].get);\n }\n\n // DEV: Instance property reserved key check (must run even if no methods to wrap)\n if (__DEV__) {\n for (const key of RESERVED_ASYNC_KEYS) {\n if (Object.getOwnPropertyDescriptor(this, key)?.value !== undefined) {\n throw new Error(\n `[mvc-kit] \"${key}\" is a reserved property on ViewModel and cannot be overridden.`\n );\n }\n }\n }\n\n // Skip async wrapping if no methods to wrap\n if (info.methods.length === 0) return;\n\n // Lazily allocate async tracking collections\n if (!this._asyncStates) this._asyncStates = new Map();\n if (!this._asyncSnapshots) this._asyncSnapshots = new Map();\n if (!this._asyncListeners) this._asyncListeners = new Set();\n\n // Initialize DEV-only active ops tracking\n if (__DEV__) {\n this._activeOps = new Map();\n }\n\n // Wrap async methods (shared with Resource)\n wrapAsyncMethods({\n instance: this,\n stopPrototype: ViewModel.prototype,\n reservedKeys: RESERVED_ASYNC_KEYS,\n lifecycleHooks: LIFECYCLE_HOOKS,\n isDisposed: () => this._disposed,\n isInitialized: () => this._initialized,\n asyncStates: this._asyncStates,\n asyncSnapshots: this._asyncSnapshots,\n asyncListeners: this._asyncListeners,\n notifyAsync: () => this._notifyAsync(),\n addCleanup: (fn) => this.addCleanup(fn),\n ghostTimeout: (this.constructor as typeof ViewModel).GHOST_TIMEOUT,\n className: 'ViewModel',\n activeOps: this._activeOps,\n methods: info.methods,\n });\n }\n\n // ── Auto-tracking internals ────────────────────────────────────\n\n /**\n * Installs a context-sensitive state getter on the instance.\n *\n * During getter tracking (_activeStateTracking is active): returns a Proxy\n * that records which state properties are accessed. The Proxy is created\n * lazily on first tracking access to keep init() fast.\n *\n * Otherwise: returns the frozen state object directly. This is critical\n * for React's useSyncExternalStore — it needs a changing reference to\n * detect state updates and trigger re-renders.\n */\n private _installStateProxy(): void {\n let stateProxy: S | null = null;\n\n Object.defineProperty(this, 'state', {\n get: () => {\n if (_activeStateTracking) {\n if (!stateProxy) {\n stateProxy = new Proxy({} as S, {\n get: (_, prop: string) => {\n _activeStateTracking?.add(prop);\n return (this._state as any)[prop];\n },\n ownKeys: () => Reflect.ownKeys(this._state as object),\n getOwnPropertyDescriptor: (_, prop) =>\n Reflect.getOwnPropertyDescriptor(this._state as object, prop),\n set: () => {\n throw new Error('Cannot mutate state directly. Use set() instead.');\n },\n has: (_, prop) => prop in (this._state as object),\n });\n }\n return stateProxy;\n }\n return this._state;\n },\n configurable: true,\n enumerable: true,\n });\n }\n\n /**\n * Scans own instance properties for Subscribable objects and sets up\n * automatic dependency tracking for each one found.\n *\n * For each subscribable member:\n * 1. Subscribe to it. On notification: bump its tracked revision\n * AND the VM's global revision, then force a new state reference\n * and notify listeners so React re-renders.\n * 2. Replace the instance property with a getter that participates\n * in dependency tracking.\n * 3. Register unsubscribe in the dispose chain.\n *\n * Called during init(), AFTER all subclass property initializers\n * have run (they execute during the constructor, before init()).\n */\n private _trackSubscribables(): void {\n for (const key of Object.getOwnPropertyNames(this)) {\n const value = (this as any)[key];\n if (!isAutoTrackable(value)) continue;\n\n let tracked: TrackedSource;\n\n const onSourceNotify = () => {\n if (this._disposed) return;\n\n // Source notified — bump revisions for getter invalidation\n tracked.revision++;\n this._revision++;\n\n for (const listener of this._listeners) {\n listener(this._state, this._state);\n }\n };\n\n const unsubState = value.subscribe(onSourceNotify);\n const unsubAsync =\n typeof value.subscribeAsync === 'function'\n ? value.subscribeAsync(onSourceNotify)\n : undefined;\n\n tracked = {\n source: value,\n revision: 0,\n unsubscribe: unsubAsync\n ? () => { unsubState(); unsubAsync(); }\n : unsubState,\n };\n\n this._trackedSources.set(key, tracked);\n\n // Replace the instance property with a tracking getter.\n // The original value is captured in the closure.\n Object.defineProperty(this, key, {\n get: () => {\n _activeSourceTracking?.set(key, tracked);\n return value;\n },\n configurable: true,\n enumerable: false,\n });\n }\n }\n\n /**\n * Bubbles cached dependency records to the active parent tracking context.\n * Called from Tier 1/Tier 2 cache hits during nested getter composition\n * so the parent getter records the full transitive dependency set.\n * Extracted to keep the getter closure small for V8 inlining.\n */\n private _bubbleDeps(stateDepKeys: string[] | undefined, sourceDepKeys: string[] | undefined): void {\n const st = _activeStateTracking!;\n if (stateDepKeys) {\n for (let i = 0; i < stateDepKeys.length; i++) st.add(stateDepKeys[i]);\n }\n if (sourceDepKeys) {\n const srt = _activeSourceTracking!;\n for (let i = 0; i < sourceDepKeys.length; i++) {\n const ts = this._trackedSources.get(sourceDepKeys[i]);\n if (ts) srt.set(sourceDepKeys[i], ts);\n }\n }\n }\n\n /**\n * Replaces a single prototype getter with a memoized version on this\n * instance. The memoized getter tracks both state dependencies and\n * subscribable member dependencies, caching its result and\n * revalidating through a three-tier strategy:\n *\n * Tier 1 (fast): revision unchanged → return cached (1 int compare)\n * Tier 2 (medium): revision changed but this getter's deps didn't → return cached\n * Tier 3 (slow): at least one dep changed → full recompute with tracking\n */\n private _wrapGetter(key: string, original: () => unknown): void {\n // Per-getter cache state, private to this getter on this instance.\n let cached: unknown;\n let validatedAtRevision = -1;\n\n // Array-based dep tracking — avoids Map iterator allocation in Tier 2\n let stateDepKeys: string[] | undefined;\n let stateDepValues: unknown[] | undefined;\n let sourceDepKeys: string[] | undefined;\n let sourceDepRevisions: number[] | undefined;\n\n // Reusable tracking containers — allocated on first Tier 3, reused via clear()\n let trackingSet: Set<string> | undefined;\n let trackingMap: Map<string, TrackedSource> | undefined;\n\n Object.defineProperty(this, key, {\n get: () => {\n // ── Tier 1: Fast path (1 integer compare) ───────────────\n if (validatedAtRevision === this._revision) {\n if (_activeStateTracking) this._bubbleDeps(stateDepKeys, sourceDepKeys);\n return cached;\n }\n\n // After dispose, revision never changes so Tier 1 hits if\n // getter was ever called. Guard the uncalled-before-dispose edge case.\n if (this._disposed) return cached;\n\n // ── Tier 2: Medium path — array-based dep check ─────────\n if (stateDepKeys !== undefined) {\n let fresh = true;\n\n // Check state deps by reference (array iteration, no iterator alloc)\n const state = this._state as any;\n for (let i = 0; i < stateDepKeys.length; i++) {\n if (state[stateDepKeys[i]] !== stateDepValues![i]) {\n fresh = false;\n break;\n }\n }\n\n // Check subscribable deps by revision\n if (fresh && sourceDepKeys !== undefined && sourceDepKeys.length > 0) {\n for (let i = 0; i < sourceDepKeys.length; i++) {\n const ts = this._trackedSources.get(sourceDepKeys[i]);\n if (ts && ts.revision !== sourceDepRevisions![i]) {\n fresh = false;\n break;\n }\n }\n }\n\n if (fresh) {\n if (_activeStateTracking) this._bubbleDeps(stateDepKeys, sourceDepKeys);\n validatedAtRevision = this._revision;\n return cached;\n }\n }\n\n // ── Tier 3: Slow path — full recompute ─────────────────\n // Save parent tracking context for nested getter composition\n const parentStateTracking = _activeStateTracking;\n const parentSourceTracking = _activeSourceTracking;\n\n // Reuse tracking containers (clear instead of allocate)\n if (trackingSet) {\n trackingSet.clear();\n } else {\n trackingSet = new Set();\n }\n if (trackingMap) {\n trackingMap.clear();\n } else {\n trackingMap = new Map();\n }\n\n _activeStateTracking = trackingSet;\n _activeSourceTracking = trackingMap;\n\n try {\n cached = original.call(this);\n } catch (e) {\n // Don't cache failed computations\n _activeStateTracking = parentStateTracking;\n _activeSourceTracking = parentSourceTracking;\n throw e;\n }\n\n // Restore parent tracking context\n _activeStateTracking = parentStateTracking;\n _activeSourceTracking = parentSourceTracking;\n\n // Bubble deps up to parent getter if nested\n if (parentStateTracking) {\n for (const d of trackingSet) parentStateTracking.add(d);\n }\n if (parentSourceTracking) {\n for (const [k, v] of trackingMap) {\n parentSourceTracking.set(k, v);\n }\n }\n\n // Snapshot state dep values into arrays for Tier 2\n const depCount = trackingSet.size;\n if (!stateDepKeys || stateDepKeys.length !== depCount) {\n stateDepKeys = new Array(depCount);\n stateDepValues = new Array(depCount);\n }\n {\n let i = 0;\n const state = this._state as any;\n for (const d of trackingSet) {\n stateDepKeys[i] = d;\n stateDepValues![i] = state[d];\n i++;\n }\n }\n\n // Snapshot subscribable revisions into arrays\n const sourceCount = trackingMap.size;\n if (sourceCount > 0) {\n if (!sourceDepKeys || sourceDepKeys.length !== sourceCount) {\n sourceDepKeys = new Array(sourceCount);\n sourceDepRevisions = new Array(sourceCount);\n }\n let i = 0;\n for (const [memberKey, tracked] of trackingMap) {\n sourceDepKeys[i] = memberKey;\n sourceDepRevisions![i] = tracked.revision;\n i++;\n }\n } else {\n sourceDepKeys = undefined;\n sourceDepRevisions = undefined;\n }\n\n validatedAtRevision = this._revision;\n\n return cached;\n },\n configurable: true,\n enumerable: true,\n });\n }\n}\n"],"names":["walkPrototypeChain","EventBus","wrapAsyncMethods"],"mappings":"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"}
|
package/dist/ViewModel.d.ts
CHANGED
|
@@ -26,8 +26,6 @@ export declare abstract class ViewModel<S extends object = EmptyViewModelState,
|
|
|
26
26
|
private _subscriptionCleanups;
|
|
27
27
|
private _eventBus;
|
|
28
28
|
private _revision;
|
|
29
|
-
private _stateTracking;
|
|
30
|
-
private _sourceTracking;
|
|
31
29
|
private _trackedSources;
|
|
32
30
|
private _asyncStates;
|
|
33
31
|
private _asyncSnapshots;
|
|
@@ -107,7 +105,11 @@ export declare abstract class ViewModel<S extends object = EmptyViewModelState,
|
|
|
107
105
|
subscribeAsync(listener: () => void): () => void;
|
|
108
106
|
private _notifyAsync;
|
|
109
107
|
private _teardownSubscriptions;
|
|
110
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Guards reserved keys and auto-binds subclass methods in a single pass
|
|
110
|
+
* using the cached class metadata from getClassMemberInfo.
|
|
111
|
+
*/
|
|
112
|
+
private _guardAndBind;
|
|
111
113
|
/**
|
|
112
114
|
* Uses cached class metadata to memoize getters (RFC 1) and delegates
|
|
113
115
|
* async method wrapping to the shared wrapAsyncMethods helper (RFC 2).
|
|
@@ -118,7 +120,7 @@ export declare abstract class ViewModel<S extends object = EmptyViewModelState,
|
|
|
118
120
|
/**
|
|
119
121
|
* Installs a context-sensitive state getter on the instance.
|
|
120
122
|
*
|
|
121
|
-
* During getter tracking (
|
|
123
|
+
* During getter tracking (_activeStateTracking is active): returns a Proxy
|
|
122
124
|
* that records which state properties are accessed. The Proxy is created
|
|
123
125
|
* lazily on first tracking access to keep init() fast.
|
|
124
126
|
*
|
|
@@ -143,6 +145,13 @@ export declare abstract class ViewModel<S extends object = EmptyViewModelState,
|
|
|
143
145
|
* have run (they execute during the constructor, before init()).
|
|
144
146
|
*/
|
|
145
147
|
private _trackSubscribables;
|
|
148
|
+
/**
|
|
149
|
+
* Bubbles cached dependency records to the active parent tracking context.
|
|
150
|
+
* Called from Tier 1/Tier 2 cache hits during nested getter composition
|
|
151
|
+
* so the parent getter records the full transitive dependency set.
|
|
152
|
+
* Extracted to keep the getter closure small for V8 inlining.
|
|
153
|
+
*/
|
|
154
|
+
private _bubbleDeps;
|
|
146
155
|
/**
|
|
147
156
|
* Replaces a single prototype getter with a memoized version on this
|
|
148
157
|
* instance. The memoized getter tracks both state dependencies and
|