mvc-kit 2.12.4 → 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
package/dist/Pending.js CHANGED
@@ -1,288 +1,256 @@
1
1
  import { classifyError, isAbortError } from "./errors.js";
2
2
  import { Trackable } from "./Trackable.js";
3
- const __DEV__ = typeof __MVC_KIT_DEV__ !== "undefined" && __MVC_KIT_DEV__;
4
- class Pending extends Trackable {
5
- // ── Static config (Channel pattern — override via subclass) ──
6
- /** Maximum number of retry attempts before marking as failed. */
7
- static MAX_RETRIES = 5;
8
- /** Base delay (ms) for retry backoff. */
9
- static RETRY_BASE = 1e3;
10
- /** Maximum delay cap (ms) for retry backoff. */
11
- static RETRY_MAX = 3e4;
12
- /** Exponential backoff multiplier for retry delay. */
13
- static RETRY_FACTOR = 2;
14
- // ── Private state ──
15
- _operations = /* @__PURE__ */ new Map();
16
- _snapshots = /* @__PURE__ */ new Map();
17
- _entriesCache = null;
18
- constructor() {
19
- super();
20
- }
21
- // ── Readable state (reactive — auto-tracked by ViewModel getters) ──
22
- /** Get the frozen status snapshot for an operation by ID, or null if not found. */
23
- getStatus(id) {
24
- return this._snapshots.get(id) ?? null;
25
- }
26
- /** Whether an operation exists for the given ID. */
27
- has(id) {
28
- return this._operations.has(id);
29
- }
30
- /** Number of operations (all statuses). */
31
- get count() {
32
- return this._operations.size;
33
- }
34
- /** Whether any operations are in-flight (active or retrying). */
35
- get hasPending() {
36
- for (const op of this._operations.values()) {
37
- if (op.status !== "failed") return true;
38
- }
39
- return false;
40
- }
41
- /** Whether any operations are in a failed state. */
42
- get hasFailed() {
43
- for (const op of this._operations.values()) {
44
- if (op.status === "failed") return true;
45
- }
46
- return false;
47
- }
48
- /** Number of operations in a failed state. */
49
- get failedCount() {
50
- let n = 0;
51
- for (const op of this._operations.values()) {
52
- if (op.status === "failed") n++;
53
- }
54
- return n;
55
- }
56
- /** All operations as a frozen array of entries (id + snapshot). Cached until next mutation. */
57
- get entries() {
58
- if (this._entriesCache === null) {
59
- const result = [];
60
- for (const [id, snapshot] of this._snapshots) {
61
- result.push(Object.freeze({ ...snapshot, id }));
62
- }
63
- this._entriesCache = Object.freeze(result);
64
- }
65
- return this._entriesCache;
66
- }
67
- // ── Core API ──
68
- /**
69
- * Enqueue an operation for the given ID. Fire-and-forget (synchronous return).
70
- * If the same ID already has a pending operation, it is superseded (aborted).
71
- */
72
- enqueue(id, operation, execute, meta) {
73
- if (this.disposed) {
74
- if (__DEV__) {
75
- console.warn("[mvc-kit] Pending.enqueue() called after dispose — ignored.");
76
- }
77
- return;
78
- }
79
- const existing = this._operations.get(id);
80
- if (existing) {
81
- existing.abortController.abort();
82
- if (existing.retryTimer !== null) {
83
- clearTimeout(existing.retryTimer);
84
- }
85
- }
86
- const op = {
87
- id,
88
- operation,
89
- execute,
90
- status: "active",
91
- attempts: 0,
92
- error: null,
93
- errorCode: null,
94
- nextRetryAt: null,
95
- createdAt: Date.now(),
96
- abortController: new AbortController(),
97
- retryTimer: null,
98
- meta: meta ?? null
99
- };
100
- this._operations.set(id, op);
101
- this._snapshot(op);
102
- this.notify();
103
- queueMicrotask(() => this._process(id));
104
- }
105
- // ── Controls ──
106
- /** Retry a failed operation. No-op if the operation is not in 'failed' status. */
107
- retry(id) {
108
- if (this.disposed) {
109
- if (__DEV__) {
110
- console.warn("[mvc-kit] Pending.retry() called after dispose — ignored.");
111
- }
112
- return;
113
- }
114
- const op = this._operations.get(id);
115
- if (!op || op.status !== "failed") return;
116
- op.attempts = 0;
117
- op.error = null;
118
- op.errorCode = null;
119
- op.nextRetryAt = null;
120
- op.abortController = new AbortController();
121
- this._process(id);
122
- }
123
- /** Retry all failed operations. */
124
- retryAll() {
125
- if (this.disposed) {
126
- if (__DEV__) {
127
- console.warn("[mvc-kit] Pending.retryAll() called after dispose — ignored.");
128
- }
129
- return;
130
- }
131
- const failedIds = [];
132
- for (const op of this._operations.values()) {
133
- if (op.status === "failed") failedIds.push(op.id);
134
- }
135
- for (const id of failedIds) {
136
- this.retry(id);
137
- }
138
- }
139
- /** Cancel an in-flight operation by ID. Aborts the signal, clears timers, and removes it. */
140
- cancel(id) {
141
- const op = this._operations.get(id);
142
- if (!op) return;
143
- op.abortController.abort();
144
- if (op.retryTimer !== null) {
145
- clearTimeout(op.retryTimer);
146
- }
147
- this._operations.delete(id);
148
- this._snapshots.delete(id);
149
- this.notify();
150
- }
151
- /** Cancel all operations. */
152
- cancelAll() {
153
- for (const op of this._operations.values()) {
154
- op.abortController.abort();
155
- if (op.retryTimer !== null) {
156
- clearTimeout(op.retryTimer);
157
- }
158
- }
159
- this._operations.clear();
160
- this._snapshots.clear();
161
- this.notify();
162
- }
163
- /** Remove a failed operation without retrying. No-op if the operation is not in 'failed' status. */
164
- dismiss(id) {
165
- const op = this._operations.get(id);
166
- if (!op || op.status !== "failed") return;
167
- this._operations.delete(id);
168
- this._snapshots.delete(id);
169
- this.notify();
170
- }
171
- /** Remove all failed operations without retrying. */
172
- dismissAll() {
173
- const failedIds = [];
174
- for (const op of this._operations.values()) {
175
- if (op.status === "failed") failedIds.push(op.id);
176
- }
177
- if (failedIds.length === 0) return;
178
- for (const id of failedIds) {
179
- this._operations.delete(id);
180
- this._snapshots.delete(id);
181
- }
182
- this.notify();
183
- }
184
- // ── Hooks (overridable in subclass) ──
185
- /**
186
- * Determines whether an error is retryable. Override in a subclass to customize.
187
- * Default: retries on network, timeout, and server_error codes.
188
- * @protected
189
- */
190
- isRetryable(error) {
191
- const code = classifyError(error).code;
192
- return code === "network" || code === "timeout" || code === "server_error";
193
- }
194
- // ── Lifecycle ──
195
- /** Dispose: cancels all operations, then runs Trackable cleanup. */
196
- dispose() {
197
- if (this.disposed) return;
198
- this.cancelAll();
199
- super.dispose();
200
- }
201
- // ── Notification override ──
202
- /** @internal Invalidates entries cache before notifying subscribers. */
203
- notify() {
204
- this._entriesCache = null;
205
- super.notify();
206
- }
207
- // ── Internals ──
208
- _snapshot(op) {
209
- const ctor = this.constructor;
210
- this._snapshots.set(op.id, Object.freeze({
211
- status: op.status,
212
- operation: op.operation,
213
- attempts: op.attempts,
214
- maxRetries: ctor.MAX_RETRIES,
215
- error: op.error,
216
- errorCode: op.errorCode,
217
- nextRetryAt: op.nextRetryAt,
218
- createdAt: op.createdAt,
219
- meta: op.meta
220
- }));
221
- }
222
- _process(id) {
223
- const op = this._operations.get(id);
224
- if (!op || this.disposed) return;
225
- op.status = "active";
226
- op.attempts++;
227
- op.error = null;
228
- op.errorCode = null;
229
- op.nextRetryAt = null;
230
- this._snapshot(op);
231
- this.notify();
232
- op.execute(op.abortController.signal).then(
233
- () => {
234
- if (this._operations.get(id) !== op) return;
235
- const operation = op.operation;
236
- this._operations.delete(id);
237
- this._snapshots.delete(id);
238
- this.notify();
239
- this.onConfirmed?.(id, operation);
240
- },
241
- (error) => {
242
- if (this._operations.get(id) !== op) return;
243
- if (isAbortError(error)) {
244
- this._operations.delete(id);
245
- this._snapshots.delete(id);
246
- this.notify();
247
- return;
248
- }
249
- const ctor = this.constructor;
250
- const classified = classifyError(error);
251
- if (this.isRetryable(error) && op.attempts < ctor.MAX_RETRIES) {
252
- op.status = "retrying";
253
- const delay = this._calculateDelay(op.attempts - 1);
254
- op.nextRetryAt = Date.now() + delay;
255
- op.error = classified.message;
256
- op.errorCode = classified.code;
257
- this._snapshot(op);
258
- this.notify();
259
- op.retryTimer = setTimeout(() => {
260
- op.retryTimer = null;
261
- this._process(id);
262
- }, delay);
263
- } else {
264
- op.status = "failed";
265
- op.error = classified.message;
266
- op.errorCode = classified.code;
267
- op.nextRetryAt = null;
268
- this._snapshot(op);
269
- this.notify();
270
- this.onFailed?.(id, op.operation, error);
271
- }
272
- }
273
- );
274
- }
275
- /** Computes retry backoff delay with jitter (Channel formula). @private */
276
- _calculateDelay(attempt) {
277
- const ctor = this.constructor;
278
- const capped = Math.min(
279
- ctor.RETRY_BASE * Math.pow(ctor.RETRY_FACTOR, attempt),
280
- ctor.RETRY_MAX
281
- );
282
- return Math.random() * capped;
283
- }
284
- }
285
- export {
286
- Pending
3
+ //#region src/Pending.ts
4
+ var __DEV__ = typeof __MVC_KIT_DEV__ !== "undefined" && __MVC_KIT_DEV__;
5
+ /**
6
+ * Per-item operation queue with retry and status tracking.
7
+ * Tracks operations by key with exponential backoff retry on transient errors.
8
+ * Subscribable auto-tracked when used as a ViewModel/Resource property.
9
+ */
10
+ var Pending = class extends Trackable {
11
+ /** Maximum number of retry attempts before marking as failed. */
12
+ static MAX_RETRIES = 5;
13
+ /** Base delay (ms) for retry backoff. */
14
+ static RETRY_BASE = 1e3;
15
+ /** Maximum delay cap (ms) for retry backoff. */
16
+ static RETRY_MAX = 3e4;
17
+ /** Exponential backoff multiplier for retry delay. */
18
+ static RETRY_FACTOR = 2;
19
+ _operations = /* @__PURE__ */ new Map();
20
+ _snapshots = /* @__PURE__ */ new Map();
21
+ _entriesCache = null;
22
+ constructor() {
23
+ super();
24
+ }
25
+ /** Get the frozen status snapshot for an operation by ID, or null if not found. */
26
+ getStatus(id) {
27
+ return this._snapshots.get(id) ?? null;
28
+ }
29
+ /** Whether an operation exists for the given ID. */
30
+ has(id) {
31
+ return this._operations.has(id);
32
+ }
33
+ /** Number of operations (all statuses). */
34
+ get count() {
35
+ return this._operations.size;
36
+ }
37
+ /** Whether any operations are in-flight (active or retrying). */
38
+ get hasPending() {
39
+ for (const op of this._operations.values()) if (op.status !== "failed") return true;
40
+ return false;
41
+ }
42
+ /** Whether any operations are in a failed state. */
43
+ get hasFailed() {
44
+ for (const op of this._operations.values()) if (op.status === "failed") return true;
45
+ return false;
46
+ }
47
+ /** Number of operations in a failed state. */
48
+ get failedCount() {
49
+ let n = 0;
50
+ for (const op of this._operations.values()) if (op.status === "failed") n++;
51
+ return n;
52
+ }
53
+ /** All operations as a frozen array of entries (id + snapshot). Cached until next mutation. */
54
+ get entries() {
55
+ if (this._entriesCache === null) {
56
+ const result = [];
57
+ for (const [id, snapshot] of this._snapshots) result.push(Object.freeze({
58
+ ...snapshot,
59
+ id
60
+ }));
61
+ this._entriesCache = Object.freeze(result);
62
+ }
63
+ return this._entriesCache;
64
+ }
65
+ /**
66
+ * Enqueue an operation for the given ID. Fire-and-forget (synchronous return).
67
+ * If the same ID already has a pending operation, it is superseded (aborted).
68
+ */
69
+ enqueue(id, operation, execute, meta) {
70
+ if (this.disposed) {
71
+ if (__DEV__) console.warn("[mvc-kit] Pending.enqueue() called after dispose — ignored.");
72
+ return;
73
+ }
74
+ const existing = this._operations.get(id);
75
+ if (existing) {
76
+ existing.abortController.abort();
77
+ if (existing.retryTimer !== null) clearTimeout(existing.retryTimer);
78
+ }
79
+ const op = {
80
+ id,
81
+ operation,
82
+ execute,
83
+ status: "active",
84
+ attempts: 0,
85
+ error: null,
86
+ errorCode: null,
87
+ nextRetryAt: null,
88
+ createdAt: Date.now(),
89
+ abortController: new AbortController(),
90
+ retryTimer: null,
91
+ meta: meta ?? null
92
+ };
93
+ this._operations.set(id, op);
94
+ this._snapshot(op);
95
+ this.notify();
96
+ queueMicrotask(() => this._process(id));
97
+ }
98
+ /** Retry a failed operation. No-op if the operation is not in 'failed' status. */
99
+ retry(id) {
100
+ if (this.disposed) {
101
+ if (__DEV__) console.warn("[mvc-kit] Pending.retry() called after dispose — ignored.");
102
+ return;
103
+ }
104
+ const op = this._operations.get(id);
105
+ if (!op || op.status !== "failed") return;
106
+ op.attempts = 0;
107
+ op.error = null;
108
+ op.errorCode = null;
109
+ op.nextRetryAt = null;
110
+ op.abortController = new AbortController();
111
+ this._process(id);
112
+ }
113
+ /** Retry all failed operations. */
114
+ retryAll() {
115
+ if (this.disposed) {
116
+ if (__DEV__) console.warn("[mvc-kit] Pending.retryAll() called after dispose — ignored.");
117
+ return;
118
+ }
119
+ const failedIds = [];
120
+ for (const op of this._operations.values()) if (op.status === "failed") failedIds.push(op.id);
121
+ for (const id of failedIds) this.retry(id);
122
+ }
123
+ /** Cancel an in-flight operation by ID. Aborts the signal, clears timers, and removes it. */
124
+ cancel(id) {
125
+ const op = this._operations.get(id);
126
+ if (!op) return;
127
+ op.abortController.abort();
128
+ if (op.retryTimer !== null) clearTimeout(op.retryTimer);
129
+ this._operations.delete(id);
130
+ this._snapshots.delete(id);
131
+ this.notify();
132
+ }
133
+ /** Cancel all operations. */
134
+ cancelAll() {
135
+ for (const op of this._operations.values()) {
136
+ op.abortController.abort();
137
+ if (op.retryTimer !== null) clearTimeout(op.retryTimer);
138
+ }
139
+ this._operations.clear();
140
+ this._snapshots.clear();
141
+ this.notify();
142
+ }
143
+ /** Remove a failed operation without retrying. No-op if the operation is not in 'failed' status. */
144
+ dismiss(id) {
145
+ const op = this._operations.get(id);
146
+ if (!op || op.status !== "failed") return;
147
+ this._operations.delete(id);
148
+ this._snapshots.delete(id);
149
+ this.notify();
150
+ }
151
+ /** Remove all failed operations without retrying. */
152
+ dismissAll() {
153
+ const failedIds = [];
154
+ for (const op of this._operations.values()) if (op.status === "failed") failedIds.push(op.id);
155
+ if (failedIds.length === 0) return;
156
+ for (const id of failedIds) {
157
+ this._operations.delete(id);
158
+ this._snapshots.delete(id);
159
+ }
160
+ this.notify();
161
+ }
162
+ /**
163
+ * Determines whether an error is retryable. Override in a subclass to customize.
164
+ * Default: retries on network, timeout, and server_error codes.
165
+ * @protected
166
+ */
167
+ isRetryable(error) {
168
+ const code = classifyError(error).code;
169
+ return code === "network" || code === "timeout" || code === "server_error";
170
+ }
171
+ /** Dispose: cancels all operations, then runs Trackable cleanup. */
172
+ dispose() {
173
+ if (this.disposed) return;
174
+ this.cancelAll();
175
+ super.dispose();
176
+ }
177
+ /** @internal Invalidates entries cache before notifying subscribers. */
178
+ notify() {
179
+ this._entriesCache = null;
180
+ super.notify();
181
+ }
182
+ _snapshot(op) {
183
+ const ctor = this.constructor;
184
+ this._snapshots.set(op.id, Object.freeze({
185
+ status: op.status,
186
+ operation: op.operation,
187
+ attempts: op.attempts,
188
+ maxRetries: ctor.MAX_RETRIES,
189
+ error: op.error,
190
+ errorCode: op.errorCode,
191
+ nextRetryAt: op.nextRetryAt,
192
+ createdAt: op.createdAt,
193
+ meta: op.meta
194
+ }));
195
+ }
196
+ _process(id) {
197
+ const op = this._operations.get(id);
198
+ if (!op || this.disposed) return;
199
+ op.status = "active";
200
+ op.attempts++;
201
+ op.error = null;
202
+ op.errorCode = null;
203
+ op.nextRetryAt = null;
204
+ this._snapshot(op);
205
+ this.notify();
206
+ op.execute(op.abortController.signal).then(() => {
207
+ if (this._operations.get(id) !== op) return;
208
+ const operation = op.operation;
209
+ this._operations.delete(id);
210
+ this._snapshots.delete(id);
211
+ this.notify();
212
+ this.onConfirmed?.(id, operation);
213
+ }, (error) => {
214
+ if (this._operations.get(id) !== op) return;
215
+ if (isAbortError(error)) {
216
+ this._operations.delete(id);
217
+ this._snapshots.delete(id);
218
+ this.notify();
219
+ return;
220
+ }
221
+ const ctor = this.constructor;
222
+ const classified = classifyError(error);
223
+ if (this.isRetryable(error) && op.attempts < ctor.MAX_RETRIES) {
224
+ op.status = "retrying";
225
+ const delay = this._calculateDelay(op.attempts - 1);
226
+ op.nextRetryAt = Date.now() + delay;
227
+ op.error = classified.message;
228
+ op.errorCode = classified.code;
229
+ this._snapshot(op);
230
+ this.notify();
231
+ op.retryTimer = setTimeout(() => {
232
+ op.retryTimer = null;
233
+ this._process(id);
234
+ }, delay);
235
+ } else {
236
+ op.status = "failed";
237
+ op.error = classified.message;
238
+ op.errorCode = classified.code;
239
+ op.nextRetryAt = null;
240
+ this._snapshot(op);
241
+ this.notify();
242
+ this.onFailed?.(id, op.operation, error);
243
+ }
244
+ });
245
+ }
246
+ /** Computes retry backoff delay with jitter (Channel formula). @private */
247
+ _calculateDelay(attempt) {
248
+ const ctor = this.constructor;
249
+ const capped = Math.min(ctor.RETRY_BASE * Math.pow(ctor.RETRY_FACTOR, attempt), ctor.RETRY_MAX);
250
+ return Math.random() * capped;
251
+ }
287
252
  };
288
- //# sourceMappingURL=Pending.js.map
253
+ //#endregion
254
+ export { Pending };
255
+
256
+ //# sourceMappingURL=Pending.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Pending.js","sources":["../src/Pending.ts"],"sourcesContent":["import { classifyError, isAbortError } from './errors';\nimport type { AppError } from './errors';\nimport { Trackable } from './Trackable';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\n// ── Types ─────────────────────────────────────────────────────────\n\n/** Frozen snapshot of a single pending operation's state. */\nexport interface PendingOperation<Meta = unknown> {\n readonly status: 'active' | 'retrying' | 'failed';\n readonly operation: string;\n readonly attempts: number;\n readonly maxRetries: number;\n readonly error: string | null;\n readonly errorCode: AppError['code'] | null;\n readonly nextRetryAt: number | null;\n readonly createdAt: number;\n readonly meta: Meta | null;\n}\n\n/** A PendingOperation snapshot paired with its key, for iteration. */\nexport interface PendingEntry<K extends string | number, Meta = unknown>\n extends PendingOperation<Meta> {\n readonly id: K;\n}\n\n/** Mutable internal state for a pending operation. */\ninterface InternalOp<K, Meta> {\n id: K;\n operation: string;\n execute: (signal: AbortSignal) => Promise<void>;\n status: PendingOperation['status'];\n attempts: number;\n error: string | null;\n errorCode: AppError['code'] | null;\n nextRetryAt: number | null;\n createdAt: number;\n abortController: AbortController;\n retryTimer: ReturnType<typeof setTimeout> | null;\n meta: Meta | null;\n}\n\n// ── Pending ───────────────────────────────────────────────────────\n\n/**\n * Per-item operation queue with retry and status tracking.\n * Tracks operations by key with exponential backoff retry on transient errors.\n * Subscribable — auto-tracked when used as a ViewModel/Resource property.\n */\nexport class Pending<K extends string | number = string | number, Meta = unknown> extends Trackable {\n // ── Static config (Channel pattern — override via subclass) ──\n\n /** Maximum number of retry attempts before marking as failed. */\n static MAX_RETRIES = 5;\n /** Base delay (ms) for retry backoff. */\n static RETRY_BASE = 1000;\n /** Maximum delay cap (ms) for retry backoff. */\n static RETRY_MAX = 30000;\n /** Exponential backoff multiplier for retry delay. */\n static RETRY_FACTOR = 2;\n\n // ── Private state ──\n\n private _operations = new Map<K, InternalOp<K, Meta>>();\n private _snapshots = new Map<K, PendingOperation<Meta>>();\n private _entriesCache: readonly PendingEntry<K, Meta>[] | null = null;\n\n constructor() {\n super();\n }\n\n // ── Readable state (reactive — auto-tracked by ViewModel getters) ──\n\n /** Get the frozen status snapshot for an operation by ID, or null if not found. */\n getStatus(id: K): PendingOperation<Meta> | null {\n return this._snapshots.get(id) ?? null;\n }\n\n /** Whether an operation exists for the given ID. */\n has(id: K): boolean {\n return this._operations.has(id);\n }\n\n /** Number of operations (all statuses). */\n get count(): number {\n return this._operations.size;\n }\n\n /** Whether any operations are in-flight (active or retrying). */\n get hasPending(): boolean {\n for (const op of this._operations.values()) {\n if (op.status !== 'failed') return true;\n }\n return false;\n }\n\n /** Whether any operations are in a failed state. */\n get hasFailed(): boolean {\n for (const op of this._operations.values()) {\n if (op.status === 'failed') return true;\n }\n return false;\n }\n\n /** Number of operations in a failed state. */\n get failedCount(): number {\n let n = 0;\n for (const op of this._operations.values()) {\n if (op.status === 'failed') n++;\n }\n return n;\n }\n\n /** All operations as a frozen array of entries (id + snapshot). Cached until next mutation. */\n get entries(): readonly PendingEntry<K, Meta>[] {\n if (this._entriesCache === null) {\n const result: PendingEntry<K, Meta>[] = [];\n for (const [id, snapshot] of this._snapshots) {\n result.push(Object.freeze({ ...snapshot, id }));\n }\n this._entriesCache = Object.freeze(result) as readonly PendingEntry<K, Meta>[];\n }\n return this._entriesCache;\n }\n\n // ── Core API ──\n\n /**\n * Enqueue an operation for the given ID. Fire-and-forget (synchronous return).\n * If the same ID already has a pending operation, it is superseded (aborted).\n */\n enqueue(id: K, operation: string, execute: (signal: AbortSignal) => Promise<void>, meta?: Meta): void {\n if (this.disposed) {\n if (__DEV__) {\n console.warn('[mvc-kit] Pending.enqueue() called after dispose — ignored.');\n }\n return;\n }\n\n // Supersede existing operation for this ID\n const existing = this._operations.get(id);\n if (existing) {\n existing.abortController.abort();\n if (existing.retryTimer !== null) {\n clearTimeout(existing.retryTimer);\n }\n }\n\n const op: InternalOp<K, Meta> = {\n id,\n operation,\n execute,\n status: 'active',\n attempts: 0,\n error: null,\n errorCode: null,\n nextRetryAt: null,\n createdAt: Date.now(),\n abortController: new AbortController(),\n retryTimer: null,\n meta: meta ?? null,\n };\n\n this._operations.set(id, op);\n this._snapshot(op);\n this.notify();\n\n // Schedule processing via microtask (allows batching multiple enqueues)\n queueMicrotask(() => this._process(id));\n }\n\n // ── Controls ──\n\n /** Retry a failed operation. No-op if the operation is not in 'failed' status. */\n retry(id: K): void {\n if (this.disposed) {\n if (__DEV__) {\n console.warn('[mvc-kit] Pending.retry() called after dispose — ignored.');\n }\n return;\n }\n\n const op = this._operations.get(id);\n if (!op || op.status !== 'failed') return;\n\n op.attempts = 0;\n op.error = null;\n op.errorCode = null;\n op.nextRetryAt = null;\n op.abortController = new AbortController();\n this._process(id);\n }\n\n /** Retry all failed operations. */\n retryAll(): void {\n if (this.disposed) {\n if (__DEV__) {\n console.warn('[mvc-kit] Pending.retryAll() called after dispose — ignored.');\n }\n return;\n }\n\n const failedIds: K[] = [];\n for (const op of this._operations.values()) {\n if (op.status === 'failed') failedIds.push(op.id);\n }\n for (const id of failedIds) {\n this.retry(id);\n }\n }\n\n /** Cancel an in-flight operation by ID. Aborts the signal, clears timers, and removes it. */\n cancel(id: K): void {\n const op = this._operations.get(id);\n if (!op) return;\n\n op.abortController.abort();\n if (op.retryTimer !== null) {\n clearTimeout(op.retryTimer);\n }\n this._operations.delete(id);\n this._snapshots.delete(id);\n this.notify();\n }\n\n /** Cancel all operations. */\n cancelAll(): void {\n for (const op of this._operations.values()) {\n op.abortController.abort();\n if (op.retryTimer !== null) {\n clearTimeout(op.retryTimer);\n }\n }\n this._operations.clear();\n this._snapshots.clear();\n this.notify();\n }\n\n /** Remove a failed operation without retrying. No-op if the operation is not in 'failed' status. */\n dismiss(id: K): void {\n const op = this._operations.get(id);\n if (!op || op.status !== 'failed') return;\n\n this._operations.delete(id);\n this._snapshots.delete(id);\n this.notify();\n }\n\n /** Remove all failed operations without retrying. */\n dismissAll(): void {\n const failedIds: K[] = [];\n for (const op of this._operations.values()) {\n if (op.status === 'failed') failedIds.push(op.id);\n }\n if (failedIds.length === 0) return;\n for (const id of failedIds) {\n this._operations.delete(id);\n this._snapshots.delete(id);\n }\n this.notify();\n }\n\n // ── Hooks (overridable in subclass) ──\n\n /**\n * Determines whether an error is retryable. Override in a subclass to customize.\n * Default: retries on network, timeout, and server_error codes.\n * @protected\n */\n protected isRetryable(error: unknown): boolean {\n const code = classifyError(error).code;\n return code === 'network' || code === 'timeout' || code === 'server_error';\n }\n\n /** Called when an operation succeeds. Override in a subclass for side effects. @protected */\n protected onConfirmed?(id: K, operation: string): void;\n /** Called when an operation fails permanently. Override in a subclass for side effects. @protected */\n protected onFailed?(id: K, operation: string, error: unknown): void;\n\n // ── Lifecycle ──\n\n /** Dispose: cancels all operations, then runs Trackable cleanup. */\n dispose(): void {\n if (this.disposed) return;\n this.cancelAll();\n super.dispose();\n }\n\n // ── Notification override ──\n\n /** @internal Invalidates entries cache before notifying subscribers. */\n protected notify(): void {\n this._entriesCache = null;\n super.notify();\n }\n\n // ── Internals ──\n\n private _snapshot(op: InternalOp<K, Meta>): void {\n const ctor = this.constructor as typeof Pending;\n this._snapshots.set(op.id, Object.freeze({\n status: op.status,\n operation: op.operation,\n attempts: op.attempts,\n maxRetries: ctor.MAX_RETRIES,\n error: op.error,\n errorCode: op.errorCode,\n nextRetryAt: op.nextRetryAt,\n createdAt: op.createdAt,\n meta: op.meta,\n }));\n }\n\n private _process(id: K): void {\n const op = this._operations.get(id);\n if (!op || this.disposed) return;\n\n // Set active status\n op.status = 'active';\n op.attempts++;\n op.error = null;\n op.errorCode = null;\n op.nextRetryAt = null;\n this._snapshot(op);\n this.notify();\n\n op.execute(op.abortController.signal).then(\n () => {\n // Identity check: ignore if this op was superseded or cancelled\n if (this._operations.get(id) !== op) return;\n const operation = op.operation;\n this._operations.delete(id);\n this._snapshots.delete(id);\n this.notify();\n this.onConfirmed?.(id, operation);\n },\n (error: unknown) => {\n // Identity check: ignore if this op was superseded or cancelled\n if (this._operations.get(id) !== op) return;\n\n // AbortError — remove silently (cancel or supersede)\n if (isAbortError(error)) {\n this._operations.delete(id);\n this._snapshots.delete(id);\n this.notify();\n return;\n }\n\n const ctor = this.constructor as typeof Pending;\n const classified = classifyError(error);\n\n // Check if retryable and under max retries\n if (this.isRetryable(error) && op.attempts < ctor.MAX_RETRIES) {\n op.status = 'retrying';\n const delay = this._calculateDelay(op.attempts - 1);\n op.nextRetryAt = Date.now() + delay;\n op.error = classified.message;\n op.errorCode = classified.code;\n this._snapshot(op);\n this.notify();\n\n op.retryTimer = setTimeout(() => {\n op.retryTimer = null;\n this._process(id);\n }, delay);\n } else {\n // Non-retryable or max retries exceeded\n op.status = 'failed';\n op.error = classified.message;\n op.errorCode = classified.code;\n op.nextRetryAt = null;\n this._snapshot(op);\n this.notify();\n this.onFailed?.(id, op.operation, error);\n }\n },\n );\n }\n\n /** Computes retry backoff delay with jitter (Channel formula). @private */\n private _calculateDelay(attempt: number): number {\n const ctor = this.constructor as typeof Pending;\n const capped = Math.min(\n ctor.RETRY_BASE * Math.pow(ctor.RETRY_FACTOR, attempt),\n ctor.RETRY_MAX,\n );\n return Math.random() * capped;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Pending.js","names":[],"sources":["../src/Pending.ts"],"sourcesContent":["import { classifyError, isAbortError } from './errors';\nimport type { AppError } from './errors';\nimport { Trackable } from './Trackable';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\n// ── Types ─────────────────────────────────────────────────────────\n\n/** Frozen snapshot of a single pending operation's state. */\nexport interface PendingOperation<Meta = unknown> {\n readonly status: 'active' | 'retrying' | 'failed';\n readonly operation: string;\n readonly attempts: number;\n readonly maxRetries: number;\n readonly error: string | null;\n readonly errorCode: AppError['code'] | null;\n readonly nextRetryAt: number | null;\n readonly createdAt: number;\n readonly meta: Meta | null;\n}\n\n/** A PendingOperation snapshot paired with its key, for iteration. */\nexport interface PendingEntry<K extends string | number, Meta = unknown>\n extends PendingOperation<Meta> {\n readonly id: K;\n}\n\n/** Mutable internal state for a pending operation. */\ninterface InternalOp<K, Meta> {\n id: K;\n operation: string;\n execute: (signal: AbortSignal) => Promise<void>;\n status: PendingOperation['status'];\n attempts: number;\n error: string | null;\n errorCode: AppError['code'] | null;\n nextRetryAt: number | null;\n createdAt: number;\n abortController: AbortController;\n retryTimer: ReturnType<typeof setTimeout> | null;\n meta: Meta | null;\n}\n\n// ── Pending ───────────────────────────────────────────────────────\n\n/**\n * Per-item operation queue with retry and status tracking.\n * Tracks operations by key with exponential backoff retry on transient errors.\n * Subscribable — auto-tracked when used as a ViewModel/Resource property.\n */\nexport class Pending<K extends string | number = string | number, Meta = unknown> extends Trackable {\n // ── Static config (Channel pattern — override via subclass) ──\n\n /** Maximum number of retry attempts before marking as failed. */\n static MAX_RETRIES = 5;\n /** Base delay (ms) for retry backoff. */\n static RETRY_BASE = 1000;\n /** Maximum delay cap (ms) for retry backoff. */\n static RETRY_MAX = 30000;\n /** Exponential backoff multiplier for retry delay. */\n static RETRY_FACTOR = 2;\n\n // ── Private state ──\n\n private _operations = new Map<K, InternalOp<K, Meta>>();\n private _snapshots = new Map<K, PendingOperation<Meta>>();\n private _entriesCache: readonly PendingEntry<K, Meta>[] | null = null;\n\n constructor() {\n super();\n }\n\n // ── Readable state (reactive — auto-tracked by ViewModel getters) ──\n\n /** Get the frozen status snapshot for an operation by ID, or null if not found. */\n getStatus(id: K): PendingOperation<Meta> | null {\n return this._snapshots.get(id) ?? null;\n }\n\n /** Whether an operation exists for the given ID. */\n has(id: K): boolean {\n return this._operations.has(id);\n }\n\n /** Number of operations (all statuses). */\n get count(): number {\n return this._operations.size;\n }\n\n /** Whether any operations are in-flight (active or retrying). */\n get hasPending(): boolean {\n for (const op of this._operations.values()) {\n if (op.status !== 'failed') return true;\n }\n return false;\n }\n\n /** Whether any operations are in a failed state. */\n get hasFailed(): boolean {\n for (const op of this._operations.values()) {\n if (op.status === 'failed') return true;\n }\n return false;\n }\n\n /** Number of operations in a failed state. */\n get failedCount(): number {\n let n = 0;\n for (const op of this._operations.values()) {\n if (op.status === 'failed') n++;\n }\n return n;\n }\n\n /** All operations as a frozen array of entries (id + snapshot). Cached until next mutation. */\n get entries(): readonly PendingEntry<K, Meta>[] {\n if (this._entriesCache === null) {\n const result: PendingEntry<K, Meta>[] = [];\n for (const [id, snapshot] of this._snapshots) {\n result.push(Object.freeze({ ...snapshot, id }));\n }\n this._entriesCache = Object.freeze(result) as readonly PendingEntry<K, Meta>[];\n }\n return this._entriesCache;\n }\n\n // ── Core API ──\n\n /**\n * Enqueue an operation for the given ID. Fire-and-forget (synchronous return).\n * If the same ID already has a pending operation, it is superseded (aborted).\n */\n enqueue(id: K, operation: string, execute: (signal: AbortSignal) => Promise<void>, meta?: Meta): void {\n if (this.disposed) {\n if (__DEV__) {\n console.warn('[mvc-kit] Pending.enqueue() called after dispose — ignored.');\n }\n return;\n }\n\n // Supersede existing operation for this ID\n const existing = this._operations.get(id);\n if (existing) {\n existing.abortController.abort();\n if (existing.retryTimer !== null) {\n clearTimeout(existing.retryTimer);\n }\n }\n\n const op: InternalOp<K, Meta> = {\n id,\n operation,\n execute,\n status: 'active',\n attempts: 0,\n error: null,\n errorCode: null,\n nextRetryAt: null,\n createdAt: Date.now(),\n abortController: new AbortController(),\n retryTimer: null,\n meta: meta ?? null,\n };\n\n this._operations.set(id, op);\n this._snapshot(op);\n this.notify();\n\n // Schedule processing via microtask (allows batching multiple enqueues)\n queueMicrotask(() => this._process(id));\n }\n\n // ── Controls ──\n\n /** Retry a failed operation. No-op if the operation is not in 'failed' status. */\n retry(id: K): void {\n if (this.disposed) {\n if (__DEV__) {\n console.warn('[mvc-kit] Pending.retry() called after dispose — ignored.');\n }\n return;\n }\n\n const op = this._operations.get(id);\n if (!op || op.status !== 'failed') return;\n\n op.attempts = 0;\n op.error = null;\n op.errorCode = null;\n op.nextRetryAt = null;\n op.abortController = new AbortController();\n this._process(id);\n }\n\n /** Retry all failed operations. */\n retryAll(): void {\n if (this.disposed) {\n if (__DEV__) {\n console.warn('[mvc-kit] Pending.retryAll() called after dispose — ignored.');\n }\n return;\n }\n\n const failedIds: K[] = [];\n for (const op of this._operations.values()) {\n if (op.status === 'failed') failedIds.push(op.id);\n }\n for (const id of failedIds) {\n this.retry(id);\n }\n }\n\n /** Cancel an in-flight operation by ID. Aborts the signal, clears timers, and removes it. */\n cancel(id: K): void {\n const op = this._operations.get(id);\n if (!op) return;\n\n op.abortController.abort();\n if (op.retryTimer !== null) {\n clearTimeout(op.retryTimer);\n }\n this._operations.delete(id);\n this._snapshots.delete(id);\n this.notify();\n }\n\n /** Cancel all operations. */\n cancelAll(): void {\n for (const op of this._operations.values()) {\n op.abortController.abort();\n if (op.retryTimer !== null) {\n clearTimeout(op.retryTimer);\n }\n }\n this._operations.clear();\n this._snapshots.clear();\n this.notify();\n }\n\n /** Remove a failed operation without retrying. No-op if the operation is not in 'failed' status. */\n dismiss(id: K): void {\n const op = this._operations.get(id);\n if (!op || op.status !== 'failed') return;\n\n this._operations.delete(id);\n this._snapshots.delete(id);\n this.notify();\n }\n\n /** Remove all failed operations without retrying. */\n dismissAll(): void {\n const failedIds: K[] = [];\n for (const op of this._operations.values()) {\n if (op.status === 'failed') failedIds.push(op.id);\n }\n if (failedIds.length === 0) return;\n for (const id of failedIds) {\n this._operations.delete(id);\n this._snapshots.delete(id);\n }\n this.notify();\n }\n\n // ── Hooks (overridable in subclass) ──\n\n /**\n * Determines whether an error is retryable. Override in a subclass to customize.\n * Default: retries on network, timeout, and server_error codes.\n * @protected\n */\n protected isRetryable(error: unknown): boolean {\n const code = classifyError(error).code;\n return code === 'network' || code === 'timeout' || code === 'server_error';\n }\n\n /** Called when an operation succeeds. Override in a subclass for side effects. @protected */\n protected onConfirmed?(id: K, operation: string): void;\n /** Called when an operation fails permanently. Override in a subclass for side effects. @protected */\n protected onFailed?(id: K, operation: string, error: unknown): void;\n\n // ── Lifecycle ──\n\n /** Dispose: cancels all operations, then runs Trackable cleanup. */\n dispose(): void {\n if (this.disposed) return;\n this.cancelAll();\n super.dispose();\n }\n\n // ── Notification override ──\n\n /** @internal Invalidates entries cache before notifying subscribers. */\n protected notify(): void {\n this._entriesCache = null;\n super.notify();\n }\n\n // ── Internals ──\n\n private _snapshot(op: InternalOp<K, Meta>): void {\n const ctor = this.constructor as typeof Pending;\n this._snapshots.set(op.id, Object.freeze({\n status: op.status,\n operation: op.operation,\n attempts: op.attempts,\n maxRetries: ctor.MAX_RETRIES,\n error: op.error,\n errorCode: op.errorCode,\n nextRetryAt: op.nextRetryAt,\n createdAt: op.createdAt,\n meta: op.meta,\n }));\n }\n\n private _process(id: K): void {\n const op = this._operations.get(id);\n if (!op || this.disposed) return;\n\n // Set active status\n op.status = 'active';\n op.attempts++;\n op.error = null;\n op.errorCode = null;\n op.nextRetryAt = null;\n this._snapshot(op);\n this.notify();\n\n op.execute(op.abortController.signal).then(\n () => {\n // Identity check: ignore if this op was superseded or cancelled\n if (this._operations.get(id) !== op) return;\n const operation = op.operation;\n this._operations.delete(id);\n this._snapshots.delete(id);\n this.notify();\n this.onConfirmed?.(id, operation);\n },\n (error: unknown) => {\n // Identity check: ignore if this op was superseded or cancelled\n if (this._operations.get(id) !== op) return;\n\n // AbortError — remove silently (cancel or supersede)\n if (isAbortError(error)) {\n this._operations.delete(id);\n this._snapshots.delete(id);\n this.notify();\n return;\n }\n\n const ctor = this.constructor as typeof Pending;\n const classified = classifyError(error);\n\n // Check if retryable and under max retries\n if (this.isRetryable(error) && op.attempts < ctor.MAX_RETRIES) {\n op.status = 'retrying';\n const delay = this._calculateDelay(op.attempts - 1);\n op.nextRetryAt = Date.now() + delay;\n op.error = classified.message;\n op.errorCode = classified.code;\n this._snapshot(op);\n this.notify();\n\n op.retryTimer = setTimeout(() => {\n op.retryTimer = null;\n this._process(id);\n }, delay);\n } else {\n // Non-retryable or max retries exceeded\n op.status = 'failed';\n op.error = classified.message;\n op.errorCode = classified.code;\n op.nextRetryAt = null;\n this._snapshot(op);\n this.notify();\n this.onFailed?.(id, op.operation, error);\n }\n },\n );\n }\n\n /** Computes retry backoff delay with jitter (Channel formula). @private */\n private _calculateDelay(attempt: number): number {\n const ctor = this.constructor as typeof Pending;\n const capped = Math.min(\n ctor.RETRY_BASE * Math.pow(ctor.RETRY_FACTOR, attempt),\n ctor.RETRY_MAX,\n );\n return Math.random() * capped;\n }\n}\n"],"mappings":"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"}