mvc-kit 2.7.0 → 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.
Files changed (62) hide show
  1. package/README.md +18 -1
  2. package/agent-config/claude-code/skills/guide/SKILL.md +1 -0
  3. package/agent-config/claude-code/skills/guide/api-reference.md +8 -1
  4. package/agent-config/claude-code/skills/scaffold/templates/model.md +38 -1
  5. package/agent-config/copilot/copilot-instructions.md +2 -1
  6. package/agent-config/cursor/cursorrules +2 -1
  7. package/dist/Collection.cjs +31 -17
  8. package/dist/Collection.cjs.map +1 -1
  9. package/dist/Collection.d.ts.map +1 -1
  10. package/dist/Collection.js +31 -17
  11. package/dist/Collection.js.map +1 -1
  12. package/dist/Model.cjs +22 -4
  13. package/dist/Model.cjs.map +1 -1
  14. package/dist/Model.d.ts +2 -0
  15. package/dist/Model.d.ts.map +1 -1
  16. package/dist/Model.js +22 -4
  17. package/dist/Model.js.map +1 -1
  18. package/dist/PersistentCollection.cjs +8 -10
  19. package/dist/PersistentCollection.cjs.map +1 -1
  20. package/dist/PersistentCollection.d.ts +1 -0
  21. package/dist/PersistentCollection.d.ts.map +1 -1
  22. package/dist/PersistentCollection.js +8 -10
  23. package/dist/PersistentCollection.js.map +1 -1
  24. package/dist/Resource.cjs +21 -157
  25. package/dist/Resource.cjs.map +1 -1
  26. package/dist/Resource.d.ts +1 -3
  27. package/dist/Resource.d.ts.map +1 -1
  28. package/dist/Resource.js +21 -157
  29. package/dist/Resource.js.map +1 -1
  30. package/dist/ViewModel.cjs +178 -228
  31. package/dist/ViewModel.cjs.map +1 -1
  32. package/dist/ViewModel.d.ts +10 -13
  33. package/dist/ViewModel.d.ts.map +1 -1
  34. package/dist/ViewModel.js +178 -228
  35. package/dist/ViewModel.js.map +1 -1
  36. package/dist/react/index.d.ts +1 -1
  37. package/dist/react/index.d.ts.map +1 -1
  38. package/dist/react/use-instance.cjs +31 -21
  39. package/dist/react/use-instance.cjs.map +1 -1
  40. package/dist/react/use-instance.d.ts +1 -1
  41. package/dist/react/use-instance.d.ts.map +1 -1
  42. package/dist/react/use-instance.js +32 -22
  43. package/dist/react/use-instance.js.map +1 -1
  44. package/dist/react/use-model.cjs +29 -2
  45. package/dist/react/use-model.cjs.map +1 -1
  46. package/dist/react/use-model.d.ts +9 -0
  47. package/dist/react/use-model.d.ts.map +1 -1
  48. package/dist/react/use-model.js +30 -3
  49. package/dist/react/use-model.js.map +1 -1
  50. package/dist/react.cjs +1 -0
  51. package/dist/react.cjs.map +1 -1
  52. package/dist/react.js +2 -1
  53. package/dist/walkPrototypeChain.cjs.map +1 -1
  54. package/dist/walkPrototypeChain.d.ts +2 -2
  55. package/dist/walkPrototypeChain.js.map +1 -1
  56. package/dist/wrapAsyncMethods.cjs +179 -0
  57. package/dist/wrapAsyncMethods.cjs.map +1 -0
  58. package/dist/wrapAsyncMethods.d.ts +35 -0
  59. package/dist/wrapAsyncMethods.d.ts.map +1 -0
  60. package/dist/wrapAsyncMethods.js +179 -0
  61. package/dist/wrapAsyncMethods.js.map +1 -0
  62. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"walkPrototypeChain.cjs","sources":["../src/walkPrototypeChain.ts"],"sourcesContent":["/**\n * Walk the prototype chain from `instance`'s class up to (but not including)\n * `stopAt`. Calls `visitor` for each own property descriptor found.\n *\n * Shared utility — used by ViewModel's _memoizeGetters() and _wrapMethods(),\n * and by Resource's _wrapMethods().\n */\nexport function walkPrototypeChain(\n instance: object,\n stopAt: object,\n visitor: (key: string, desc: PropertyDescriptor, proto: object) => void,\n): void {\n let proto = Object.getPrototypeOf(instance);\n while (proto && proto !== stopAt) {\n const descriptors = Object.getOwnPropertyDescriptors(proto);\n for (const [key, desc] of Object.entries(descriptors)) {\n if (key === 'constructor') continue;\n visitor(key, desc, proto);\n }\n proto = Object.getPrototypeOf(proto);\n }\n}\n"],"names":[],"mappings":";;AAOO,SAAS,mBACd,UACA,QACA,SACM;AACN,MAAI,QAAQ,OAAO,eAAe,QAAQ;AAC1C,SAAO,SAAS,UAAU,QAAQ;AAChC,UAAM,cAAc,OAAO,0BAA0B,KAAK;AAC1D,eAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,WAAW,GAAG;AACrD,UAAI,QAAQ,cAAe;AAC3B,cAAQ,KAAK,MAAM,KAAK;AAAA,IAC1B;AACA,YAAQ,OAAO,eAAe,KAAK;AAAA,EACrC;AACF;;"}
1
+ {"version":3,"file":"walkPrototypeChain.cjs","sources":["../src/walkPrototypeChain.ts"],"sourcesContent":["/**\n * Walk the prototype chain from `instance`'s class up to (but not including)\n * `stopAt`. Calls `visitor` for each own property descriptor found.\n *\n * Shared utility — used by ViewModel's _processMembers(), wrapAsyncMethods(),\n * and ViewModel/Resource's _guardReservedKeys().\n */\nexport function walkPrototypeChain(\n instance: object,\n stopAt: object,\n visitor: (key: string, desc: PropertyDescriptor, proto: object) => void,\n): void {\n let proto = Object.getPrototypeOf(instance);\n while (proto && proto !== stopAt) {\n const descriptors = Object.getOwnPropertyDescriptors(proto);\n for (const [key, desc] of Object.entries(descriptors)) {\n if (key === 'constructor') continue;\n visitor(key, desc, proto);\n }\n proto = Object.getPrototypeOf(proto);\n }\n}\n"],"names":[],"mappings":";;AAOO,SAAS,mBACd,UACA,QACA,SACM;AACN,MAAI,QAAQ,OAAO,eAAe,QAAQ;AAC1C,SAAO,SAAS,UAAU,QAAQ;AAChC,UAAM,cAAc,OAAO,0BAA0B,KAAK;AAC1D,eAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,WAAW,GAAG;AACrD,UAAI,QAAQ,cAAe;AAC3B,cAAQ,KAAK,MAAM,KAAK;AAAA,IAC1B;AACA,YAAQ,OAAO,eAAe,KAAK;AAAA,EACrC;AACF;;"}
@@ -2,8 +2,8 @@
2
2
  * Walk the prototype chain from `instance`'s class up to (but not including)
3
3
  * `stopAt`. Calls `visitor` for each own property descriptor found.
4
4
  *
5
- * Shared utility — used by ViewModel's _memoizeGetters() and _wrapMethods(),
6
- * and by Resource's _wrapMethods().
5
+ * Shared utility — used by ViewModel's _processMembers(), wrapAsyncMethods(),
6
+ * and ViewModel/Resource's _guardReservedKeys().
7
7
  */
8
8
  export declare function walkPrototypeChain(instance: object, stopAt: object, visitor: (key: string, desc: PropertyDescriptor, proto: object) => void): void;
9
9
  //# sourceMappingURL=walkPrototypeChain.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"walkPrototypeChain.js","sources":["../src/walkPrototypeChain.ts"],"sourcesContent":["/**\n * Walk the prototype chain from `instance`'s class up to (but not including)\n * `stopAt`. Calls `visitor` for each own property descriptor found.\n *\n * Shared utility — used by ViewModel's _memoizeGetters() and _wrapMethods(),\n * and by Resource's _wrapMethods().\n */\nexport function walkPrototypeChain(\n instance: object,\n stopAt: object,\n visitor: (key: string, desc: PropertyDescriptor, proto: object) => void,\n): void {\n let proto = Object.getPrototypeOf(instance);\n while (proto && proto !== stopAt) {\n const descriptors = Object.getOwnPropertyDescriptors(proto);\n for (const [key, desc] of Object.entries(descriptors)) {\n if (key === 'constructor') continue;\n visitor(key, desc, proto);\n }\n proto = Object.getPrototypeOf(proto);\n }\n}\n"],"names":[],"mappings":"AAOO,SAAS,mBACd,UACA,QACA,SACM;AACN,MAAI,QAAQ,OAAO,eAAe,QAAQ;AAC1C,SAAO,SAAS,UAAU,QAAQ;AAChC,UAAM,cAAc,OAAO,0BAA0B,KAAK;AAC1D,eAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,WAAW,GAAG;AACrD,UAAI,QAAQ,cAAe;AAC3B,cAAQ,KAAK,MAAM,KAAK;AAAA,IAC1B;AACA,YAAQ,OAAO,eAAe,KAAK;AAAA,EACrC;AACF;"}
1
+ {"version":3,"file":"walkPrototypeChain.js","sources":["../src/walkPrototypeChain.ts"],"sourcesContent":["/**\n * Walk the prototype chain from `instance`'s class up to (but not including)\n * `stopAt`. Calls `visitor` for each own property descriptor found.\n *\n * Shared utility — used by ViewModel's _processMembers(), wrapAsyncMethods(),\n * and ViewModel/Resource's _guardReservedKeys().\n */\nexport function walkPrototypeChain(\n instance: object,\n stopAt: object,\n visitor: (key: string, desc: PropertyDescriptor, proto: object) => void,\n): void {\n let proto = Object.getPrototypeOf(instance);\n while (proto && proto !== stopAt) {\n const descriptors = Object.getOwnPropertyDescriptors(proto);\n for (const [key, desc] of Object.entries(descriptors)) {\n if (key === 'constructor') continue;\n visitor(key, desc, proto);\n }\n proto = Object.getPrototypeOf(proto);\n }\n}\n"],"names":[],"mappings":"AAOO,SAAS,mBACd,UACA,QACA,SACM;AACN,MAAI,QAAQ,OAAO,eAAe,QAAQ;AAC1C,SAAO,SAAS,UAAU,QAAQ;AAChC,UAAM,cAAc,OAAO,0BAA0B,KAAK;AAC1D,eAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,WAAW,GAAG;AACrD,UAAI,QAAQ,cAAe;AAC3B,cAAQ,KAAK,MAAM,KAAK;AAAA,IAC1B;AACA,YAAQ,OAAO,eAAe,KAAK;AAAA,EACrC;AACF;"}
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const errors = require("./errors.cjs");
4
+ const walkPrototypeChain = require("./walkPrototypeChain.cjs");
5
+ const __DEV__ = typeof __MVC_KIT_DEV__ !== "undefined" && __MVC_KIT_DEV__;
6
+ const LOADING_TASK_STATE = Object.freeze({ loading: true, error: null, errorCode: null });
7
+ function wrapAsyncMethods(ctx) {
8
+ const {
9
+ instance,
10
+ stopPrototype,
11
+ reservedKeys,
12
+ lifecycleHooks,
13
+ isDisposed,
14
+ isInitialized,
15
+ asyncStates,
16
+ asyncSnapshots,
17
+ asyncListeners,
18
+ notifyAsync,
19
+ addCleanup,
20
+ ghostTimeout,
21
+ className,
22
+ activeOps
23
+ } = ctx;
24
+ if (__DEV__) {
25
+ for (const key of reservedKeys) {
26
+ if (Object.getOwnPropertyDescriptor(instance, key)?.value !== void 0) {
27
+ throw new Error(
28
+ `[mvc-kit] "${key}" is a reserved property on ${className} and cannot be overridden.`
29
+ );
30
+ }
31
+ }
32
+ }
33
+ const methodEntries = ctx.methods ?? (() => {
34
+ const result = [];
35
+ const processed = /* @__PURE__ */ new Set();
36
+ walkPrototypeChain.walkPrototypeChain(instance, stopPrototype, (key, desc) => {
37
+ if (desc.get || desc.set) return;
38
+ if (typeof desc.value !== "function") return;
39
+ if (key.startsWith("_")) return;
40
+ if (lifecycleHooks.has(key)) return;
41
+ if (processed.has(key)) return;
42
+ processed.add(key);
43
+ result.push({ key, fn: desc.value });
44
+ });
45
+ return result;
46
+ })();
47
+ const wrappedKeys = [];
48
+ for (const { key, fn: original } of methodEntries) {
49
+ let pruned = false;
50
+ const wrapper = function(...args) {
51
+ if (isDisposed()) {
52
+ if (__DEV__) {
53
+ console.warn(`[mvc-kit] "${key}" called after dispose — ignored.`);
54
+ }
55
+ return void 0;
56
+ }
57
+ if (__DEV__ && !isInitialized()) {
58
+ console.warn(
59
+ `[mvc-kit] "${key}" called before init(). Async tracking is active only after init().`
60
+ );
61
+ }
62
+ let result;
63
+ try {
64
+ result = original.apply(instance, args);
65
+ } catch (e) {
66
+ throw e;
67
+ }
68
+ if (!result || typeof result.then !== "function") {
69
+ if (!pruned) {
70
+ pruned = true;
71
+ asyncStates.delete(key);
72
+ asyncSnapshots.delete(key);
73
+ instance[key] = original.bind(instance);
74
+ }
75
+ return result;
76
+ }
77
+ let internal = asyncStates.get(key);
78
+ if (!internal) {
79
+ internal = { loading: false, error: null, errorCode: null, count: 0 };
80
+ asyncStates.set(key, internal);
81
+ }
82
+ internal.count++;
83
+ internal.loading = true;
84
+ internal.error = null;
85
+ internal.errorCode = null;
86
+ asyncSnapshots.set(key, LOADING_TASK_STATE);
87
+ notifyAsync();
88
+ if (__DEV__ && activeOps) {
89
+ activeOps.set(key, (activeOps.get(key) ?? 0) + 1);
90
+ }
91
+ return result.then(
92
+ (value) => {
93
+ if (isDisposed()) return value;
94
+ internal.count--;
95
+ internal.loading = internal.count > 0;
96
+ asyncSnapshots.set(
97
+ key,
98
+ Object.freeze({ loading: internal.loading, error: internal.error, errorCode: internal.errorCode })
99
+ );
100
+ notifyAsync();
101
+ if (__DEV__ && activeOps) {
102
+ const c = (activeOps.get(key) ?? 1) - 1;
103
+ if (c <= 0) activeOps.delete(key);
104
+ else activeOps.set(key, c);
105
+ }
106
+ return value;
107
+ },
108
+ (error) => {
109
+ if (errors.isAbortError(error)) {
110
+ if (!isDisposed()) {
111
+ internal.count--;
112
+ internal.loading = internal.count > 0;
113
+ asyncSnapshots.set(
114
+ key,
115
+ Object.freeze({ loading: internal.loading, error: internal.error, errorCode: internal.errorCode })
116
+ );
117
+ notifyAsync();
118
+ }
119
+ if (__DEV__ && activeOps) {
120
+ const c = (activeOps.get(key) ?? 1) - 1;
121
+ if (c <= 0) activeOps.delete(key);
122
+ else activeOps.set(key, c);
123
+ }
124
+ return void 0;
125
+ }
126
+ if (isDisposed()) return void 0;
127
+ internal.count--;
128
+ internal.loading = internal.count > 0;
129
+ const classified = errors.classifyError(error);
130
+ internal.error = classified.message;
131
+ internal.errorCode = classified.code;
132
+ asyncSnapshots.set(
133
+ key,
134
+ Object.freeze({ loading: internal.loading, error: classified.message, errorCode: classified.code })
135
+ );
136
+ notifyAsync();
137
+ if (__DEV__ && activeOps) {
138
+ const c = (activeOps.get(key) ?? 1) - 1;
139
+ if (c <= 0) activeOps.delete(key);
140
+ else activeOps.set(key, c);
141
+ }
142
+ throw error;
143
+ }
144
+ );
145
+ };
146
+ wrappedKeys.push(key);
147
+ instance[key] = wrapper;
148
+ }
149
+ if (wrappedKeys.length > 0) {
150
+ addCleanup(() => {
151
+ const opsSnapshot = __DEV__ && activeOps ? new Map(activeOps) : null;
152
+ for (const k of wrappedKeys) {
153
+ if (__DEV__) {
154
+ instance[k] = () => {
155
+ console.warn(`[mvc-kit] "${k}" called after dispose — ignored.`);
156
+ return void 0;
157
+ };
158
+ } else {
159
+ instance[k] = () => void 0;
160
+ }
161
+ }
162
+ asyncListeners.clear();
163
+ asyncStates.clear();
164
+ asyncSnapshots.clear();
165
+ if (__DEV__ && opsSnapshot && opsSnapshot.size > 0) {
166
+ setTimeout(() => {
167
+ for (const [key, count] of opsSnapshot) {
168
+ console.warn(
169
+ `[mvc-kit] Ghost async operation detected: "${key}" had ${count} pending call(s) when the ${className} was disposed. Consider using disposeSignal to cancel in-flight work.`
170
+ );
171
+ }
172
+ }, ghostTimeout);
173
+ }
174
+ });
175
+ }
176
+ return wrappedKeys;
177
+ }
178
+ exports.wrapAsyncMethods = wrapAsyncMethods;
179
+ //# sourceMappingURL=wrapAsyncMethods.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapAsyncMethods.cjs","sources":["../src/wrapAsyncMethods.ts"],"sourcesContent":["import { isAbortError, classifyError } from './errors';\nimport { walkPrototypeChain } from './walkPrototypeChain';\nimport type { TaskState } from './types';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\nconst LOADING_TASK_STATE: TaskState = Object.freeze({ loading: true, error: null, errorCode: null });\n\nexport interface InternalTaskState {\n loading: boolean;\n error: string | null;\n errorCode: TaskState['errorCode'];\n count: number;\n}\n\nexport interface AsyncTrackingContext {\n instance: object;\n stopPrototype: object;\n reservedKeys: readonly string[];\n lifecycleHooks: Set<string>;\n isDisposed: () => boolean;\n isInitialized: () => boolean;\n asyncStates: Map<string, InternalTaskState>;\n asyncSnapshots: Map<string, TaskState>;\n asyncListeners: Set<() => void>;\n notifyAsync: () => void;\n addCleanup: (fn: () => void) => void;\n ghostTimeout: number;\n className: string;\n activeOps: Map<string, number> | null;\n /** Pre-scanned methods from class metadata cache. When provided, skips prototype walk. */\n methods?: Array<{ key: string; fn: Function }>;\n}\n\n/**\n * Shared async method wrapping logic used by both ViewModel and Resource.\n * Walks the prototype chain, wraps methods with async tracking, and registers cleanup.\n * Returns the list of wrapped method keys.\n */\nexport function wrapAsyncMethods(ctx: AsyncTrackingContext): string[] {\n const {\n instance,\n stopPrototype,\n reservedKeys,\n lifecycleHooks,\n isDisposed,\n isInitialized,\n asyncStates,\n asyncSnapshots,\n asyncListeners,\n notifyAsync,\n addCleanup,\n ghostTimeout,\n className,\n activeOps,\n } = ctx;\n\n // Instance property reserved key check (DEV-only — prototype check in constructor catches most cases)\n if (__DEV__) {\n for (const key of reservedKeys) {\n if (Object.getOwnPropertyDescriptor(instance, key)?.value !== undefined) {\n throw new Error(\n `[mvc-kit] \"${key}\" is a reserved property on ${className} and cannot be overridden.`\n );\n }\n }\n }\n\n // Use pre-scanned methods from class metadata cache, or walk prototype chain\n const methodEntries: Array<{ key: string; fn: Function }> = ctx.methods ?? (() => {\n const result: Array<{ key: string; fn: Function }> = [];\n const processed = new Set<string>();\n walkPrototypeChain(instance, stopPrototype, (key, desc) => {\n if (desc.get || desc.set) return;\n if (typeof desc.value !== 'function') return;\n if (key.startsWith('_')) return;\n if (lifecycleHooks.has(key)) return;\n if (processed.has(key)) return;\n processed.add(key);\n result.push({ key, fn: desc.value });\n });\n return result;\n })();\n\n const wrappedKeys: string[] = [];\n\n for (const { key, fn: original } of methodEntries) {\n let pruned = false;\n\n const wrapper = function (this: any, ...args: unknown[]) {\n // Disposed guard\n if (isDisposed()) {\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__ && !isInitialized()) {\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(instance, 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 asyncStates.delete(key);\n asyncSnapshots.delete(key);\n // Replace wrapper with bound original for zero overhead\n (instance as any)[key] = original.bind(instance);\n }\n return result;\n }\n\n // ── Async tracking ──────────────────────────────────────\n let internal = asyncStates.get(key);\n if (!internal) {\n internal = { loading: false, error: null, errorCode: null, count: 0 };\n asyncStates.set(key, internal);\n }\n\n internal.count++;\n internal.loading = true;\n internal.error = null;\n internal.errorCode = null;\n asyncSnapshots.set(key, LOADING_TASK_STATE);\n notifyAsync();\n\n if (__DEV__ && activeOps) {\n activeOps.set(key, (activeOps.get(key) ?? 0) + 1);\n }\n\n return (result as Promise<unknown>).then(\n (value) => {\n if (isDisposed()) return value;\n\n internal!.count--;\n internal!.loading = internal!.count > 0;\n asyncSnapshots.set(\n key,\n Object.freeze({ loading: internal!.loading, error: internal!.error, errorCode: internal!.errorCode }),\n );\n notifyAsync();\n\n if (__DEV__ && activeOps) {\n const c = (activeOps.get(key) ?? 1) - 1;\n if (c <= 0) activeOps.delete(key);\n else activeOps.set(key, c);\n }\n\n return value;\n },\n (error) => {\n // AbortError — silently swallow\n if (isAbortError(error)) {\n if (!isDisposed()) {\n internal!.count--;\n internal!.loading = internal!.count > 0;\n asyncSnapshots.set(\n key,\n Object.freeze({ loading: internal!.loading, error: internal!.error, errorCode: internal!.errorCode }),\n );\n notifyAsync();\n }\n\n if (__DEV__ && activeOps) {\n const c = (activeOps.get(key) ?? 1) - 1;\n if (c <= 0) activeOps.delete(key);\n else activeOps.set(key, c);\n }\n\n return undefined;\n }\n\n // Disposed — fizzle silently\n if (isDisposed()) 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 asyncSnapshots.set(\n key,\n Object.freeze({ loading: internal!.loading, error: classified.message, errorCode: classified.code }),\n );\n notifyAsync();\n\n if (__DEV__ && activeOps) {\n const c = (activeOps.get(key) ?? 1) - 1;\n if (c <= 0) activeOps.delete(key);\n else 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 (instance as any)[key] = wrapper;\n }\n\n // Register cleanup for disposal\n if (wrappedKeys.length > 0) {\n addCleanup(() => {\n // Snapshot active ops for ghost check before clearing\n const opsSnapshot = __DEV__ && activeOps ? new Map(activeOps) : null;\n\n // Swap all wrapped methods to no-ops (with DEV warning)\n for (const k of wrappedKeys) {\n if (__DEV__) {\n (instance as any)[k] = () => {\n console.warn(`[mvc-kit] \"${k}\" called after dispose — ignored.`);\n return undefined;\n };\n } else {\n (instance as any)[k] = () => undefined;\n }\n }\n\n // Clear async state\n asyncListeners.clear();\n asyncStates.clear();\n asyncSnapshots.clear();\n\n // DEV: schedule ghost check\n if (__DEV__ && opsSnapshot && opsSnapshot.size > 0) {\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 ${className} was disposed. ` +\n `Consider using disposeSignal to cancel in-flight work.`\n );\n }\n }, ghostTimeout);\n }\n });\n }\n\n return wrappedKeys;\n}\n"],"names":["walkPrototypeChain","isAbortError","classifyError"],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { TaskState } from './types';
2
+ export interface InternalTaskState {
3
+ loading: boolean;
4
+ error: string | null;
5
+ errorCode: TaskState['errorCode'];
6
+ count: number;
7
+ }
8
+ export interface AsyncTrackingContext {
9
+ instance: object;
10
+ stopPrototype: object;
11
+ reservedKeys: readonly string[];
12
+ lifecycleHooks: Set<string>;
13
+ isDisposed: () => boolean;
14
+ isInitialized: () => boolean;
15
+ asyncStates: Map<string, InternalTaskState>;
16
+ asyncSnapshots: Map<string, TaskState>;
17
+ asyncListeners: Set<() => void>;
18
+ notifyAsync: () => void;
19
+ addCleanup: (fn: () => void) => void;
20
+ ghostTimeout: number;
21
+ className: string;
22
+ activeOps: Map<string, number> | null;
23
+ /** Pre-scanned methods from class metadata cache. When provided, skips prototype walk. */
24
+ methods?: Array<{
25
+ key: string;
26
+ fn: Function;
27
+ }>;
28
+ }
29
+ /**
30
+ * Shared async method wrapping logic used by both ViewModel and Resource.
31
+ * Walks the prototype chain, wraps methods with async tracking, and registers cleanup.
32
+ * Returns the list of wrapped method keys.
33
+ */
34
+ export declare function wrapAsyncMethods(ctx: AsyncTrackingContext): string[];
35
+ //# sourceMappingURL=wrapAsyncMethods.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapAsyncMethods.d.ts","sourceRoot":"","sources":["../src/wrapAsyncMethods.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAMzC,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B,aAAa,EAAE,MAAM,OAAO,CAAC;IAC7B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC5C,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACvC,cAAc,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;IAChC,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACtC,0FAA0F;IAC1F,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;CAChD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,oBAAoB,GAAG,MAAM,EAAE,CAyNpE"}
@@ -0,0 +1,179 @@
1
+ import { isAbortError, classifyError } from "./errors.js";
2
+ import { walkPrototypeChain } from "./walkPrototypeChain.js";
3
+ const __DEV__ = typeof __MVC_KIT_DEV__ !== "undefined" && __MVC_KIT_DEV__;
4
+ const LOADING_TASK_STATE = Object.freeze({ loading: true, error: null, errorCode: null });
5
+ function wrapAsyncMethods(ctx) {
6
+ const {
7
+ instance,
8
+ stopPrototype,
9
+ reservedKeys,
10
+ lifecycleHooks,
11
+ isDisposed,
12
+ isInitialized,
13
+ asyncStates,
14
+ asyncSnapshots,
15
+ asyncListeners,
16
+ notifyAsync,
17
+ addCleanup,
18
+ ghostTimeout,
19
+ className,
20
+ activeOps
21
+ } = ctx;
22
+ if (__DEV__) {
23
+ for (const key of reservedKeys) {
24
+ if (Object.getOwnPropertyDescriptor(instance, key)?.value !== void 0) {
25
+ throw new Error(
26
+ `[mvc-kit] "${key}" is a reserved property on ${className} and cannot be overridden.`
27
+ );
28
+ }
29
+ }
30
+ }
31
+ const methodEntries = ctx.methods ?? (() => {
32
+ const result = [];
33
+ const processed = /* @__PURE__ */ new Set();
34
+ walkPrototypeChain(instance, stopPrototype, (key, desc) => {
35
+ if (desc.get || desc.set) return;
36
+ if (typeof desc.value !== "function") return;
37
+ if (key.startsWith("_")) return;
38
+ if (lifecycleHooks.has(key)) return;
39
+ if (processed.has(key)) return;
40
+ processed.add(key);
41
+ result.push({ key, fn: desc.value });
42
+ });
43
+ return result;
44
+ })();
45
+ const wrappedKeys = [];
46
+ for (const { key, fn: original } of methodEntries) {
47
+ let pruned = false;
48
+ const wrapper = function(...args) {
49
+ if (isDisposed()) {
50
+ if (__DEV__) {
51
+ console.warn(`[mvc-kit] "${key}" called after dispose — ignored.`);
52
+ }
53
+ return void 0;
54
+ }
55
+ if (__DEV__ && !isInitialized()) {
56
+ console.warn(
57
+ `[mvc-kit] "${key}" called before init(). Async tracking is active only after init().`
58
+ );
59
+ }
60
+ let result;
61
+ try {
62
+ result = original.apply(instance, args);
63
+ } catch (e) {
64
+ throw e;
65
+ }
66
+ if (!result || typeof result.then !== "function") {
67
+ if (!pruned) {
68
+ pruned = true;
69
+ asyncStates.delete(key);
70
+ asyncSnapshots.delete(key);
71
+ instance[key] = original.bind(instance);
72
+ }
73
+ return result;
74
+ }
75
+ let internal = asyncStates.get(key);
76
+ if (!internal) {
77
+ internal = { loading: false, error: null, errorCode: null, count: 0 };
78
+ asyncStates.set(key, internal);
79
+ }
80
+ internal.count++;
81
+ internal.loading = true;
82
+ internal.error = null;
83
+ internal.errorCode = null;
84
+ asyncSnapshots.set(key, LOADING_TASK_STATE);
85
+ notifyAsync();
86
+ if (__DEV__ && activeOps) {
87
+ activeOps.set(key, (activeOps.get(key) ?? 0) + 1);
88
+ }
89
+ return result.then(
90
+ (value) => {
91
+ if (isDisposed()) return value;
92
+ internal.count--;
93
+ internal.loading = internal.count > 0;
94
+ asyncSnapshots.set(
95
+ key,
96
+ Object.freeze({ loading: internal.loading, error: internal.error, errorCode: internal.errorCode })
97
+ );
98
+ notifyAsync();
99
+ if (__DEV__ && activeOps) {
100
+ const c = (activeOps.get(key) ?? 1) - 1;
101
+ if (c <= 0) activeOps.delete(key);
102
+ else activeOps.set(key, c);
103
+ }
104
+ return value;
105
+ },
106
+ (error) => {
107
+ if (isAbortError(error)) {
108
+ if (!isDisposed()) {
109
+ internal.count--;
110
+ internal.loading = internal.count > 0;
111
+ asyncSnapshots.set(
112
+ key,
113
+ Object.freeze({ loading: internal.loading, error: internal.error, errorCode: internal.errorCode })
114
+ );
115
+ notifyAsync();
116
+ }
117
+ if (__DEV__ && activeOps) {
118
+ const c = (activeOps.get(key) ?? 1) - 1;
119
+ if (c <= 0) activeOps.delete(key);
120
+ else activeOps.set(key, c);
121
+ }
122
+ return void 0;
123
+ }
124
+ if (isDisposed()) return void 0;
125
+ internal.count--;
126
+ internal.loading = internal.count > 0;
127
+ const classified = classifyError(error);
128
+ internal.error = classified.message;
129
+ internal.errorCode = classified.code;
130
+ asyncSnapshots.set(
131
+ key,
132
+ Object.freeze({ loading: internal.loading, error: classified.message, errorCode: classified.code })
133
+ );
134
+ notifyAsync();
135
+ if (__DEV__ && activeOps) {
136
+ const c = (activeOps.get(key) ?? 1) - 1;
137
+ if (c <= 0) activeOps.delete(key);
138
+ else activeOps.set(key, c);
139
+ }
140
+ throw error;
141
+ }
142
+ );
143
+ };
144
+ wrappedKeys.push(key);
145
+ instance[key] = wrapper;
146
+ }
147
+ if (wrappedKeys.length > 0) {
148
+ addCleanup(() => {
149
+ const opsSnapshot = __DEV__ && activeOps ? new Map(activeOps) : null;
150
+ for (const k of wrappedKeys) {
151
+ if (__DEV__) {
152
+ instance[k] = () => {
153
+ console.warn(`[mvc-kit] "${k}" called after dispose — ignored.`);
154
+ return void 0;
155
+ };
156
+ } else {
157
+ instance[k] = () => void 0;
158
+ }
159
+ }
160
+ asyncListeners.clear();
161
+ asyncStates.clear();
162
+ asyncSnapshots.clear();
163
+ if (__DEV__ && opsSnapshot && opsSnapshot.size > 0) {
164
+ setTimeout(() => {
165
+ for (const [key, count] of opsSnapshot) {
166
+ console.warn(
167
+ `[mvc-kit] Ghost async operation detected: "${key}" had ${count} pending call(s) when the ${className} was disposed. Consider using disposeSignal to cancel in-flight work.`
168
+ );
169
+ }
170
+ }, ghostTimeout);
171
+ }
172
+ });
173
+ }
174
+ return wrappedKeys;
175
+ }
176
+ export {
177
+ wrapAsyncMethods
178
+ };
179
+ //# sourceMappingURL=wrapAsyncMethods.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapAsyncMethods.js","sources":["../src/wrapAsyncMethods.ts"],"sourcesContent":["import { isAbortError, classifyError } from './errors';\nimport { walkPrototypeChain } from './walkPrototypeChain';\nimport type { TaskState } from './types';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\nconst LOADING_TASK_STATE: TaskState = Object.freeze({ loading: true, error: null, errorCode: null });\n\nexport interface InternalTaskState {\n loading: boolean;\n error: string | null;\n errorCode: TaskState['errorCode'];\n count: number;\n}\n\nexport interface AsyncTrackingContext {\n instance: object;\n stopPrototype: object;\n reservedKeys: readonly string[];\n lifecycleHooks: Set<string>;\n isDisposed: () => boolean;\n isInitialized: () => boolean;\n asyncStates: Map<string, InternalTaskState>;\n asyncSnapshots: Map<string, TaskState>;\n asyncListeners: Set<() => void>;\n notifyAsync: () => void;\n addCleanup: (fn: () => void) => void;\n ghostTimeout: number;\n className: string;\n activeOps: Map<string, number> | null;\n /** Pre-scanned methods from class metadata cache. When provided, skips prototype walk. */\n methods?: Array<{ key: string; fn: Function }>;\n}\n\n/**\n * Shared async method wrapping logic used by both ViewModel and Resource.\n * Walks the prototype chain, wraps methods with async tracking, and registers cleanup.\n * Returns the list of wrapped method keys.\n */\nexport function wrapAsyncMethods(ctx: AsyncTrackingContext): string[] {\n const {\n instance,\n stopPrototype,\n reservedKeys,\n lifecycleHooks,\n isDisposed,\n isInitialized,\n asyncStates,\n asyncSnapshots,\n asyncListeners,\n notifyAsync,\n addCleanup,\n ghostTimeout,\n className,\n activeOps,\n } = ctx;\n\n // Instance property reserved key check (DEV-only — prototype check in constructor catches most cases)\n if (__DEV__) {\n for (const key of reservedKeys) {\n if (Object.getOwnPropertyDescriptor(instance, key)?.value !== undefined) {\n throw new Error(\n `[mvc-kit] \"${key}\" is a reserved property on ${className} and cannot be overridden.`\n );\n }\n }\n }\n\n // Use pre-scanned methods from class metadata cache, or walk prototype chain\n const methodEntries: Array<{ key: string; fn: Function }> = ctx.methods ?? (() => {\n const result: Array<{ key: string; fn: Function }> = [];\n const processed = new Set<string>();\n walkPrototypeChain(instance, stopPrototype, (key, desc) => {\n if (desc.get || desc.set) return;\n if (typeof desc.value !== 'function') return;\n if (key.startsWith('_')) return;\n if (lifecycleHooks.has(key)) return;\n if (processed.has(key)) return;\n processed.add(key);\n result.push({ key, fn: desc.value });\n });\n return result;\n })();\n\n const wrappedKeys: string[] = [];\n\n for (const { key, fn: original } of methodEntries) {\n let pruned = false;\n\n const wrapper = function (this: any, ...args: unknown[]) {\n // Disposed guard\n if (isDisposed()) {\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__ && !isInitialized()) {\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(instance, 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 asyncStates.delete(key);\n asyncSnapshots.delete(key);\n // Replace wrapper with bound original for zero overhead\n (instance as any)[key] = original.bind(instance);\n }\n return result;\n }\n\n // ── Async tracking ──────────────────────────────────────\n let internal = asyncStates.get(key);\n if (!internal) {\n internal = { loading: false, error: null, errorCode: null, count: 0 };\n asyncStates.set(key, internal);\n }\n\n internal.count++;\n internal.loading = true;\n internal.error = null;\n internal.errorCode = null;\n asyncSnapshots.set(key, LOADING_TASK_STATE);\n notifyAsync();\n\n if (__DEV__ && activeOps) {\n activeOps.set(key, (activeOps.get(key) ?? 0) + 1);\n }\n\n return (result as Promise<unknown>).then(\n (value) => {\n if (isDisposed()) return value;\n\n internal!.count--;\n internal!.loading = internal!.count > 0;\n asyncSnapshots.set(\n key,\n Object.freeze({ loading: internal!.loading, error: internal!.error, errorCode: internal!.errorCode }),\n );\n notifyAsync();\n\n if (__DEV__ && activeOps) {\n const c = (activeOps.get(key) ?? 1) - 1;\n if (c <= 0) activeOps.delete(key);\n else activeOps.set(key, c);\n }\n\n return value;\n },\n (error) => {\n // AbortError — silently swallow\n if (isAbortError(error)) {\n if (!isDisposed()) {\n internal!.count--;\n internal!.loading = internal!.count > 0;\n asyncSnapshots.set(\n key,\n Object.freeze({ loading: internal!.loading, error: internal!.error, errorCode: internal!.errorCode }),\n );\n notifyAsync();\n }\n\n if (__DEV__ && activeOps) {\n const c = (activeOps.get(key) ?? 1) - 1;\n if (c <= 0) activeOps.delete(key);\n else activeOps.set(key, c);\n }\n\n return undefined;\n }\n\n // Disposed — fizzle silently\n if (isDisposed()) 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 asyncSnapshots.set(\n key,\n Object.freeze({ loading: internal!.loading, error: classified.message, errorCode: classified.code }),\n );\n notifyAsync();\n\n if (__DEV__ && activeOps) {\n const c = (activeOps.get(key) ?? 1) - 1;\n if (c <= 0) activeOps.delete(key);\n else 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 (instance as any)[key] = wrapper;\n }\n\n // Register cleanup for disposal\n if (wrappedKeys.length > 0) {\n addCleanup(() => {\n // Snapshot active ops for ghost check before clearing\n const opsSnapshot = __DEV__ && activeOps ? new Map(activeOps) : null;\n\n // Swap all wrapped methods to no-ops (with DEV warning)\n for (const k of wrappedKeys) {\n if (__DEV__) {\n (instance as any)[k] = () => {\n console.warn(`[mvc-kit] \"${k}\" called after dispose — ignored.`);\n return undefined;\n };\n } else {\n (instance as any)[k] = () => undefined;\n }\n }\n\n // Clear async state\n asyncListeners.clear();\n asyncStates.clear();\n asyncSnapshots.clear();\n\n // DEV: schedule ghost check\n if (__DEV__ && opsSnapshot && opsSnapshot.size > 0) {\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 ${className} was disposed. ` +\n `Consider using disposeSignal to cancel in-flight work.`\n );\n }\n }, ghostTimeout);\n }\n });\n }\n\n return wrappedKeys;\n}\n"],"names":[],"mappings":";;AAIA,MAAM,UAAU,OAAO,oBAAoB,eAAe;AAE1D,MAAM,qBAAgC,OAAO,OAAO,EAAE,SAAS,MAAM,OAAO,MAAM,WAAW,MAAM;AAiC5F,SAAS,iBAAiB,KAAqC;AACpE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,IACE;AAGJ,MAAI,SAAS;AACX,eAAW,OAAO,cAAc;AAC9B,UAAI,OAAO,yBAAyB,UAAU,GAAG,GAAG,UAAU,QAAW;AACvE,cAAM,IAAI;AAAA,UACR,cAAc,GAAG,+BAA+B,SAAS;AAAA,QAAA;AAAA,MAE7D;AAAA,IACF;AAAA,EACF;AAGA,QAAM,gBAAsD,IAAI,YAAY,MAAM;AAChF,UAAM,SAA+C,CAAA;AACrD,UAAM,gCAAgB,IAAA;AACtB,uBAAmB,UAAU,eAAe,CAAC,KAAK,SAAS;AACzD,UAAI,KAAK,OAAO,KAAK,IAAK;AAC1B,UAAI,OAAO,KAAK,UAAU,WAAY;AACtC,UAAI,IAAI,WAAW,GAAG,EAAG;AACzB,UAAI,eAAe,IAAI,GAAG,EAAG;AAC7B,UAAI,UAAU,IAAI,GAAG,EAAG;AACxB,gBAAU,IAAI,GAAG;AACjB,aAAO,KAAK,EAAE,KAAK,IAAI,KAAK,OAAO;AAAA,IACrC,CAAC;AACD,WAAO;AAAA,EACT,GAAA;AAEA,QAAM,cAAwB,CAAA;AAE9B,aAAW,EAAE,KAAK,IAAI,SAAA,KAAc,eAAe;AACjD,QAAI,SAAS;AAEb,UAAM,UAAU,YAAwB,MAAiB;AAEvD,UAAI,cAAc;AAChB,YAAI,SAAS;AACX,kBAAQ,KAAK,cAAc,GAAG,mCAAmC;AAAA,QACnE;AACA,eAAO;AAAA,MACT;AAGA,UAAI,WAAW,CAAC,iBAAiB;AAC/B,gBAAQ;AAAA,UACN,cAAc,GAAG;AAAA,QAAA;AAAA,MAGrB;AAEA,UAAI;AACJ,UAAI;AACF,iBAAS,SAAS,MAAM,UAAU,IAAI;AAAA,MACxC,SAAS,GAAG;AAEV,cAAM;AAAA,MACR;AAGA,UAAI,CAAC,UAAU,OAAQ,OAAe,SAAS,YAAY;AACzD,YAAI,CAAC,QAAQ;AACX,mBAAS;AAET,sBAAY,OAAO,GAAG;AACtB,yBAAe,OAAO,GAAG;AAExB,mBAAiB,GAAG,IAAI,SAAS,KAAK,QAAQ;AAAA,QACjD;AACA,eAAO;AAAA,MACT;AAGA,UAAI,WAAW,YAAY,IAAI,GAAG;AAClC,UAAI,CAAC,UAAU;AACb,mBAAW,EAAE,SAAS,OAAO,OAAO,MAAM,WAAW,MAAM,OAAO,EAAA;AAClE,oBAAY,IAAI,KAAK,QAAQ;AAAA,MAC/B;AAEA,eAAS;AACT,eAAS,UAAU;AACnB,eAAS,QAAQ;AACjB,eAAS,YAAY;AACrB,qBAAe,IAAI,KAAK,kBAAkB;AAC1C,kBAAA;AAEA,UAAI,WAAW,WAAW;AACxB,kBAAU,IAAI,MAAM,UAAU,IAAI,GAAG,KAAK,KAAK,CAAC;AAAA,MAClD;AAEA,aAAQ,OAA4B;AAAA,QAClC,CAAC,UAAU;AACT,cAAI,WAAA,EAAc,QAAO;AAEzB,mBAAU;AACV,mBAAU,UAAU,SAAU,QAAQ;AACtC,yBAAe;AAAA,YACb;AAAA,YACA,OAAO,OAAO,EAAE,SAAS,SAAU,SAAS,OAAO,SAAU,OAAO,WAAW,SAAU,UAAA,CAAW;AAAA,UAAA;AAEtG,sBAAA;AAEA,cAAI,WAAW,WAAW;AACxB,kBAAM,KAAK,UAAU,IAAI,GAAG,KAAK,KAAK;AACtC,gBAAI,KAAK,EAAG,WAAU,OAAO,GAAG;AAAA,gBAC3B,WAAU,IAAI,KAAK,CAAC;AAAA,UAC3B;AAEA,iBAAO;AAAA,QACT;AAAA,QACA,CAAC,UAAU;AAET,cAAI,aAAa,KAAK,GAAG;AACvB,gBAAI,CAAC,cAAc;AACjB,uBAAU;AACV,uBAAU,UAAU,SAAU,QAAQ;AACtC,6BAAe;AAAA,gBACb;AAAA,gBACA,OAAO,OAAO,EAAE,SAAS,SAAU,SAAS,OAAO,SAAU,OAAO,WAAW,SAAU,UAAA,CAAW;AAAA,cAAA;AAEtG,0BAAA;AAAA,YACF;AAEA,gBAAI,WAAW,WAAW;AACxB,oBAAM,KAAK,UAAU,IAAI,GAAG,KAAK,KAAK;AACtC,kBAAI,KAAK,EAAG,WAAU,OAAO,GAAG;AAAA,kBAC3B,WAAU,IAAI,KAAK,CAAC;AAAA,YAC3B;AAEA,mBAAO;AAAA,UACT;AAGA,cAAI,WAAA,EAAc,QAAO;AAEzB,mBAAU;AACV,mBAAU,UAAU,SAAU,QAAQ;AACtC,gBAAM,aAAa,cAAc,KAAK;AACtC,mBAAU,QAAQ,WAAW;AAC7B,mBAAU,YAAY,WAAW;AACjC,yBAAe;AAAA,YACb;AAAA,YACA,OAAO,OAAO,EAAE,SAAS,SAAU,SAAS,OAAO,WAAW,SAAS,WAAW,WAAW,KAAA,CAAM;AAAA,UAAA;AAErG,sBAAA;AAEA,cAAI,WAAW,WAAW;AACxB,kBAAM,KAAK,UAAU,IAAI,GAAG,KAAK,KAAK;AACtC,gBAAI,KAAK,EAAG,WAAU,OAAO,GAAG;AAAA,gBAC3B,WAAU,IAAI,KAAK,CAAC;AAAA,UAC3B;AAGA,gBAAM;AAAA,QACR;AAAA,MAAA;AAAA,IAEJ;AAEA,gBAAY,KAAK,GAAG;AACnB,aAAiB,GAAG,IAAI;AAAA,EAC3B;AAGA,MAAI,YAAY,SAAS,GAAG;AAC1B,eAAW,MAAM;AAEf,YAAM,cAAc,WAAW,YAAY,IAAI,IAAI,SAAS,IAAI;AAGhE,iBAAW,KAAK,aAAa;AAC3B,YAAI,SAAS;AACV,mBAAiB,CAAC,IAAI,MAAM;AAC3B,oBAAQ,KAAK,cAAc,CAAC,mCAAmC;AAC/D,mBAAO;AAAA,UACT;AAAA,QACF,OAAO;AACJ,mBAAiB,CAAC,IAAI,MAAM;AAAA,QAC/B;AAAA,MACF;AAGA,qBAAe,MAAA;AACf,kBAAY,MAAA;AACZ,qBAAe,MAAA;AAGf,UAAI,WAAW,eAAe,YAAY,OAAO,GAAG;AAClD,mBAAW,MAAM;AACf,qBAAW,CAAC,KAAK,KAAK,KAAK,aAAa;AACtC,oBAAQ;AAAA,cACN,8CAA8C,GAAG,SAAS,KAAK,6BACnC,SAAS;AAAA,YAAA;AAAA,UAGzC;AAAA,QACF,GAAG,YAAY;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mvc-kit",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
4
4
  "description": "Zero-magic, class-based reactive ViewModel library",
5
5
  "type": "module",
6
6
  "main": "./dist/mvc-kit.cjs",