mvc-kit 2.7.1 → 2.8.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 +18 -1
- package/agent-config/claude-code/skills/guide/SKILL.md +1 -0
- package/agent-config/claude-code/skills/guide/api-reference.md +8 -1
- package/agent-config/claude-code/skills/scaffold/templates/model.md +38 -1
- package/agent-config/copilot/copilot-instructions.md +2 -1
- package/agent-config/cursor/cursorrules +2 -1
- package/dist/Collection.cjs +31 -17
- package/dist/Collection.cjs.map +1 -1
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Collection.js +31 -17
- package/dist/Collection.js.map +1 -1
- package/dist/Model.cjs +22 -4
- package/dist/Model.cjs.map +1 -1
- package/dist/Model.d.ts +2 -0
- package/dist/Model.d.ts.map +1 -1
- package/dist/Model.js +22 -4
- package/dist/Model.js.map +1 -1
- package/dist/PersistentCollection.cjs +8 -10
- package/dist/PersistentCollection.cjs.map +1 -1
- package/dist/PersistentCollection.d.ts +1 -0
- package/dist/PersistentCollection.d.ts.map +1 -1
- package/dist/PersistentCollection.js +8 -10
- package/dist/PersistentCollection.js.map +1 -1
- package/dist/Resource.cjs +20 -156
- package/dist/Resource.cjs.map +1 -1
- package/dist/Resource.d.ts +0 -2
- package/dist/Resource.d.ts.map +1 -1
- package/dist/Resource.js +20 -156
- package/dist/Resource.js.map +1 -1
- package/dist/ViewModel.cjs +177 -227
- package/dist/ViewModel.cjs.map +1 -1
- package/dist/ViewModel.d.ts +9 -12
- package/dist/ViewModel.d.ts.map +1 -1
- package/dist/ViewModel.js +177 -227
- package/dist/ViewModel.js.map +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/use-instance.cjs +31 -21
- package/dist/react/use-instance.cjs.map +1 -1
- package/dist/react/use-instance.d.ts +1 -1
- package/dist/react/use-instance.d.ts.map +1 -1
- package/dist/react/use-instance.js +32 -22
- package/dist/react/use-instance.js.map +1 -1
- package/dist/react/use-model.cjs +29 -2
- package/dist/react/use-model.cjs.map +1 -1
- package/dist/react/use-model.d.ts +9 -0
- package/dist/react/use-model.d.ts.map +1 -1
- package/dist/react/use-model.js +30 -3
- package/dist/react/use-model.js.map +1 -1
- package/dist/react.cjs +1 -0
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +2 -1
- package/dist/walkPrototypeChain.cjs.map +1 -1
- package/dist/walkPrototypeChain.d.ts +2 -2
- package/dist/walkPrototypeChain.js.map +1 -1
- package/dist/wrapAsyncMethods.cjs +179 -0
- package/dist/wrapAsyncMethods.cjs.map +1 -0
- package/dist/wrapAsyncMethods.d.ts +35 -0
- package/dist/wrapAsyncMethods.d.ts.map +1 -0
- package/dist/wrapAsyncMethods.js +179 -0
- package/dist/wrapAsyncMethods.js.map +1 -0
- package/package.json +1 -1
package/dist/Resource.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Resource.js","sources":["../src/Resource.ts"],"sourcesContent":["import { Collection } from './Collection';\nimport { walkPrototypeChain } from './walkPrototypeChain';\nimport { isAbortError, classifyError } from './errors';\nimport type { Listener, TaskState } from './types';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\n// ── Async tracking types ────────────────────────────────────────\n\nconst DEFAULT_TASK_STATE: TaskState = Object.freeze({ loading: false, error: null, errorCode: null });\n\nexport type ResourceAsyncMethodKeys<T> = {\n [K in Exclude<keyof T, keyof Resource<any>>]: T[K] extends (...args: any[]) => Promise<any> ? K : never;\n}[Exclude<keyof T, keyof Resource<any>>];\n\ntype ResourceAsyncMap<T> = {\n readonly [K in ResourceAsyncMethodKeys<T>]: TaskState;\n};\n\ninterface InternalTaskState {\n loading: boolean;\n error: string | null;\n errorCode: TaskState['errorCode'];\n count: number;\n}\n\nconst RESERVED_ASYNC_KEYS = ['async', 'subscribeAsync'] as const;\nconst LIFECYCLE_HOOKS = new Set(['onInit', 'onDispose']);\n\n// ── Resource ────────────────────────────────────────────────────\n\n/**\n * Collection + async tracking toolkit. Extends Collection with lifecycle\n * (init/dispose) and automatic async method tracking. Optionally delegates\n * to an external Collection for shared data scenarios.\n */\nexport class Resource<T extends { id: string | number }> extends Collection<T> {\n private _external: Collection<T> | null = null;\n private _initialized = false;\n\n // ── Async tracking fields ──\n private _asyncStates = new Map<string, InternalTaskState>();\n private _asyncSnapshots = new Map<string, TaskState>();\n private _asyncListeners = new Set<() => void>();\n private _asyncProxy: ResourceAsyncMap<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(collectionOrItems?: Collection<T> | T[]) {\n const isExternal = collectionOrItems != null && !Array.isArray(collectionOrItems);\n super(isExternal ? [] : (collectionOrItems as T[]) ?? []);\n\n if (isExternal) {\n this._external = collectionOrItems as Collection<T>;\n\n if (__DEV__) {\n const Ctor = this.constructor as typeof Resource;\n if (Ctor.MAX_SIZE > 0 || Ctor.TTL > 0) {\n console.warn(\n `[mvc-kit] Resource \"${Ctor.name}\" has MAX_SIZE or TTL set but uses an ` +\n `injected Collection. Configure these on the Collection instead.`\n );\n }\n }\n }\n\n this._guardReservedKeys();\n }\n\n // ── Lifecycle ─────────────────────────────────────────────────\n\n /** Whether init() has been called. */\n get initialized(): boolean {\n return this._initialized;\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._wrapMethods();\n return this.onInit?.();\n }\n\n /** Lifecycle hook called at the end of init(). Override to load initial data. @protected */\n protected onInit?(): void | Promise<void>;\n\n // ── Collection delegation ─────────────────────────────────────\n\n get state(): T[] {\n return this._external ? this._external.state : super.state;\n }\n\n get items(): T[] {\n return this._external ? this._external.items : super.items;\n }\n\n get length(): number {\n return this._external ? this._external.length : super.length;\n }\n\n add(...items: T[]): void {\n this._external ? this._external.add(...items) : super.add(...items);\n }\n\n upsert(...items: T[]): void {\n this._external ? this._external.upsert(...items) : super.upsert(...items);\n }\n\n update(id: T['id'], changes: Partial<T>): void {\n this._external ? this._external.update(id, changes) : super.update(id, changes);\n }\n\n remove(...ids: T['id'][]): void {\n this._external ? this._external.remove(...ids) : super.remove(...ids);\n }\n\n reset(items: T[]): void {\n this._external ? this._external.reset(items) : super.reset(items);\n }\n\n clear(): void {\n this._external ? this._external.clear() : super.clear();\n }\n\n optimistic(callback: () => void): () => void {\n return this._external ? this._external.optimistic(callback) : super.optimistic(callback);\n }\n\n get(id: T['id']): T | undefined {\n return this._external ? this._external.get(id) : super.get(id);\n }\n\n has(id: T['id']): boolean {\n return this._external ? this._external.has(id) : super.has(id);\n }\n\n find(predicate: (item: T) => boolean): T | undefined {\n return this._external ? this._external.find(predicate) : super.find(predicate);\n }\n\n filter(predicate: (item: T) => boolean): T[] {\n return this._external ? this._external.filter(predicate) : super.filter(predicate);\n }\n\n sorted(compareFn: (a: T, b: T) => number): T[] {\n return this._external ? this._external.sorted(compareFn) : super.sorted(compareFn);\n }\n\n map<U>(fn: (item: T) => U): U[] {\n return this._external ? this._external.map(fn) : super.map(fn);\n }\n\n subscribe(listener: Listener<T[]>): () => void {\n if (this.disposed) return () => {};\n return this._external ? this._external.subscribe(listener) : super.subscribe(listener);\n }\n\n // ── Async tracking API ────────────────────────────────────────\n\n /** Proxy providing `TaskState` (loading, error, errorCode) per async method. */\n get async(): ResourceAsyncMap<this> {\n if (!this._asyncProxy) {\n const self = this;\n this._asyncProxy = new Proxy({} as ResourceAsyncMap<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);\n },\n ownKeys() {\n return 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 by `useInstance` for React integration. */\n subscribeAsync(listener: () => void): () => void {\n if (this.disposed) return () => {};\n this._asyncListeners.add(listener);\n return () => { this._asyncListeners.delete(listener); };\n }\n\n // ── Private: async tracking internals ─────────────────────────\n\n private _notifyAsync(): void {\n for (const listener of this._asyncListeners) {\n listener();\n }\n }\n\n private _guardReservedKeys(): void {\n walkPrototypeChain(this, Resource.prototype, (key) => {\n if (RESERVED_ASYNC_KEYS.includes(key as any)) {\n throw new Error(\n `[mvc-kit] \"${key}\" is a reserved property on Resource and cannot be overridden.`\n );\n }\n });\n }\n\n private _wrapMethods(): void {\n // Instance property check (class fields assigned after super())\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 Resource and cannot be overridden.`\n );\n }\n }\n\n const self = this;\n const processed = new Set<string>();\n const wrappedKeys: string[] = [];\n\n if (__DEV__) {\n this._activeOps = new Map();\n }\n\n walkPrototypeChain(this, Resource.prototype, (key, desc) => {\n // Skip getters/setters\n if (desc.get || desc.set) return;\n // Skip non-functions\n if (typeof desc.value !== 'function') return;\n // Skip _-prefixed (private convention)\n if (key.startsWith('_')) return;\n // Skip lifecycle hooks (managed by framework)\n if (LIFECYCLE_HOOKS.has(key)) return;\n // Most-derived wins\n if (processed.has(key)) return;\n processed.add(key);\n\n const original = desc.value as (...args: unknown[]) => unknown;\n let pruned = false;\n\n const wrapper = function (this: any, ...args: unknown[]) {\n // Disposed guard\n if (self.disposed) {\n if (__DEV__) {\n console.warn(`[mvc-kit] \"${key}\" called after dispose — ignored.`);\n }\n return undefined;\n }\n\n // Pre-init guard (DEV only — method still executes)\n if (__DEV__ && !self._initialized) {\n console.warn(\n `[mvc-kit] \"${key}\" called before init(). ` +\n `Async tracking is active only after init().`\n );\n }\n\n let result: unknown;\n try {\n result = original.apply(self, args);\n } catch (e) {\n // Sync throw — not tracked as async\n throw e;\n }\n\n // Sync detection: if not thenable, prune from async tracking\n if (!result || typeof (result as any).then !== 'function') {\n if (!pruned) {\n pruned = true;\n // Remove from async maps\n self._asyncStates.delete(key);\n self._asyncSnapshots.delete(key);\n // Replace wrapper with bound original for zero overhead\n (self as any)[key] = original.bind(self);\n }\n return result;\n }\n\n // ── Async tracking ──────────────────────────────────────\n let internal = self._asyncStates.get(key);\n if (!internal) {\n internal = { loading: false, error: null, errorCode: null, count: 0 };\n self._asyncStates.set(key, internal);\n }\n\n internal.count++;\n internal.loading = true;\n internal.error = null;\n internal.errorCode = null;\n self._asyncSnapshots.set(key, Object.freeze({ loading: true, error: null, errorCode: null }));\n self._notifyAsync();\n\n if (__DEV__ && self._activeOps) {\n self._activeOps.set(key, (self._activeOps.get(key) ?? 0) + 1);\n }\n\n return (result as Promise<unknown>).then(\n (value) => {\n if (self.disposed) return value;\n\n internal!.count--;\n internal!.loading = internal!.count > 0;\n self._asyncSnapshots.set(\n key,\n Object.freeze({ loading: internal!.loading, error: internal!.error, errorCode: internal!.errorCode }),\n );\n self._notifyAsync();\n\n if (__DEV__ && self._activeOps) {\n const c = (self._activeOps.get(key) ?? 1) - 1;\n if (c <= 0) self._activeOps.delete(key);\n else self._activeOps.set(key, c);\n }\n\n return value;\n },\n (error) => {\n // AbortError — silently swallow\n if (isAbortError(error)) {\n if (!self.disposed) {\n internal!.count--;\n internal!.loading = internal!.count > 0;\n self._asyncSnapshots.set(\n key,\n Object.freeze({ loading: internal!.loading, error: internal!.error, errorCode: internal!.errorCode }),\n );\n self._notifyAsync();\n }\n\n if (__DEV__ && self._activeOps) {\n const c = (self._activeOps.get(key) ?? 1) - 1;\n if (c <= 0) self._activeOps.delete(key);\n else self._activeOps.set(key, c);\n }\n\n return undefined;\n }\n\n // Disposed — fizzle silently\n if (self.disposed) return undefined;\n\n internal!.count--;\n internal!.loading = internal!.count > 0;\n const classified = classifyError(error);\n internal!.error = classified.message;\n internal!.errorCode = classified.code;\n self._asyncSnapshots.set(\n key,\n Object.freeze({ loading: internal!.loading, error: classified.message, errorCode: classified.code }),\n );\n self._notifyAsync();\n\n if (__DEV__ && self._activeOps) {\n const c = (self._activeOps.get(key) ?? 1) - 1;\n if (c <= 0) self._activeOps.delete(key);\n else self._activeOps.set(key, c);\n }\n\n // Re-throw to preserve standard Promise rejection\n throw error;\n },\n );\n };\n\n wrappedKeys.push(key);\n (self as any)[key] = wrapper;\n });\n\n // Register cleanup for disposal\n if (wrappedKeys.length > 0) {\n this.addCleanup(() => {\n // Snapshot active ops for ghost check before clearing\n const opsSnapshot = __DEV__ && self._activeOps ? new Map(self._activeOps) : null;\n\n // Swap all wrapped methods to no-ops (with DEV warning)\n for (const k of wrappedKeys) {\n if (__DEV__) {\n (self as any)[k] = () => {\n console.warn(`[mvc-kit] \"${k}\" called after dispose — ignored.`);\n return undefined;\n };\n } else {\n (self as any)[k] = () => undefined;\n }\n }\n\n // Clear async state\n self._asyncListeners.clear();\n self._asyncStates.clear();\n self._asyncSnapshots.clear();\n\n // DEV: schedule ghost check\n if (__DEV__ && opsSnapshot && opsSnapshot.size > 0) {\n self._scheduleGhostCheck(opsSnapshot);\n }\n });\n }\n }\n\n private _scheduleGhostCheck(opsSnapshot: Map<string, number>): void {\n if (!__DEV__) return;\n setTimeout(() => {\n for (const [key, count] of opsSnapshot) {\n console.warn(\n `[mvc-kit] Ghost async operation detected: \"${key}\" had ${count} ` +\n `pending call(s) when the Resource was disposed. ` +\n `Consider using disposeSignal to cancel in-flight work.`\n );\n }\n }, (this.constructor as typeof Resource).GHOST_TIMEOUT);\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Resource.js","sources":["../src/Resource.ts"],"sourcesContent":["import { Collection } from './Collection';\nimport { walkPrototypeChain } from './walkPrototypeChain';\nimport { wrapAsyncMethods } from './wrapAsyncMethods';\nimport type { InternalTaskState } from './wrapAsyncMethods';\nimport type { Listener, TaskState } from './types';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\n// ── Async tracking types ────────────────────────────────────────\n\nconst DEFAULT_TASK_STATE: TaskState = Object.freeze({ loading: false, error: null, errorCode: null });\n\nexport type ResourceAsyncMethodKeys<T> = {\n [K in Exclude<keyof T, keyof Resource<any>>]: T[K] extends (...args: any[]) => Promise<any> ? K : never;\n}[Exclude<keyof T, keyof Resource<any>>];\n\ntype ResourceAsyncMap<T> = {\n readonly [K in ResourceAsyncMethodKeys<T>]: TaskState;\n};\n\nconst RESERVED_ASYNC_KEYS = ['async', 'subscribeAsync'] as const;\nconst LIFECYCLE_HOOKS = new Set(['onInit', 'onDispose']);\n\n// ── Resource ────────────────────────────────────────────────────\n\n/**\n * Collection + async tracking toolkit. Extends Collection with lifecycle\n * (init/dispose) and automatic async method tracking. Optionally delegates\n * to an external Collection for shared data scenarios.\n */\nexport class Resource<T extends { id: string | number }> extends Collection<T> {\n private _external: Collection<T> | null = null;\n private _initialized = false;\n\n // ── Async tracking fields ──\n private _asyncStates = new Map<string, InternalTaskState>();\n private _asyncSnapshots = new Map<string, TaskState>();\n private _asyncListeners = new Set<() => void>();\n private _asyncProxy: ResourceAsyncMap<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(collectionOrItems?: Collection<T> | T[]) {\n const isExternal = collectionOrItems != null && !Array.isArray(collectionOrItems);\n super(isExternal ? [] : (collectionOrItems as T[]) ?? []);\n\n if (isExternal) {\n this._external = collectionOrItems as Collection<T>;\n\n if (__DEV__) {\n const Ctor = this.constructor as typeof Resource;\n if (Ctor.MAX_SIZE > 0 || Ctor.TTL > 0) {\n console.warn(\n `[mvc-kit] Resource \"${Ctor.name}\" has MAX_SIZE or TTL set but uses an ` +\n `injected Collection. Configure these on the Collection instead.`\n );\n }\n }\n }\n\n this._guardReservedKeys();\n }\n\n // ── Lifecycle ─────────────────────────────────────────────────\n\n /** Whether init() has been called. */\n get initialized(): boolean {\n return this._initialized;\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\n if (__DEV__) {\n this._activeOps = new Map();\n }\n\n wrapAsyncMethods({\n instance: this,\n stopPrototype: Resource.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 Resource).GHOST_TIMEOUT,\n className: 'Resource',\n activeOps: this._activeOps,\n });\n\n return this.onInit?.();\n }\n\n /** Lifecycle hook called at the end of init(). Override to load initial data. @protected */\n protected onInit?(): void | Promise<void>;\n\n // ── Collection delegation ─────────────────────────────────────\n\n get state(): T[] {\n return this._external ? this._external.state : super.state;\n }\n\n get items(): T[] {\n return this._external ? this._external.items : super.items;\n }\n\n get length(): number {\n return this._external ? this._external.length : super.length;\n }\n\n add(...items: T[]): void {\n this._external ? this._external.add(...items) : super.add(...items);\n }\n\n upsert(...items: T[]): void {\n this._external ? this._external.upsert(...items) : super.upsert(...items);\n }\n\n update(id: T['id'], changes: Partial<T>): void {\n this._external ? this._external.update(id, changes) : super.update(id, changes);\n }\n\n remove(...ids: T['id'][]): void {\n this._external ? this._external.remove(...ids) : super.remove(...ids);\n }\n\n reset(items: T[]): void {\n this._external ? this._external.reset(items) : super.reset(items);\n }\n\n clear(): void {\n this._external ? this._external.clear() : super.clear();\n }\n\n optimistic(callback: () => void): () => void {\n return this._external ? this._external.optimistic(callback) : super.optimistic(callback);\n }\n\n get(id: T['id']): T | undefined {\n return this._external ? this._external.get(id) : super.get(id);\n }\n\n has(id: T['id']): boolean {\n return this._external ? this._external.has(id) : super.has(id);\n }\n\n find(predicate: (item: T) => boolean): T | undefined {\n return this._external ? this._external.find(predicate) : super.find(predicate);\n }\n\n filter(predicate: (item: T) => boolean): T[] {\n return this._external ? this._external.filter(predicate) : super.filter(predicate);\n }\n\n sorted(compareFn: (a: T, b: T) => number): T[] {\n return this._external ? this._external.sorted(compareFn) : super.sorted(compareFn);\n }\n\n map<U>(fn: (item: T) => U): U[] {\n return this._external ? this._external.map(fn) : super.map(fn);\n }\n\n subscribe(listener: Listener<T[]>): () => void {\n if (this.disposed) return () => {};\n return this._external ? this._external.subscribe(listener) : super.subscribe(listener);\n }\n\n // ── Async tracking API ────────────────────────────────────────\n\n /** Proxy providing `TaskState` (loading, error, errorCode) per async method. */\n get async(): ResourceAsyncMap<this> {\n if (!this._asyncProxy) {\n const self = this;\n this._asyncProxy = new Proxy({} as ResourceAsyncMap<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);\n },\n ownKeys() {\n return 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 by `useInstance` for React integration. */\n subscribeAsync(listener: () => void): () => void {\n if (this.disposed) return () => {};\n this._asyncListeners.add(listener);\n return () => { this._asyncListeners.delete(listener); };\n }\n\n // ── Private: async tracking internals ─────────────────────────\n\n private _notifyAsync(): void {\n for (const listener of this._asyncListeners) {\n listener();\n }\n }\n\n private _guardReservedKeys(): void {\n walkPrototypeChain(this, Resource.prototype, (key) => {\n if (RESERVED_ASYNC_KEYS.includes(key as any)) {\n throw new Error(\n `[mvc-kit] \"${key}\" is a reserved property on Resource and cannot be overridden.`\n );\n }\n });\n }\n\n}\n"],"names":[],"mappings":"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"}
|
package/dist/ViewModel.cjs
CHANGED
|
@@ -1,9 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const EventBus = require("./EventBus.cjs");
|
|
4
|
-
const errors = require("./errors.cjs");
|
|
5
4
|
const walkPrototypeChain = require("./walkPrototypeChain.cjs");
|
|
5
|
+
const wrapAsyncMethods = require("./wrapAsyncMethods.cjs");
|
|
6
6
|
const __DEV__ = typeof __MVC_KIT_DEV__ !== "undefined" && __MVC_KIT_DEV__;
|
|
7
|
+
function freeze(obj) {
|
|
8
|
+
return __DEV__ ? Object.freeze(obj) : obj;
|
|
9
|
+
}
|
|
10
|
+
const classMembers = /* @__PURE__ */ new WeakMap();
|
|
11
|
+
function getClassMemberInfo(instance, stopPrototype, reservedKeys, lifecycleHooks) {
|
|
12
|
+
const ctor = instance.constructor;
|
|
13
|
+
let info = classMembers.get(ctor);
|
|
14
|
+
if (info) return info;
|
|
15
|
+
const getters = [];
|
|
16
|
+
const methods = [];
|
|
17
|
+
const found = [];
|
|
18
|
+
const processedGetters = /* @__PURE__ */ new Set();
|
|
19
|
+
const processedMethods = /* @__PURE__ */ new Set();
|
|
20
|
+
walkPrototypeChain.walkPrototypeChain(instance, stopPrototype, (key, desc) => {
|
|
21
|
+
if (reservedKeys.includes(key)) {
|
|
22
|
+
found.push(key);
|
|
23
|
+
}
|
|
24
|
+
if (desc.get && !processedGetters.has(key)) {
|
|
25
|
+
processedGetters.add(key);
|
|
26
|
+
getters.push({ key, get: desc.get });
|
|
27
|
+
}
|
|
28
|
+
if (!desc.get && !desc.set && typeof desc.value === "function" && !key.startsWith("_") && !lifecycleHooks.has(key) && !processedMethods.has(key)) {
|
|
29
|
+
processedMethods.add(key);
|
|
30
|
+
methods.push({ key, fn: desc.value });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
info = { getters, methods, reservedKeys: found };
|
|
34
|
+
classMembers.set(ctor, info);
|
|
35
|
+
return info;
|
|
36
|
+
}
|
|
7
37
|
function isAutoTrackable(value) {
|
|
8
38
|
return value !== null && typeof value === "object" && typeof value.subscribe === "function";
|
|
9
39
|
}
|
|
@@ -26,16 +56,17 @@ class ViewModel {
|
|
|
26
56
|
_sourceTracking = null;
|
|
27
57
|
_trackedSources = /* @__PURE__ */ new Map();
|
|
28
58
|
// ── Async tracking (RFC 2) ──────────────────────────────────────
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
59
|
+
// Lazily allocated on first async method wrap to keep construction fast.
|
|
60
|
+
_asyncStates = null;
|
|
61
|
+
_asyncSnapshots = null;
|
|
62
|
+
_asyncListeners = null;
|
|
32
63
|
_asyncProxy = null;
|
|
33
64
|
_activeOps = null;
|
|
34
65
|
/** DEV-only timeout (ms) for detecting ghost async operations after dispose. */
|
|
35
66
|
static GHOST_TIMEOUT = 3e3;
|
|
36
67
|
constructor(...args) {
|
|
37
68
|
const initialState = args[0] ?? {};
|
|
38
|
-
this._state =
|
|
69
|
+
this._state = freeze({ ...initialState });
|
|
39
70
|
this._initialState = this._state;
|
|
40
71
|
this._guardReservedKeys();
|
|
41
72
|
}
|
|
@@ -71,8 +102,7 @@ class ViewModel {
|
|
|
71
102
|
this._initialized = true;
|
|
72
103
|
this._trackSubscribables();
|
|
73
104
|
this._installStateProxy();
|
|
74
|
-
this.
|
|
75
|
-
this._wrapMethods();
|
|
105
|
+
this._processMembers();
|
|
76
106
|
return this.onInit?.();
|
|
77
107
|
}
|
|
78
108
|
/**
|
|
@@ -94,15 +124,19 @@ class ViewModel {
|
|
|
94
124
|
return;
|
|
95
125
|
}
|
|
96
126
|
const partial = typeof partialOrUpdater === "function" ? partialOrUpdater(this._state) : partialOrUpdater;
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
127
|
+
let hasChanges = false;
|
|
128
|
+
const current = this._state;
|
|
129
|
+
for (const key in partial) {
|
|
130
|
+
if (partial[key] !== current[key]) {
|
|
131
|
+
hasChanges = true;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
101
135
|
if (!hasChanges) {
|
|
102
136
|
return;
|
|
103
137
|
}
|
|
104
138
|
const prev = this._state;
|
|
105
|
-
const next =
|
|
139
|
+
const next = freeze({ ...prev, ...partial });
|
|
106
140
|
this._state = next;
|
|
107
141
|
this._revision++;
|
|
108
142
|
this.onSet?.(prev, next);
|
|
@@ -155,10 +189,10 @@ class ViewModel {
|
|
|
155
189
|
this._abortController?.abort();
|
|
156
190
|
this._abortController = null;
|
|
157
191
|
this._teardownSubscriptions();
|
|
158
|
-
this._state = newState ?
|
|
192
|
+
this._state = newState ? freeze({ ...newState }) : this._initialState;
|
|
159
193
|
this._revision++;
|
|
160
|
-
this._asyncStates
|
|
161
|
-
this._asyncSnapshots
|
|
194
|
+
this._asyncStates?.clear();
|
|
195
|
+
this._asyncSnapshots?.clear();
|
|
162
196
|
this._notifyAsync();
|
|
163
197
|
this._trackSubscribables();
|
|
164
198
|
for (const listener of this._listeners) {
|
|
@@ -206,16 +240,16 @@ class ViewModel {
|
|
|
206
240
|
const self = this;
|
|
207
241
|
this._asyncProxy = new Proxy({}, {
|
|
208
242
|
get(_, prop) {
|
|
209
|
-
return self._asyncSnapshots
|
|
243
|
+
return self._asyncSnapshots?.get(prop) ?? DEFAULT_TASK_STATE;
|
|
210
244
|
},
|
|
211
245
|
has(_, prop) {
|
|
212
|
-
return self._asyncSnapshots
|
|
246
|
+
return self._asyncSnapshots?.has(prop) ?? false;
|
|
213
247
|
},
|
|
214
248
|
ownKeys() {
|
|
215
|
-
return Array.from(self._asyncSnapshots.keys());
|
|
249
|
+
return self._asyncSnapshots ? Array.from(self._asyncSnapshots.keys()) : [];
|
|
216
250
|
},
|
|
217
251
|
getOwnPropertyDescriptor(_, prop) {
|
|
218
|
-
if (self._asyncSnapshots
|
|
252
|
+
if (self._asyncSnapshots?.has(prop)) {
|
|
219
253
|
return { configurable: true, enumerable: true, value: self._asyncSnapshots.get(prop) };
|
|
220
254
|
}
|
|
221
255
|
return void 0;
|
|
@@ -228,12 +262,14 @@ class ViewModel {
|
|
|
228
262
|
subscribeAsync(listener) {
|
|
229
263
|
if (this._disposed) return () => {
|
|
230
264
|
};
|
|
265
|
+
if (!this._asyncListeners) this._asyncListeners = /* @__PURE__ */ new Set();
|
|
231
266
|
this._asyncListeners.add(listener);
|
|
232
267
|
return () => {
|
|
233
268
|
this._asyncListeners.delete(listener);
|
|
234
269
|
};
|
|
235
270
|
}
|
|
236
271
|
_notifyAsync() {
|
|
272
|
+
if (!this._asyncListeners) return;
|
|
237
273
|
for (const listener of this._asyncListeners) {
|
|
238
274
|
listener();
|
|
239
275
|
}
|
|
@@ -248,195 +284,92 @@ class ViewModel {
|
|
|
248
284
|
}
|
|
249
285
|
}
|
|
250
286
|
_guardReservedKeys() {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
});
|
|
287
|
+
const info = getClassMemberInfo(this, ViewModel.prototype, RESERVED_ASYNC_KEYS, LIFECYCLE_HOOKS);
|
|
288
|
+
if (info.reservedKeys.length > 0) {
|
|
289
|
+
throw new Error(
|
|
290
|
+
`[mvc-kit] "${info.reservedKeys[0]}" is a reserved property on ViewModel and cannot be overridden.`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
258
293
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
294
|
+
// ── Member processing (merged getter memoization + async method wrapping) ──
|
|
295
|
+
/**
|
|
296
|
+
* Uses cached class metadata to memoize getters (RFC 1) and delegates
|
|
297
|
+
* async method wrapping to the shared wrapAsyncMethods helper (RFC 2).
|
|
298
|
+
* Class metadata is computed once per class via getClassMemberInfo() and reused
|
|
299
|
+
* across all instances — avoids repeated prototype walks.
|
|
300
|
+
*/
|
|
301
|
+
_processMembers() {
|
|
302
|
+
const info = getClassMemberInfo(this, ViewModel.prototype, RESERVED_ASYNC_KEYS, LIFECYCLE_HOOKS);
|
|
303
|
+
for (let i = 0; i < info.getters.length; i++) {
|
|
304
|
+
this._wrapGetter(info.getters[i].key, info.getters[i].get);
|
|
305
|
+
}
|
|
306
|
+
if (__DEV__) {
|
|
307
|
+
for (const key of RESERVED_ASYNC_KEYS) {
|
|
308
|
+
if (Object.getOwnPropertyDescriptor(this, key)?.value !== void 0) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
`[mvc-kit] "${key}" is a reserved property on ViewModel and cannot be overridden.`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
265
313
|
}
|
|
266
314
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
315
|
+
if (info.methods.length === 0) return;
|
|
316
|
+
if (!this._asyncStates) this._asyncStates = /* @__PURE__ */ new Map();
|
|
317
|
+
if (!this._asyncSnapshots) this._asyncSnapshots = /* @__PURE__ */ new Map();
|
|
318
|
+
if (!this._asyncListeners) this._asyncListeners = /* @__PURE__ */ new Set();
|
|
270
319
|
if (__DEV__) {
|
|
271
320
|
this._activeOps = /* @__PURE__ */ new Map();
|
|
272
321
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (__DEV__ && !self._initialized) {
|
|
290
|
-
console.warn(
|
|
291
|
-
`[mvc-kit] "${key}" called before init(). Async tracking is active only after init().`
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
let result;
|
|
295
|
-
try {
|
|
296
|
-
result = original.apply(self, args);
|
|
297
|
-
} catch (e) {
|
|
298
|
-
throw e;
|
|
299
|
-
}
|
|
300
|
-
if (!result || typeof result.then !== "function") {
|
|
301
|
-
if (!pruned) {
|
|
302
|
-
pruned = true;
|
|
303
|
-
self._asyncStates.delete(key);
|
|
304
|
-
self._asyncSnapshots.delete(key);
|
|
305
|
-
self[key] = original.bind(self);
|
|
306
|
-
}
|
|
307
|
-
return result;
|
|
308
|
-
}
|
|
309
|
-
let internal = self._asyncStates.get(key);
|
|
310
|
-
if (!internal) {
|
|
311
|
-
internal = { loading: false, error: null, errorCode: null, count: 0 };
|
|
312
|
-
self._asyncStates.set(key, internal);
|
|
313
|
-
}
|
|
314
|
-
internal.count++;
|
|
315
|
-
internal.loading = true;
|
|
316
|
-
internal.error = null;
|
|
317
|
-
internal.errorCode = null;
|
|
318
|
-
self._asyncSnapshots.set(key, Object.freeze({ loading: true, error: null, errorCode: null }));
|
|
319
|
-
self._notifyAsync();
|
|
320
|
-
if (__DEV__ && self._activeOps) {
|
|
321
|
-
self._activeOps.set(key, (self._activeOps.get(key) ?? 0) + 1);
|
|
322
|
-
}
|
|
323
|
-
return result.then(
|
|
324
|
-
(value) => {
|
|
325
|
-
if (self._disposed) return value;
|
|
326
|
-
internal.count--;
|
|
327
|
-
internal.loading = internal.count > 0;
|
|
328
|
-
self._asyncSnapshots.set(
|
|
329
|
-
key,
|
|
330
|
-
Object.freeze({ loading: internal.loading, error: internal.error, errorCode: internal.errorCode })
|
|
331
|
-
);
|
|
332
|
-
self._notifyAsync();
|
|
333
|
-
if (__DEV__ && self._activeOps) {
|
|
334
|
-
const c = (self._activeOps.get(key) ?? 1) - 1;
|
|
335
|
-
if (c <= 0) self._activeOps.delete(key);
|
|
336
|
-
else self._activeOps.set(key, c);
|
|
337
|
-
}
|
|
338
|
-
return value;
|
|
339
|
-
},
|
|
340
|
-
(error) => {
|
|
341
|
-
if (errors.isAbortError(error)) {
|
|
342
|
-
if (!self._disposed) {
|
|
343
|
-
internal.count--;
|
|
344
|
-
internal.loading = internal.count > 0;
|
|
345
|
-
self._asyncSnapshots.set(
|
|
346
|
-
key,
|
|
347
|
-
Object.freeze({ loading: internal.loading, error: internal.error, errorCode: internal.errorCode })
|
|
348
|
-
);
|
|
349
|
-
self._notifyAsync();
|
|
350
|
-
}
|
|
351
|
-
if (__DEV__ && self._activeOps) {
|
|
352
|
-
const c = (self._activeOps.get(key) ?? 1) - 1;
|
|
353
|
-
if (c <= 0) self._activeOps.delete(key);
|
|
354
|
-
else self._activeOps.set(key, c);
|
|
355
|
-
}
|
|
356
|
-
return void 0;
|
|
357
|
-
}
|
|
358
|
-
if (self._disposed) return void 0;
|
|
359
|
-
internal.count--;
|
|
360
|
-
internal.loading = internal.count > 0;
|
|
361
|
-
const classified = errors.classifyError(error);
|
|
362
|
-
internal.error = classified.message;
|
|
363
|
-
internal.errorCode = classified.code;
|
|
364
|
-
self._asyncSnapshots.set(
|
|
365
|
-
key,
|
|
366
|
-
Object.freeze({ loading: internal.loading, error: classified.message, errorCode: classified.code })
|
|
367
|
-
);
|
|
368
|
-
self._notifyAsync();
|
|
369
|
-
if (__DEV__ && self._activeOps) {
|
|
370
|
-
const c = (self._activeOps.get(key) ?? 1) - 1;
|
|
371
|
-
if (c <= 0) self._activeOps.delete(key);
|
|
372
|
-
else self._activeOps.set(key, c);
|
|
373
|
-
}
|
|
374
|
-
throw error;
|
|
375
|
-
}
|
|
376
|
-
);
|
|
377
|
-
};
|
|
378
|
-
wrappedKeys.push(key);
|
|
379
|
-
self[key] = wrapper;
|
|
322
|
+
wrapAsyncMethods.wrapAsyncMethods({
|
|
323
|
+
instance: this,
|
|
324
|
+
stopPrototype: ViewModel.prototype,
|
|
325
|
+
reservedKeys: RESERVED_ASYNC_KEYS,
|
|
326
|
+
lifecycleHooks: LIFECYCLE_HOOKS,
|
|
327
|
+
isDisposed: () => this._disposed,
|
|
328
|
+
isInitialized: () => this._initialized,
|
|
329
|
+
asyncStates: this._asyncStates,
|
|
330
|
+
asyncSnapshots: this._asyncSnapshots,
|
|
331
|
+
asyncListeners: this._asyncListeners,
|
|
332
|
+
notifyAsync: () => this._notifyAsync(),
|
|
333
|
+
addCleanup: (fn) => this.addCleanup(fn),
|
|
334
|
+
ghostTimeout: this.constructor.GHOST_TIMEOUT,
|
|
335
|
+
className: "ViewModel",
|
|
336
|
+
activeOps: this._activeOps,
|
|
337
|
+
methods: info.methods
|
|
380
338
|
});
|
|
381
|
-
if (wrappedKeys.length > 0) {
|
|
382
|
-
this.addCleanup(() => {
|
|
383
|
-
const opsSnapshot = __DEV__ && self._activeOps ? new Map(self._activeOps) : null;
|
|
384
|
-
for (const k of wrappedKeys) {
|
|
385
|
-
if (__DEV__) {
|
|
386
|
-
self[k] = () => {
|
|
387
|
-
console.warn(`[mvc-kit] "${k}" called after dispose — ignored.`);
|
|
388
|
-
return void 0;
|
|
389
|
-
};
|
|
390
|
-
} else {
|
|
391
|
-
self[k] = () => void 0;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
self._asyncListeners.clear();
|
|
395
|
-
self._asyncStates.clear();
|
|
396
|
-
self._asyncSnapshots.clear();
|
|
397
|
-
if (__DEV__ && opsSnapshot && opsSnapshot.size > 0) {
|
|
398
|
-
self._scheduleGhostCheck(opsSnapshot);
|
|
399
|
-
}
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
_scheduleGhostCheck(opsSnapshot) {
|
|
404
|
-
if (!__DEV__) return;
|
|
405
|
-
setTimeout(() => {
|
|
406
|
-
for (const [key, count] of opsSnapshot) {
|
|
407
|
-
console.warn(
|
|
408
|
-
`[mvc-kit] Ghost async operation detected: "${key}" had ${count} pending call(s) when the ViewModel was disposed. Consider using disposeSignal to cancel in-flight work.`
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
}, this.constructor.GHOST_TIMEOUT);
|
|
412
339
|
}
|
|
413
340
|
// ── Auto-tracking internals ────────────────────────────────────
|
|
414
341
|
/**
|
|
415
342
|
* Installs a context-sensitive state getter on the instance.
|
|
416
343
|
*
|
|
417
344
|
* During getter tracking (_stateTracking is active): returns a Proxy
|
|
418
|
-
* that records which state properties are accessed.
|
|
345
|
+
* that records which state properties are accessed. The Proxy is created
|
|
346
|
+
* lazily on first tracking access to keep init() fast.
|
|
419
347
|
*
|
|
420
348
|
* Otherwise: returns the frozen state object directly. This is critical
|
|
421
349
|
* for React's useSyncExternalStore — it needs a changing reference to
|
|
422
350
|
* detect state updates and trigger re-renders.
|
|
423
351
|
*/
|
|
424
352
|
_installStateProxy() {
|
|
425
|
-
|
|
426
|
-
get: (_, prop) => {
|
|
427
|
-
this._stateTracking?.add(prop);
|
|
428
|
-
return this._state[prop];
|
|
429
|
-
},
|
|
430
|
-
ownKeys: () => Reflect.ownKeys(this._state),
|
|
431
|
-
getOwnPropertyDescriptor: (_, prop) => Reflect.getOwnPropertyDescriptor(this._state, prop),
|
|
432
|
-
set: () => {
|
|
433
|
-
throw new Error("Cannot mutate state directly. Use set() instead.");
|
|
434
|
-
},
|
|
435
|
-
has: (_, prop) => prop in this._state
|
|
436
|
-
});
|
|
353
|
+
let stateProxy = null;
|
|
437
354
|
Object.defineProperty(this, "state", {
|
|
438
355
|
get: () => {
|
|
439
|
-
if (this._stateTracking)
|
|
356
|
+
if (this._stateTracking) {
|
|
357
|
+
if (!stateProxy) {
|
|
358
|
+
stateProxy = new Proxy({}, {
|
|
359
|
+
get: (_, prop) => {
|
|
360
|
+
this._stateTracking?.add(prop);
|
|
361
|
+
return this._state[prop];
|
|
362
|
+
},
|
|
363
|
+
ownKeys: () => Reflect.ownKeys(this._state),
|
|
364
|
+
getOwnPropertyDescriptor: (_, prop) => Reflect.getOwnPropertyDescriptor(this._state, prop),
|
|
365
|
+
set: () => {
|
|
366
|
+
throw new Error("Cannot mutate state directly. Use set() instead.");
|
|
367
|
+
},
|
|
368
|
+
has: (_, prop) => prop in this._state
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
return stateProxy;
|
|
372
|
+
}
|
|
440
373
|
return this._state;
|
|
441
374
|
},
|
|
442
375
|
configurable: true,
|
|
@@ -467,7 +400,6 @@ class ViewModel {
|
|
|
467
400
|
if (this._disposed) return;
|
|
468
401
|
tracked.revision++;
|
|
469
402
|
this._revision++;
|
|
470
|
-
this._state = Object.freeze({ ...this._state });
|
|
471
403
|
for (const listener of this._listeners) {
|
|
472
404
|
listener(this._state, this._state);
|
|
473
405
|
}
|
|
@@ -493,22 +425,6 @@ class ViewModel {
|
|
|
493
425
|
});
|
|
494
426
|
}
|
|
495
427
|
}
|
|
496
|
-
/**
|
|
497
|
-
* Walks the prototype chain from the subclass up to (but not including)
|
|
498
|
-
* ViewModel.prototype. For every getter found, replaces it on the
|
|
499
|
-
* instance with a memoized version that tracks dependencies and caches.
|
|
500
|
-
*
|
|
501
|
-
* Processing order: most-derived class first. If a subclass overrides
|
|
502
|
-
* a parent getter, only the subclass version is memoized.
|
|
503
|
-
*/
|
|
504
|
-
_memoizeGetters() {
|
|
505
|
-
const processed = /* @__PURE__ */ new Set();
|
|
506
|
-
walkPrototypeChain.walkPrototypeChain(this, ViewModel.prototype, (key, desc) => {
|
|
507
|
-
if (!desc.get || processed.has(key)) return;
|
|
508
|
-
processed.add(key);
|
|
509
|
-
this._wrapGetter(key, desc.get);
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
428
|
/**
|
|
513
429
|
* Replaces a single prototype getter with a memoized version on this
|
|
514
430
|
* instance. The memoized getter tracks both state dependencies and
|
|
@@ -522,27 +438,31 @@ class ViewModel {
|
|
|
522
438
|
_wrapGetter(key, original) {
|
|
523
439
|
let cached;
|
|
524
440
|
let validatedAtRevision = -1;
|
|
525
|
-
let
|
|
526
|
-
let
|
|
527
|
-
let
|
|
441
|
+
let stateDepKeys;
|
|
442
|
+
let stateDepValues;
|
|
443
|
+
let sourceDepKeys;
|
|
444
|
+
let sourceDepRevisions;
|
|
445
|
+
let trackingSet;
|
|
446
|
+
let trackingMap;
|
|
528
447
|
Object.defineProperty(this, key, {
|
|
529
448
|
get: () => {
|
|
530
|
-
if (this._disposed) return cached;
|
|
531
449
|
if (validatedAtRevision === this._revision) {
|
|
532
450
|
return cached;
|
|
533
451
|
}
|
|
534
|
-
if (
|
|
452
|
+
if (this._disposed) return cached;
|
|
453
|
+
if (stateDepKeys !== void 0) {
|
|
535
454
|
let fresh = true;
|
|
536
|
-
|
|
537
|
-
|
|
455
|
+
const state = this._state;
|
|
456
|
+
for (let i = 0; i < stateDepKeys.length; i++) {
|
|
457
|
+
if (state[stateDepKeys[i]] !== stateDepValues[i]) {
|
|
538
458
|
fresh = false;
|
|
539
459
|
break;
|
|
540
460
|
}
|
|
541
461
|
}
|
|
542
|
-
if (fresh &&
|
|
543
|
-
for (
|
|
544
|
-
const ts = this._trackedSources.get(
|
|
545
|
-
if (ts && ts.revision !==
|
|
462
|
+
if (fresh && sourceDepKeys !== void 0 && sourceDepKeys.length > 0) {
|
|
463
|
+
for (let i = 0; i < sourceDepKeys.length; i++) {
|
|
464
|
+
const ts = this._trackedSources.get(sourceDepKeys[i]);
|
|
465
|
+
if (ts && ts.revision !== sourceDepRevisions[i]) {
|
|
546
466
|
fresh = false;
|
|
547
467
|
break;
|
|
548
468
|
}
|
|
@@ -555,8 +475,18 @@ class ViewModel {
|
|
|
555
475
|
}
|
|
556
476
|
const parentStateTracking = this._stateTracking;
|
|
557
477
|
const parentSourceTracking = this._sourceTracking;
|
|
558
|
-
|
|
559
|
-
|
|
478
|
+
if (trackingSet) {
|
|
479
|
+
trackingSet.clear();
|
|
480
|
+
} else {
|
|
481
|
+
trackingSet = /* @__PURE__ */ new Set();
|
|
482
|
+
}
|
|
483
|
+
if (trackingMap) {
|
|
484
|
+
trackingMap.clear();
|
|
485
|
+
} else {
|
|
486
|
+
trackingMap = /* @__PURE__ */ new Map();
|
|
487
|
+
}
|
|
488
|
+
this._stateTracking = trackingSet;
|
|
489
|
+
this._sourceTracking = trackingMap;
|
|
560
490
|
try {
|
|
561
491
|
cached = original.call(this);
|
|
562
492
|
} catch (e) {
|
|
@@ -564,25 +494,45 @@ class ViewModel {
|
|
|
564
494
|
this._sourceTracking = parentSourceTracking;
|
|
565
495
|
throw e;
|
|
566
496
|
}
|
|
567
|
-
stateDeps = this._stateTracking;
|
|
568
|
-
const capturedSourceDeps = this._sourceTracking;
|
|
569
497
|
this._stateTracking = parentStateTracking;
|
|
570
498
|
this._sourceTracking = parentSourceTracking;
|
|
571
499
|
if (parentStateTracking) {
|
|
572
|
-
for (const d of
|
|
500
|
+
for (const d of trackingSet) parentStateTracking.add(d);
|
|
573
501
|
}
|
|
574
502
|
if (parentSourceTracking) {
|
|
575
|
-
for (const [k, v] of
|
|
503
|
+
for (const [k, v] of trackingMap) {
|
|
576
504
|
parentSourceTracking.set(k, v);
|
|
577
505
|
}
|
|
578
506
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
507
|
+
const depCount = trackingSet.size;
|
|
508
|
+
if (!stateDepKeys || stateDepKeys.length !== depCount) {
|
|
509
|
+
stateDepKeys = new Array(depCount);
|
|
510
|
+
stateDepValues = new Array(depCount);
|
|
582
511
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
512
|
+
{
|
|
513
|
+
let i = 0;
|
|
514
|
+
const state = this._state;
|
|
515
|
+
for (const d of trackingSet) {
|
|
516
|
+
stateDepKeys[i] = d;
|
|
517
|
+
stateDepValues[i] = state[d];
|
|
518
|
+
i++;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
const sourceCount = trackingMap.size;
|
|
522
|
+
if (sourceCount > 0) {
|
|
523
|
+
if (!sourceDepKeys || sourceDepKeys.length !== sourceCount) {
|
|
524
|
+
sourceDepKeys = new Array(sourceCount);
|
|
525
|
+
sourceDepRevisions = new Array(sourceCount);
|
|
526
|
+
}
|
|
527
|
+
let i = 0;
|
|
528
|
+
for (const [memberKey, tracked] of trackingMap) {
|
|
529
|
+
sourceDepKeys[i] = memberKey;
|
|
530
|
+
sourceDepRevisions[i] = tracked.revision;
|
|
531
|
+
i++;
|
|
532
|
+
}
|
|
533
|
+
} else {
|
|
534
|
+
sourceDepKeys = void 0;
|
|
535
|
+
sourceDepRevisions = void 0;
|
|
586
536
|
}
|
|
587
537
|
validatedAtRevision = this._revision;
|
|
588
538
|
return cached;
|