mvc-kit 2.12.5 → 2.13.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 (186) hide show
  1. package/agent-config/bin/postinstall.mjs +4 -3
  2. package/agent-config/bin/setup.mjs +5 -1
  3. package/agent-config/claude-code/agents/mvc-kit-architect.md +11 -8
  4. package/agent-config/claude-code/skills/guide/SKILL.md +20 -7
  5. package/agent-config/claude-code/skills/guide/patterns.md +12 -0
  6. package/agent-config/claude-code/skills/guide/recipes.md +510 -0
  7. package/agent-config/claude-code/skills/guide/testing.md +297 -0
  8. package/agent-config/claude-code/skills/review/SKILL.md +3 -13
  9. package/agent-config/claude-code/skills/review/checklist.md +30 -5
  10. package/agent-config/claude-code/skills/scaffold/SKILL.md +4 -13
  11. package/agent-config/lib/install-claude.mjs +84 -25
  12. package/dist/Channel.cjs +276 -300
  13. package/dist/Channel.cjs.map +1 -1
  14. package/dist/Channel.js +275 -299
  15. package/dist/Channel.js.map +1 -1
  16. package/dist/Collection.cjs +424 -504
  17. package/dist/Collection.cjs.map +1 -1
  18. package/dist/Collection.js +423 -503
  19. package/dist/Collection.js.map +1 -1
  20. package/dist/Controller.cjs +70 -67
  21. package/dist/Controller.cjs.map +1 -1
  22. package/dist/Controller.js +69 -66
  23. package/dist/Controller.js.map +1 -1
  24. package/dist/EventBus.cjs +77 -88
  25. package/dist/EventBus.cjs.map +1 -1
  26. package/dist/EventBus.js +76 -87
  27. package/dist/EventBus.js.map +1 -1
  28. package/dist/Feed.cjs +81 -77
  29. package/dist/Feed.cjs.map +1 -1
  30. package/dist/Feed.js +80 -76
  31. package/dist/Feed.js.map +1 -1
  32. package/dist/Model.cjs +181 -207
  33. package/dist/Model.cjs.map +1 -1
  34. package/dist/Model.js +179 -205
  35. package/dist/Model.js.map +1 -1
  36. package/dist/Pagination.cjs +75 -73
  37. package/dist/Pagination.cjs.map +1 -1
  38. package/dist/Pagination.js +74 -72
  39. package/dist/Pagination.js.map +1 -1
  40. package/dist/Pending.cjs +255 -287
  41. package/dist/Pending.cjs.map +1 -1
  42. package/dist/Pending.js +253 -285
  43. package/dist/Pending.js.map +1 -1
  44. package/dist/PersistentCollection.cjs +242 -285
  45. package/dist/PersistentCollection.cjs.map +1 -1
  46. package/dist/PersistentCollection.js +241 -284
  47. package/dist/PersistentCollection.js.map +1 -1
  48. package/dist/Resource.cjs +166 -174
  49. package/dist/Resource.cjs.map +1 -1
  50. package/dist/Resource.js +164 -172
  51. package/dist/Resource.js.map +1 -1
  52. package/dist/Selection.cjs +84 -94
  53. package/dist/Selection.cjs.map +1 -1
  54. package/dist/Selection.js +83 -93
  55. package/dist/Selection.js.map +1 -1
  56. package/dist/Service.cjs +54 -55
  57. package/dist/Service.cjs.map +1 -1
  58. package/dist/Service.js +53 -54
  59. package/dist/Service.js.map +1 -1
  60. package/dist/Sorting.cjs +102 -101
  61. package/dist/Sorting.cjs.map +1 -1
  62. package/dist/Sorting.js +102 -101
  63. package/dist/Sorting.js.map +1 -1
  64. package/dist/Trackable.cjs +112 -80
  65. package/dist/Trackable.cjs.map +1 -1
  66. package/dist/Trackable.js +111 -79
  67. package/dist/Trackable.js.map +1 -1
  68. package/dist/ViewModel.cjs +528 -576
  69. package/dist/ViewModel.cjs.map +1 -1
  70. package/dist/ViewModel.js +525 -573
  71. package/dist/ViewModel.js.map +1 -1
  72. package/dist/bindPublicMethods.cjs +43 -24
  73. package/dist/bindPublicMethods.cjs.map +1 -1
  74. package/dist/bindPublicMethods.js +43 -24
  75. package/dist/bindPublicMethods.js.map +1 -1
  76. package/dist/errors.cjs +67 -68
  77. package/dist/errors.cjs.map +1 -1
  78. package/dist/errors.js +68 -71
  79. package/dist/errors.js.map +1 -1
  80. package/dist/mvc-kit.cjs +44 -46
  81. package/dist/mvc-kit.js +5 -32
  82. package/dist/produceDraft.cjs +105 -95
  83. package/dist/produceDraft.cjs.map +1 -1
  84. package/dist/produceDraft.js +106 -97
  85. package/dist/produceDraft.js.map +1 -1
  86. package/dist/react/components/CardList.cjs +30 -40
  87. package/dist/react/components/CardList.cjs.map +1 -1
  88. package/dist/react/components/CardList.js +31 -41
  89. package/dist/react/components/CardList.js.map +1 -1
  90. package/dist/react/components/DataTable.cjs +146 -169
  91. package/dist/react/components/DataTable.cjs.map +1 -1
  92. package/dist/react/components/DataTable.js +147 -170
  93. package/dist/react/components/DataTable.js.map +1 -1
  94. package/dist/react/components/InfiniteScroll.cjs +51 -42
  95. package/dist/react/components/InfiniteScroll.cjs.map +1 -1
  96. package/dist/react/components/InfiniteScroll.js +52 -43
  97. package/dist/react/components/InfiniteScroll.js.map +1 -1
  98. package/dist/react/components/types.cjs +10 -6
  99. package/dist/react/components/types.cjs.map +1 -1
  100. package/dist/react/components/types.js +11 -9
  101. package/dist/react/components/types.js.map +1 -1
  102. package/dist/react/guards.cjs +10 -6
  103. package/dist/react/guards.cjs.map +1 -1
  104. package/dist/react/guards.js +11 -9
  105. package/dist/react/guards.js.map +1 -1
  106. package/dist/react/provider.cjs +23 -20
  107. package/dist/react/provider.cjs.map +1 -1
  108. package/dist/react/provider.js +23 -21
  109. package/dist/react/provider.js.map +1 -1
  110. package/dist/react/use-event-bus.cjs +24 -20
  111. package/dist/react/use-event-bus.cjs.map +1 -1
  112. package/dist/react/use-event-bus.js +24 -21
  113. package/dist/react/use-event-bus.js.map +1 -1
  114. package/dist/react/use-instance.cjs +43 -36
  115. package/dist/react/use-instance.cjs.map +1 -1
  116. package/dist/react/use-instance.js +43 -36
  117. package/dist/react/use-instance.js.map +1 -1
  118. package/dist/react/use-local.cjs +48 -64
  119. package/dist/react/use-local.cjs.map +1 -1
  120. package/dist/react/use-local.js +47 -63
  121. package/dist/react/use-local.js.map +1 -1
  122. package/dist/react/use-model.cjs +84 -98
  123. package/dist/react/use-model.cjs.map +1 -1
  124. package/dist/react/use-model.js +84 -100
  125. package/dist/react/use-model.js.map +1 -1
  126. package/dist/react/use-singleton.cjs +19 -23
  127. package/dist/react/use-singleton.cjs.map +1 -1
  128. package/dist/react/use-singleton.js +16 -20
  129. package/dist/react/use-singleton.js.map +1 -1
  130. package/dist/react/use-subscribe-only.cjs +28 -22
  131. package/dist/react/use-subscribe-only.cjs.map +1 -1
  132. package/dist/react/use-subscribe-only.js +28 -22
  133. package/dist/react/use-subscribe-only.js.map +1 -1
  134. package/dist/react/use-teardown.cjs +20 -19
  135. package/dist/react/use-teardown.cjs.map +1 -1
  136. package/dist/react/use-teardown.js +20 -19
  137. package/dist/react/use-teardown.js.map +1 -1
  138. package/dist/react-native/NativeCollection.cjs +98 -78
  139. package/dist/react-native/NativeCollection.cjs.map +1 -1
  140. package/dist/react-native/NativeCollection.js +97 -77
  141. package/dist/react-native/NativeCollection.js.map +1 -1
  142. package/dist/react-native.cjs +2 -4
  143. package/dist/react-native.js +1 -4
  144. package/dist/react.cjs +24 -26
  145. package/dist/react.js +1 -17
  146. package/dist/singleton.cjs +28 -22
  147. package/dist/singleton.cjs.map +1 -1
  148. package/dist/singleton.js +29 -26
  149. package/dist/singleton.js.map +1 -1
  150. package/dist/walkPrototypeChain.cjs +20 -12
  151. package/dist/walkPrototypeChain.cjs.map +1 -1
  152. package/dist/walkPrototypeChain.js +21 -13
  153. package/dist/walkPrototypeChain.js.map +1 -1
  154. package/dist/web/IndexedDBCollection.cjs +53 -36
  155. package/dist/web/IndexedDBCollection.cjs.map +1 -1
  156. package/dist/web/IndexedDBCollection.js +52 -35
  157. package/dist/web/IndexedDBCollection.js.map +1 -1
  158. package/dist/web/WebStorageCollection.cjs +82 -84
  159. package/dist/web/WebStorageCollection.cjs.map +1 -1
  160. package/dist/web/WebStorageCollection.js +81 -83
  161. package/dist/web/WebStorageCollection.js.map +1 -1
  162. package/dist/web/idb.cjs +107 -99
  163. package/dist/web/idb.cjs.map +1 -1
  164. package/dist/web/idb.js +108 -105
  165. package/dist/web/idb.js.map +1 -1
  166. package/dist/web.cjs +4 -6
  167. package/dist/web.js +1 -5
  168. package/dist/wrapAsyncMethods.cjs +141 -168
  169. package/dist/wrapAsyncMethods.cjs.map +1 -1
  170. package/dist/wrapAsyncMethods.js +141 -168
  171. package/dist/wrapAsyncMethods.js.map +1 -1
  172. package/package.json +8 -8
  173. package/src/Pending.test.ts +1 -2
  174. package/src/Sorting.test.ts +1 -1
  175. package/src/produceDraft.test.ts +3 -3
  176. package/src/react/components/CardList.test.tsx +1 -1
  177. package/src/react/components/DataTable.test.tsx +1 -1
  178. package/src/react/components/InfiniteScroll.test.tsx +5 -5
  179. package/dist/mvc-kit.cjs.map +0 -1
  180. package/dist/mvc-kit.js.map +0 -1
  181. package/dist/react-native.cjs.map +0 -1
  182. package/dist/react-native.js.map +0 -1
  183. package/dist/react.cjs.map +0 -1
  184. package/dist/react.js.map +0 -1
  185. package/dist/web.cjs.map +0 -1
  186. package/dist/web.js.map +0 -1
@@ -1,171 +1,144 @@
1
- import { isAbortError, classifyError } from "./errors.js";
2
1
  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 });
2
+ import { classifyError, isAbortError } from "./errors.js";
3
+ //#region src/wrapAsyncMethods.ts
4
+ var __DEV__ = typeof __MVC_KIT_DEV__ !== "undefined" && __MVC_KIT_DEV__;
5
+ var LOADING_TASK_STATE = Object.freeze({
6
+ loading: true,
7
+ error: null,
8
+ errorCode: null
9
+ });
10
+ /**
11
+ * Shared async method wrapping logic used by both ViewModel and Resource.
12
+ * Walks the prototype chain, wraps methods with async tracking, and registers cleanup.
13
+ * Returns the list of wrapped method keys.
14
+ */
5
15
  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
- let fromPrototype = false;
26
- let proto = Object.getPrototypeOf(instance);
27
- while (proto && proto !== Object.prototype) {
28
- const desc = Object.getOwnPropertyDescriptor(proto, key);
29
- if (desc && typeof desc.value === "function") {
30
- fromPrototype = true;
31
- break;
32
- }
33
- proto = Object.getPrototypeOf(proto);
34
- }
35
- if (!fromPrototype) {
36
- throw new Error(
37
- `[mvc-kit] "${key}" is a reserved property on ${className} and cannot be overridden.`
38
- );
39
- }
40
- }
41
- }
42
- }
43
- const methodEntries = ctx.methods ?? (() => {
44
- const result = [];
45
- const processed = /* @__PURE__ */ new Set();
46
- walkPrototypeChain(instance, stopPrototype, (key, desc) => {
47
- if (desc.get || desc.set) return;
48
- if (typeof desc.value !== "function") return;
49
- if (key.startsWith("_")) return;
50
- if (lifecycleHooks.has(key)) return;
51
- if (processed.has(key)) return;
52
- processed.add(key);
53
- result.push({ key, fn: desc.value });
54
- });
55
- return result;
56
- })();
57
- const wrappedKeys = [];
58
- for (const { key, fn: original } of methodEntries) {
59
- let pruned = false;
60
- const wrapper = function(...args) {
61
- if (isDisposed()) {
62
- if (__DEV__) {
63
- console.warn(`[mvc-kit] "${key}" called after dispose — ignored.`);
64
- }
65
- return void 0;
66
- }
67
- if (__DEV__ && !isInitialized()) {
68
- console.warn(
69
- `[mvc-kit] "${key}" called before init(). Async tracking is active only after init().`
70
- );
71
- }
72
- let result;
73
- try {
74
- result = original.apply(instance, args);
75
- } catch (e) {
76
- throw e;
77
- }
78
- if (!result || typeof result.then !== "function") {
79
- if (!pruned) {
80
- pruned = true;
81
- asyncStates.delete(key);
82
- asyncSnapshots.delete(key);
83
- instance[key] = original.bind(instance);
84
- }
85
- return result;
86
- }
87
- let internal = asyncStates.get(key);
88
- if (!internal) {
89
- internal = { loading: false, error: null, errorCode: null, count: 0 };
90
- asyncStates.set(key, internal);
91
- }
92
- internal.count++;
93
- internal.loading = true;
94
- internal.error = null;
95
- internal.errorCode = null;
96
- asyncSnapshots.set(key, LOADING_TASK_STATE);
97
- notifyAsync();
98
- if (__DEV__ && activeOps) {
99
- activeOps.set(key, (activeOps.get(key) ?? 0) + 1);
100
- }
101
- const finalizeOp = (errorMsg, errorCode) => {
102
- internal.count--;
103
- internal.loading = internal.count > 0;
104
- if (errorMsg !== void 0) {
105
- internal.error = errorMsg;
106
- internal.errorCode = errorCode ?? null;
107
- }
108
- asyncSnapshots.set(
109
- key,
110
- Object.freeze({ loading: internal.loading, error: internal.error, errorCode: internal.errorCode })
111
- );
112
- notifyAsync();
113
- if (__DEV__ && activeOps) {
114
- const c = (activeOps.get(key) ?? 1) - 1;
115
- if (c <= 0) activeOps.delete(key);
116
- else activeOps.set(key, c);
117
- }
118
- };
119
- return result.then(
120
- (value) => {
121
- if (!isDisposed()) finalizeOp();
122
- return value;
123
- },
124
- (error) => {
125
- if (isAbortError(error)) {
126
- if (!isDisposed()) finalizeOp();
127
- return void 0;
128
- }
129
- if (isDisposed()) return void 0;
130
- const classified = classifyError(error);
131
- finalizeOp(classified.message, classified.code);
132
- throw error;
133
- }
134
- );
135
- };
136
- wrappedKeys.push(key);
137
- instance[key] = wrapper;
138
- }
139
- if (wrappedKeys.length > 0) {
140
- addCleanup(() => {
141
- const opsSnapshot = __DEV__ && activeOps ? new Map(activeOps) : null;
142
- for (const k of wrappedKeys) {
143
- if (__DEV__) {
144
- instance[k] = () => {
145
- console.warn(`[mvc-kit] "${k}" called after dispose — ignored.`);
146
- return void 0;
147
- };
148
- } else {
149
- instance[k] = () => void 0;
150
- }
151
- }
152
- asyncListeners.clear();
153
- asyncStates.clear();
154
- asyncSnapshots.clear();
155
- if (__DEV__ && opsSnapshot && opsSnapshot.size > 0) {
156
- setTimeout(() => {
157
- for (const [key, count] of opsSnapshot) {
158
- console.warn(
159
- `[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.`
160
- );
161
- }
162
- }, ghostTimeout);
163
- }
164
- });
165
- }
166
- return wrappedKeys;
16
+ const { instance, stopPrototype, reservedKeys, lifecycleHooks, isDisposed, isInitialized, asyncStates, asyncSnapshots, asyncListeners, notifyAsync, addCleanup, ghostTimeout, className, activeOps } = ctx;
17
+ if (__DEV__) {
18
+ for (const key of reservedKeys) if (Object.getOwnPropertyDescriptor(instance, key)?.value !== void 0) {
19
+ let fromPrototype = false;
20
+ let proto = Object.getPrototypeOf(instance);
21
+ while (proto && proto !== Object.prototype) {
22
+ const desc = Object.getOwnPropertyDescriptor(proto, key);
23
+ if (desc && typeof desc.value === "function") {
24
+ fromPrototype = true;
25
+ break;
26
+ }
27
+ proto = Object.getPrototypeOf(proto);
28
+ }
29
+ if (!fromPrototype) throw new Error(`[mvc-kit] "${key}" is a reserved property on ${className} and cannot be overridden.`);
30
+ }
31
+ }
32
+ const methodEntries = ctx.methods ?? (() => {
33
+ const result = [];
34
+ const processed = /* @__PURE__ */ new Set();
35
+ walkPrototypeChain(instance, stopPrototype, (key, desc) => {
36
+ if (desc.get || desc.set) return;
37
+ if (typeof desc.value !== "function") return;
38
+ if (key.startsWith("_")) return;
39
+ if (lifecycleHooks.has(key)) return;
40
+ if (processed.has(key)) return;
41
+ processed.add(key);
42
+ result.push({
43
+ key,
44
+ fn: desc.value
45
+ });
46
+ });
47
+ return result;
48
+ })();
49
+ const wrappedKeys = [];
50
+ for (const { key, fn: original } of methodEntries) {
51
+ let pruned = false;
52
+ const wrapper = function(...args) {
53
+ if (isDisposed()) {
54
+ if (__DEV__) console.warn(`[mvc-kit] "${key}" called after dispose — ignored.`);
55
+ return;
56
+ }
57
+ if (__DEV__ && !isInitialized()) console.warn(`[mvc-kit] "${key}" called before init(). Async tracking is active only after init().`);
58
+ let result;
59
+ try {
60
+ result = original.apply(instance, args);
61
+ } catch (e) {
62
+ throw e;
63
+ }
64
+ if (!result || typeof result.then !== "function") {
65
+ if (!pruned) {
66
+ pruned = true;
67
+ asyncStates.delete(key);
68
+ asyncSnapshots.delete(key);
69
+ instance[key] = original.bind(instance);
70
+ }
71
+ return result;
72
+ }
73
+ let internal = asyncStates.get(key);
74
+ if (!internal) {
75
+ internal = {
76
+ loading: false,
77
+ error: null,
78
+ errorCode: null,
79
+ count: 0
80
+ };
81
+ asyncStates.set(key, internal);
82
+ }
83
+ internal.count++;
84
+ internal.loading = true;
85
+ internal.error = null;
86
+ internal.errorCode = null;
87
+ asyncSnapshots.set(key, LOADING_TASK_STATE);
88
+ notifyAsync();
89
+ if (__DEV__ && activeOps) activeOps.set(key, (activeOps.get(key) ?? 0) + 1);
90
+ const finalizeOp = (errorMsg, errorCode) => {
91
+ internal.count--;
92
+ internal.loading = internal.count > 0;
93
+ if (errorMsg !== void 0) {
94
+ internal.error = errorMsg;
95
+ internal.errorCode = errorCode ?? null;
96
+ }
97
+ asyncSnapshots.set(key, Object.freeze({
98
+ loading: internal.loading,
99
+ error: internal.error,
100
+ errorCode: internal.errorCode
101
+ }));
102
+ notifyAsync();
103
+ if (__DEV__ && activeOps) {
104
+ const c = (activeOps.get(key) ?? 1) - 1;
105
+ if (c <= 0) activeOps.delete(key);
106
+ else activeOps.set(key, c);
107
+ }
108
+ };
109
+ return result.then((value) => {
110
+ if (!isDisposed()) finalizeOp();
111
+ return value;
112
+ }, (error) => {
113
+ if (isAbortError(error)) {
114
+ if (!isDisposed()) finalizeOp();
115
+ return;
116
+ }
117
+ if (isDisposed()) return void 0;
118
+ const classified = classifyError(error);
119
+ finalizeOp(classified.message, classified.code);
120
+ throw error;
121
+ });
122
+ };
123
+ wrappedKeys.push(key);
124
+ instance[key] = wrapper;
125
+ }
126
+ if (wrappedKeys.length > 0) addCleanup(() => {
127
+ const opsSnapshot = __DEV__ && activeOps ? new Map(activeOps) : null;
128
+ for (const k of wrappedKeys) if (__DEV__) instance[k] = () => {
129
+ console.warn(`[mvc-kit] "${k}" called after dispose — ignored.`);
130
+ };
131
+ else instance[k] = () => void 0;
132
+ asyncListeners.clear();
133
+ asyncStates.clear();
134
+ asyncSnapshots.clear();
135
+ if (__DEV__ && opsSnapshot && opsSnapshot.size > 0) setTimeout(() => {
136
+ for (const [key, count] of opsSnapshot) console.warn(`[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.`);
137
+ }, ghostTimeout);
138
+ });
139
+ return wrappedKeys;
167
140
  }
168
- export {
169
- wrapAsyncMethods
170
- };
171
- //# sourceMappingURL=wrapAsyncMethods.js.map
141
+ //#endregion
142
+ export { wrapAsyncMethods };
143
+
144
+ //# sourceMappingURL=wrapAsyncMethods.js.map
@@ -1 +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\n/** @internal Mutable internal async tracking state per method. */\nexport interface InternalTaskState {\n loading: boolean;\n error: string | null;\n errorCode: TaskState['errorCode'];\n count: number;\n}\n\n/** @internal Configuration for the shared async method wrapping logic. */\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 // Skip own properties that are bound methods from bindPublicMethods (the key\n // also exists as a method on the prototype chain). Only reject class-field overrides.\n if (__DEV__) {\n for (const key of reservedKeys) {\n if (Object.getOwnPropertyDescriptor(instance, key)?.value !== undefined) {\n // Check if the prototype chain has a method with this name —\n // if so, the own property is from bindPublicMethods, not a user override\n let fromPrototype = false;\n let proto = Object.getPrototypeOf(instance);\n while (proto && proto !== Object.prototype) {\n const desc = Object.getOwnPropertyDescriptor(proto, key);\n if (desc && typeof desc.value === 'function') { fromPrototype = true; break; }\n proto = Object.getPrototypeOf(proto);\n }\n if (!fromPrototype) {\n throw new Error(\n `[mvc-kit] \"${key}\" is a reserved property on ${className} and cannot be overridden.`\n );\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 // Shared bookkeeping: decrement count, snapshot state, update DEV active ops\n const finalizeOp = (errorMsg?: string | null, errorCode?: TaskState['errorCode']) => {\n internal!.count--;\n internal!.loading = internal!.count > 0;\n if (errorMsg !== undefined) {\n internal!.error = errorMsg;\n internal!.errorCode = errorCode ?? null;\n }\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\n return (result as Promise<unknown>).then(\n (value) => {\n if (!isDisposed()) finalizeOp();\n return value;\n },\n (error) => {\n // AbortError — silently swallow\n if (isAbortError(error)) {\n if (!isDisposed()) finalizeOp();\n return undefined;\n }\n\n // Disposed — fizzle silently\n if (isDisposed()) return undefined;\n\n const classified = classifyError(error);\n finalizeOp(classified.message, classified.code);\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":"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"}
1
+ {"version":3,"file":"wrapAsyncMethods.js","names":[],"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\n/** @internal Mutable internal async tracking state per method. */\nexport interface InternalTaskState {\n loading: boolean;\n error: string | null;\n errorCode: TaskState['errorCode'];\n count: number;\n}\n\n/** @internal Configuration for the shared async method wrapping logic. */\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 // Skip own properties that are bound methods from bindPublicMethods (the key\n // also exists as a method on the prototype chain). Only reject class-field overrides.\n if (__DEV__) {\n for (const key of reservedKeys) {\n if (Object.getOwnPropertyDescriptor(instance, key)?.value !== undefined) {\n // Check if the prototype chain has a method with this name —\n // if so, the own property is from bindPublicMethods, not a user override\n let fromPrototype = false;\n let proto = Object.getPrototypeOf(instance);\n while (proto && proto !== Object.prototype) {\n const desc = Object.getOwnPropertyDescriptor(proto, key);\n if (desc && typeof desc.value === 'function') { fromPrototype = true; break; }\n proto = Object.getPrototypeOf(proto);\n }\n if (!fromPrototype) {\n throw new Error(\n `[mvc-kit] \"${key}\" is a reserved property on ${className} and cannot be overridden.`\n );\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 // Shared bookkeeping: decrement count, snapshot state, update DEV active ops\n const finalizeOp = (errorMsg?: string | null, errorCode?: TaskState['errorCode']) => {\n internal!.count--;\n internal!.loading = internal!.count > 0;\n if (errorMsg !== undefined) {\n internal!.error = errorMsg;\n internal!.errorCode = errorCode ?? null;\n }\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\n return (result as Promise<unknown>).then(\n (value) => {\n if (!isDisposed()) finalizeOp();\n return value;\n },\n (error) => {\n // AbortError — silently swallow\n if (isAbortError(error)) {\n if (!isDisposed()) finalizeOp();\n return undefined;\n }\n\n // Disposed — fizzle silently\n if (isDisposed()) return undefined;\n\n const classified = classifyError(error);\n finalizeOp(classified.message, classified.code);\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"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mvc-kit",
3
- "version": "2.12.5",
3
+ "version": "2.13.0",
4
4
  "description": "Zero-magic, class-based reactive ViewModel library",
5
5
  "type": "module",
6
6
  "main": "./dist/mvc-kit.cjs",
@@ -81,16 +81,16 @@
81
81
  },
82
82
  "devDependencies": {
83
83
  "@testing-library/react": "^16.0.0",
84
- "@types/react": "^18.3.0",
85
- "esbuild": "^0.25.12",
84
+ "@types/react": "^19.2.14",
85
+ "esbuild": "^0.28.0",
86
86
  "fake-indexeddb": "^6.2.5",
87
- "jsdom": "^26.0.0",
87
+ "jsdom": "^29.0.2",
88
88
  "msw": "^2.12.10",
89
- "react": "^18.3.0",
90
- "react-dom": "^18.3.0",
89
+ "react": "^19.2.5",
90
+ "react-dom": "^19.2.5",
91
91
  "react-router-dom": "^7.0.0",
92
- "typescript": "~5.9.3",
93
- "vite": "^7.3.1",
92
+ "typescript": "~6.0.2",
93
+ "vite": "^8.0.8",
94
94
  "vitest": "^4.0.18"
95
95
  }
96
96
  }
@@ -1510,8 +1510,7 @@ describe('Pending', () => {
1510
1510
 
1511
1511
  it('supersede replaces meta', async () => {
1512
1512
  const p = new Pending<string, TestMeta>();
1513
- let resolve1!: () => void;
1514
- const promise1 = new Promise<void>(r => { resolve1 = r; });
1513
+ const promise1 = new Promise<void>(() => {});
1515
1514
 
1516
1515
  p.enqueue('a', 'send', () => promise1, { label: 'first', priority: 1 });
1517
1516
  await vi.advanceTimersByTimeAsync(0);
@@ -1,4 +1,4 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
1
+ import { describe, it, expect, vi } from 'vitest';
2
2
  import { Sorting } from './Sorting';
3
3
  import type { SortDescriptor } from './Sorting';
4
4
  import { ViewModel } from './ViewModel';
@@ -143,7 +143,7 @@ describe('produceDraft', () => {
143
143
  // ── Original immutability ───────────────────────────────────────
144
144
 
145
145
  it('never mutates original state', () => {
146
- const state = Object.freeze({ count: 0, name: 'test' });
146
+ const state = Object.freeze<{ count: number; name: string }>({ count: 0, name: 'test' });
147
147
  const result = produceDraft(state, (d) => {
148
148
  d.count = 5;
149
149
  d.name = 'updated';
@@ -154,8 +154,8 @@ describe('produceDraft', () => {
154
154
  });
155
155
 
156
156
  it('never mutates original nested objects', () => {
157
- const config = Object.freeze({ theme: 'dark', size: 14 });
158
- const state = Object.freeze({ config });
157
+ const config = { theme: 'dark', size: 14 };
158
+ const state = Object.freeze<{ config: { theme: string; size: number } }>({ config });
159
159
  const result = produceDraft(state, (d) => {
160
160
  d.config.theme = 'light';
161
161
  });
@@ -59,7 +59,7 @@ describe('CardList', () => {
59
59
  });
60
60
 
61
61
  it('uses custom keyOf', () => {
62
- const { container } = render(
62
+ render(
63
63
  <CardList
64
64
  items={[{ uid: 'x', label: 'X' }]}
65
65
  keyOf={(item: any) => item.uid}
@@ -411,7 +411,7 @@ describe('DataTable', () => {
411
411
  <DataTable
412
412
  items={users.slice(0, 1)}
413
413
  columns={columns}
414
- renderRow={(item, index, cells) => (
414
+ renderRow={(_item, _index, cells) => (
415
415
  <>{cells}<td data-testid="extra">Extra</td></>
416
416
  )}
417
417
  />,
@@ -14,7 +14,7 @@ beforeEach(() => {
14
14
  mockObserve = vi.fn();
15
15
  mockDisconnect = vi.fn();
16
16
 
17
- global.IntersectionObserver = class MockIntersectionObserver {
17
+ globalThis.IntersectionObserver = class MockIntersectionObserver {
18
18
  constructor(callback: IntersectionObserverCallback) {
19
19
  observerCallback = callback;
20
20
  }
@@ -157,18 +157,18 @@ describe('InfiniteScroll', () => {
157
157
  });
158
158
 
159
159
  it('handles missing IntersectionObserver (SSR guard)', () => {
160
- const saved = global.IntersectionObserver;
161
- delete (global as any).IntersectionObserver;
160
+ const saved = globalThis.IntersectionObserver;
161
+ delete (globalThis as any).IntersectionObserver;
162
162
 
163
163
  // Should not throw
164
- const { container } = render(
164
+ render(
165
165
  <InfiniteScroll hasMore={true} onLoadMore={() => {}}>
166
166
  <p>Content</p>
167
167
  </InfiniteScroll>,
168
168
  );
169
169
  expect(screen.getByText('Content')).toBeDefined();
170
170
 
171
- global.IntersectionObserver = saved;
171
+ globalThis.IntersectionObserver = saved;
172
172
  });
173
173
 
174
174
  describe('direction prop', () => {
@@ -1 +0,0 @@
1
- {"version":3,"file":"mvc-kit.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"mvc-kit.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"react-native.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"react-native.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"react.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/react.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"react.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
package/dist/web.cjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"web.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
package/dist/web.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"web.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}