gracefulerrors 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +199 -0
- package/dist/adapters/sonner.cjs +126 -0
- package/dist/adapters/sonner.cjs.map +1 -0
- package/dist/adapters/sonner.d.cts +6 -0
- package/dist/adapters/sonner.d.ts +6 -0
- package/dist/adapters/sonner.js +121 -0
- package/dist/adapters/sonner.js.map +1 -0
- package/dist/index.cjs +486 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +481 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.cjs +169 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.cts +14 -0
- package/dist/internal.d.ts +14 -0
- package/dist/internal.js +166 -0
- package/dist/internal.js.map +1 -0
- package/dist/react.cjs +71 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +35 -0
- package/dist/react.d.ts +35 -0
- package/dist/react.js +61 -0
- package/dist/react.js.map +1 -0
- package/dist/types-CsPmpcbL.d.cts +149 -0
- package/dist/types-CsPmpcbL.d.ts +149 -0
- package/package.json +80 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
// src/normalizer.ts
|
|
2
|
+
function mapNativeError(raw) {
|
|
3
|
+
if (raw instanceof TypeError) {
|
|
4
|
+
return { code: "NETWORK_ERROR", message: raw.message, raw };
|
|
5
|
+
}
|
|
6
|
+
if (raw instanceof DOMException && raw.name === "NetworkError") {
|
|
7
|
+
return { code: "NETWORK_ERROR", message: raw.message, raw };
|
|
8
|
+
}
|
|
9
|
+
const code = "GRACEFULERRORS_UNKNOWN";
|
|
10
|
+
return { code, message: raw.message, raw };
|
|
11
|
+
}
|
|
12
|
+
function isAxiosError(raw) {
|
|
13
|
+
if (raw == null || typeof raw !== "object") return false;
|
|
14
|
+
const r = raw;
|
|
15
|
+
return r["isAxiosError"] === true || r["response"] !== void 0 && r["config"] !== void 0;
|
|
16
|
+
}
|
|
17
|
+
function isStructuredError(raw) {
|
|
18
|
+
if (raw == null || typeof raw !== "object") return false;
|
|
19
|
+
const r = raw;
|
|
20
|
+
return typeof r["code"] === "string";
|
|
21
|
+
}
|
|
22
|
+
function builtInNormalizer(raw, _current) {
|
|
23
|
+
if (raw instanceof Error && raw.name === "AbortError") {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
if (raw instanceof Response && !raw.ok) {
|
|
27
|
+
const status = raw.status;
|
|
28
|
+
const code = `HTTP_${status}`;
|
|
29
|
+
return { code, status, message: raw.statusText, raw };
|
|
30
|
+
}
|
|
31
|
+
if (isAxiosError(raw)) {
|
|
32
|
+
const response = raw.response;
|
|
33
|
+
const status = response?.status;
|
|
34
|
+
const data = response?.data;
|
|
35
|
+
let code = status != null ? `HTTP_${status}` : "GRACEFULERRORS_UNKNOWN";
|
|
36
|
+
let message;
|
|
37
|
+
if (data != null && typeof data === "object") {
|
|
38
|
+
const d = data;
|
|
39
|
+
if (typeof d["code"] === "string") code = d["code"];
|
|
40
|
+
if (typeof d["message"] === "string") message = d["message"];
|
|
41
|
+
}
|
|
42
|
+
return { code, status, message, raw };
|
|
43
|
+
}
|
|
44
|
+
if (isStructuredError(raw)) {
|
|
45
|
+
const { code, status, message, context } = raw;
|
|
46
|
+
return { code, status, message, context, raw };
|
|
47
|
+
}
|
|
48
|
+
if (raw instanceof Error) {
|
|
49
|
+
return mapNativeError(raw);
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
function mergeAppError(current, partial) {
|
|
54
|
+
if (current === null) {
|
|
55
|
+
return partial;
|
|
56
|
+
}
|
|
57
|
+
const merged = { ...current };
|
|
58
|
+
if (partial.code !== void 0) merged.code = partial.code;
|
|
59
|
+
if (partial.status !== void 0) merged.status = partial.status;
|
|
60
|
+
if (partial.message !== void 0) merged.message = partial.message;
|
|
61
|
+
if (partial.context !== void 0) {
|
|
62
|
+
merged.context = { ...current.context, ...partial.context };
|
|
63
|
+
}
|
|
64
|
+
return merged;
|
|
65
|
+
}
|
|
66
|
+
function runNormalizerPipeline(raw, customNormalizers, builtIn, onError) {
|
|
67
|
+
let current = null;
|
|
68
|
+
for (const normalizer of customNormalizers) {
|
|
69
|
+
try {
|
|
70
|
+
const result = normalizer(raw, current);
|
|
71
|
+
if (result !== null) {
|
|
72
|
+
current = mergeAppError(current, result);
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
onError?.(err);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const result = builtIn(raw, current);
|
|
80
|
+
if (result !== null) {
|
|
81
|
+
current = mergeAppError(current, result);
|
|
82
|
+
}
|
|
83
|
+
} catch (err) {
|
|
84
|
+
onError?.(err);
|
|
85
|
+
}
|
|
86
|
+
if (current === null) {
|
|
87
|
+
return { code: "GRACEFULERRORS_UNHANDLED", raw };
|
|
88
|
+
}
|
|
89
|
+
if (!current.code) {
|
|
90
|
+
return { ...current, code: "GRACEFULERRORS_UNKNOWN" };
|
|
91
|
+
}
|
|
92
|
+
return current;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/state-manager.ts
|
|
96
|
+
function createStateManager(config) {
|
|
97
|
+
const maxConcurrent = config.maxConcurrent ?? 3;
|
|
98
|
+
const maxQueue = config.maxQueue;
|
|
99
|
+
const dedupeWindow = config.dedupeWindow ?? 300;
|
|
100
|
+
const { onDropped } = config;
|
|
101
|
+
const activeSlots = /* @__PURE__ */ new Map();
|
|
102
|
+
const queue = [];
|
|
103
|
+
const dedupeMap = /* @__PURE__ */ new Map();
|
|
104
|
+
const ttlTimers = /* @__PURE__ */ new Map();
|
|
105
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
106
|
+
function notify(event) {
|
|
107
|
+
for (const listener of listeners) {
|
|
108
|
+
try {
|
|
109
|
+
listener(event);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
if (process.env["NODE_ENV"] !== "production") {
|
|
112
|
+
console.error("[gracefulerrors] StateManager listener threw:", err);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function promoteFromQueue() {
|
|
118
|
+
if (queue.length > 0 && activeSlots.size < maxConcurrent) {
|
|
119
|
+
const next = queue.shift();
|
|
120
|
+
activateSlot(next, next._pendingAction);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function activateSlot(slot, action) {
|
|
124
|
+
slot.state = "ACTIVE";
|
|
125
|
+
activeSlots.set(slot.fingerprint, slot);
|
|
126
|
+
const ttl = slot.ttl;
|
|
127
|
+
if (ttl != null && ttl > 0) {
|
|
128
|
+
const timer = setTimeout(() => {
|
|
129
|
+
if (activeSlots.has(slot.fingerprint)) {
|
|
130
|
+
activeSlots.delete(slot.fingerprint);
|
|
131
|
+
ttlTimers.delete(slot.fingerprint);
|
|
132
|
+
onDropped?.(slot.error, "ttl_expired");
|
|
133
|
+
promoteFromQueue();
|
|
134
|
+
}
|
|
135
|
+
}, ttl);
|
|
136
|
+
ttlTimers.set(slot.fingerprint, timer);
|
|
137
|
+
}
|
|
138
|
+
notify({ type: "ERROR_ADDED", error: slot.error, action });
|
|
139
|
+
}
|
|
140
|
+
function canHandle(fingerprint) {
|
|
141
|
+
const lastSeen = dedupeMap.get(fingerprint);
|
|
142
|
+
if (lastSeen === void 0) return true;
|
|
143
|
+
return Date.now() - lastSeen >= dedupeWindow;
|
|
144
|
+
}
|
|
145
|
+
function enqueue(slot) {
|
|
146
|
+
if (!canHandle(slot.fingerprint)) {
|
|
147
|
+
onDropped?.(slot.error, "dedupe");
|
|
148
|
+
return "rejected";
|
|
149
|
+
}
|
|
150
|
+
dedupeMap.set(slot.fingerprint, Date.now());
|
|
151
|
+
if (activeSlots.size < maxConcurrent) {
|
|
152
|
+
activateSlot(slot, slot._pendingAction);
|
|
153
|
+
return "active";
|
|
154
|
+
}
|
|
155
|
+
if (maxQueue !== void 0 && queue.length >= maxQueue) {
|
|
156
|
+
onDropped?.(slot.error, "queue_overflow");
|
|
157
|
+
return "rejected";
|
|
158
|
+
}
|
|
159
|
+
slot.state = "QUEUED";
|
|
160
|
+
queue.push(slot);
|
|
161
|
+
return "queued";
|
|
162
|
+
}
|
|
163
|
+
function release(code) {
|
|
164
|
+
let targetFingerprint;
|
|
165
|
+
for (const [fingerprint, slot] of activeSlots) {
|
|
166
|
+
if (slot.error.code === code) {
|
|
167
|
+
targetFingerprint = fingerprint;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (targetFingerprint !== void 0) {
|
|
172
|
+
const slot = activeSlots.get(targetFingerprint);
|
|
173
|
+
slot.state = "EXPIRED";
|
|
174
|
+
activeSlots.delete(targetFingerprint);
|
|
175
|
+
const timer = ttlTimers.get(targetFingerprint);
|
|
176
|
+
if (timer !== void 0) {
|
|
177
|
+
clearTimeout(timer);
|
|
178
|
+
ttlTimers.delete(targetFingerprint);
|
|
179
|
+
}
|
|
180
|
+
notify({ type: "ERROR_CLEARED", code });
|
|
181
|
+
promoteFromQueue();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const queueIdx = queue.findIndex((slot) => slot.error.code === code);
|
|
185
|
+
if (queueIdx !== -1) {
|
|
186
|
+
const slot = queue[queueIdx];
|
|
187
|
+
slot.state = "EXPIRED";
|
|
188
|
+
queue.splice(queueIdx, 1);
|
|
189
|
+
notify({ type: "ERROR_CLEARED", code });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function getActiveSlots() {
|
|
193
|
+
return Array.from(activeSlots.values());
|
|
194
|
+
}
|
|
195
|
+
function getQueueLength() {
|
|
196
|
+
return queue.length;
|
|
197
|
+
}
|
|
198
|
+
function clearAll() {
|
|
199
|
+
for (const timer of ttlTimers.values()) {
|
|
200
|
+
clearTimeout(timer);
|
|
201
|
+
}
|
|
202
|
+
ttlTimers.clear();
|
|
203
|
+
activeSlots.clear();
|
|
204
|
+
queue.length = 0;
|
|
205
|
+
notify({ type: "ALL_CLEARED" });
|
|
206
|
+
}
|
|
207
|
+
function subscribe(listener) {
|
|
208
|
+
listeners.add(listener);
|
|
209
|
+
return () => {
|
|
210
|
+
listeners.delete(listener);
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return { canHandle, enqueue, release, getActiveSlots, getQueueLength, clearAll, subscribe };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/registry.ts
|
|
217
|
+
function lookupEntry(registry, code) {
|
|
218
|
+
return registry[code];
|
|
219
|
+
}
|
|
220
|
+
function mergeRegistries(base, override) {
|
|
221
|
+
return { ...base, ...override };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/router.ts
|
|
225
|
+
function createUIRouter() {
|
|
226
|
+
function route(error, registry, config) {
|
|
227
|
+
const entry = lookupEntry(registry, error.code);
|
|
228
|
+
if (entry == null && config.requireRegistry) {
|
|
229
|
+
throw new Error(`[gracefulerrors] Registry entry required for code: ${error.code}`);
|
|
230
|
+
}
|
|
231
|
+
const routingContext = config.routingContext ?? {
|
|
232
|
+
activeCount: 0,
|
|
233
|
+
queueLength: 0
|
|
234
|
+
};
|
|
235
|
+
let strategyResult;
|
|
236
|
+
if (config.routingStrategy) {
|
|
237
|
+
try {
|
|
238
|
+
strategyResult = config.routingStrategy(error, entry, routingContext);
|
|
239
|
+
} catch (err) {
|
|
240
|
+
if (process.env["NODE_ENV"] !== "production") {
|
|
241
|
+
console.error("[gracefulerrors] routingStrategy threw:", err);
|
|
242
|
+
}
|
|
243
|
+
strategyResult = null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (strategyResult != null) {
|
|
247
|
+
return strategyResult;
|
|
248
|
+
}
|
|
249
|
+
if (entry != null) {
|
|
250
|
+
return entry.ui;
|
|
251
|
+
}
|
|
252
|
+
const allowFallback = config.allowFallback !== false;
|
|
253
|
+
if (allowFallback && config.fallback) {
|
|
254
|
+
return config.fallback.ui;
|
|
255
|
+
}
|
|
256
|
+
return "toast";
|
|
257
|
+
}
|
|
258
|
+
return { route };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/engine.ts
|
|
262
|
+
function isSuppressionDecision(result) {
|
|
263
|
+
return result !== null && typeof result === "object" && "suppress" in result && result.suppress === true;
|
|
264
|
+
}
|
|
265
|
+
function safeCall(fn, ...args) {
|
|
266
|
+
if (!fn) return;
|
|
267
|
+
try {
|
|
268
|
+
fn(...args);
|
|
269
|
+
} catch (err) {
|
|
270
|
+
if (process.env["NODE_ENV"] !== "production") {
|
|
271
|
+
console.error("[gracefulerrors] Hook threw:", err);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function safeTransform(fn, error) {
|
|
276
|
+
if (!fn) return null;
|
|
277
|
+
try {
|
|
278
|
+
return fn(error, { raw: error.raw });
|
|
279
|
+
} catch (err) {
|
|
280
|
+
if (process.env["NODE_ENV"] !== "production") {
|
|
281
|
+
console.error("[gracefulerrors] transform threw:", err);
|
|
282
|
+
}
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function defaultFingerprint(error) {
|
|
287
|
+
return `${error.code}:${error.status ?? ""}:${error.context?.field ?? ""}`;
|
|
288
|
+
}
|
|
289
|
+
function buildFallbackEntry(action, config) {
|
|
290
|
+
return { ui: action, message: config.fallback?.message ?? "An error occurred" };
|
|
291
|
+
}
|
|
292
|
+
function debugTrace(debug, trace) {
|
|
293
|
+
const shouldTrace = debug === true || typeof debug === "object" && debug !== null && debug.trace === true;
|
|
294
|
+
if (shouldTrace && process.env["NODE_ENV"] !== "production") {
|
|
295
|
+
console.log("[gracefulerrors trace]", trace);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function noOpNormalizer(_raw, current) {
|
|
299
|
+
return current;
|
|
300
|
+
}
|
|
301
|
+
function createErrorEngine(config) {
|
|
302
|
+
let customNormalizers;
|
|
303
|
+
let resolvedBuiltIn;
|
|
304
|
+
if (config.normalizer && config.normalizers) {
|
|
305
|
+
if (process.env["NODE_ENV"] === "development") {
|
|
306
|
+
console.warn(
|
|
307
|
+
"[gracefulerrors] Both normalizer and normalizers provided. normalizer takes precedence and normalizers is ignored."
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
customNormalizers = [config.normalizer];
|
|
311
|
+
resolvedBuiltIn = noOpNormalizer;
|
|
312
|
+
} else if (config.normalizer) {
|
|
313
|
+
customNormalizers = [config.normalizer];
|
|
314
|
+
resolvedBuiltIn = noOpNormalizer;
|
|
315
|
+
} else if (config.normalizers) {
|
|
316
|
+
customNormalizers = config.normalizers;
|
|
317
|
+
resolvedBuiltIn = builtInNormalizer;
|
|
318
|
+
} else {
|
|
319
|
+
customNormalizers = [];
|
|
320
|
+
resolvedBuiltIn = builtInNormalizer;
|
|
321
|
+
}
|
|
322
|
+
const stateManager = createStateManager({
|
|
323
|
+
maxConcurrent: config.maxConcurrent,
|
|
324
|
+
maxQueue: config.maxQueue,
|
|
325
|
+
dedupeWindow: config.dedupeWindow,
|
|
326
|
+
onDropped: config.onDropped
|
|
327
|
+
});
|
|
328
|
+
const router = createUIRouter();
|
|
329
|
+
const aggregationMap = /* @__PURE__ */ new Map();
|
|
330
|
+
function handle(raw) {
|
|
331
|
+
safeCall(config.onError, raw);
|
|
332
|
+
const normalized = runNormalizerPipeline(
|
|
333
|
+
raw,
|
|
334
|
+
customNormalizers,
|
|
335
|
+
resolvedBuiltIn,
|
|
336
|
+
config.onError
|
|
337
|
+
);
|
|
338
|
+
safeCall(config.onNormalized, normalized);
|
|
339
|
+
if (config.onErrorAsync) {
|
|
340
|
+
Promise.resolve(config.onErrorAsync(normalized)).catch((err) => {
|
|
341
|
+
if (process.env["NODE_ENV"] === "development") {
|
|
342
|
+
console.error("[gracefulerrors] onErrorAsync rejected:", err);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
const transformResult = safeTransform(config.transform, normalized);
|
|
347
|
+
let current = normalized;
|
|
348
|
+
if (transformResult !== null) {
|
|
349
|
+
if (isSuppressionDecision(transformResult)) {
|
|
350
|
+
safeCall(config.onSuppressed, normalized, transformResult.reason);
|
|
351
|
+
return { handled: false, error: normalized, uiAction: null };
|
|
352
|
+
}
|
|
353
|
+
current = transformResult;
|
|
354
|
+
}
|
|
355
|
+
const fingerprint = config.fingerprint ? config.fingerprint(current) : defaultFingerprint(current);
|
|
356
|
+
const entry = lookupEntry(config.registry, current.code);
|
|
357
|
+
if (!entry && config.requireRegistry) {
|
|
358
|
+
safeCall(config.onFallback, current);
|
|
359
|
+
throw new Error(`[gracefulerrors] Registry entry required for code: ${current.code}`);
|
|
360
|
+
}
|
|
361
|
+
const routingContext = {
|
|
362
|
+
activeCount: stateManager.getActiveSlots().length,
|
|
363
|
+
queueLength: stateManager.getQueueLength()
|
|
364
|
+
};
|
|
365
|
+
const action = router.route(
|
|
366
|
+
current,
|
|
367
|
+
config.registry,
|
|
368
|
+
{
|
|
369
|
+
fallback: config.fallback,
|
|
370
|
+
requireRegistry: config.requireRegistry,
|
|
371
|
+
allowFallback: config.allowFallback,
|
|
372
|
+
routingStrategy: config.routingStrategy,
|
|
373
|
+
routingContext
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
if (!entry && !config.requireRegistry) {
|
|
377
|
+
safeCall(config.onFallback, current);
|
|
378
|
+
}
|
|
379
|
+
safeCall(config.onRouted, current, action);
|
|
380
|
+
const aggConfig = config.aggregation;
|
|
381
|
+
if (aggConfig) {
|
|
382
|
+
const aggEnabled = aggConfig === true || typeof aggConfig === "object" && aggConfig.enabled;
|
|
383
|
+
if (aggEnabled) {
|
|
384
|
+
const aggWindow = typeof aggConfig === "object" && aggConfig.window != null ? aggConfig.window : 300;
|
|
385
|
+
const aggKey = action;
|
|
386
|
+
const lastAgg = aggregationMap.get(aggKey);
|
|
387
|
+
const now = Date.now();
|
|
388
|
+
if (lastAgg !== void 0 && now - lastAgg < aggWindow) {
|
|
389
|
+
return { handled: false, error: current, uiAction: null };
|
|
390
|
+
}
|
|
391
|
+
aggregationMap.set(aggKey, now);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const slot = {
|
|
395
|
+
error: current,
|
|
396
|
+
state: "ACTIVE",
|
|
397
|
+
fingerprint,
|
|
398
|
+
ttl: entry?.ttl,
|
|
399
|
+
_pendingAction: action
|
|
400
|
+
};
|
|
401
|
+
const placement = stateManager.enqueue(slot);
|
|
402
|
+
if (placement === "rejected") {
|
|
403
|
+
return { handled: false, error: current, uiAction: null };
|
|
404
|
+
}
|
|
405
|
+
if (config.renderer && action !== "silent" && action !== "inline") {
|
|
406
|
+
const intent = {
|
|
407
|
+
ui: action,
|
|
408
|
+
error: current,
|
|
409
|
+
entry: entry ?? buildFallbackEntry(action, config)
|
|
410
|
+
};
|
|
411
|
+
let onDismiss;
|
|
412
|
+
if (action === "modal") {
|
|
413
|
+
let dismissTimeoutId;
|
|
414
|
+
if (config.modalDismissTimeoutMs != null && process.env["NODE_ENV"] !== "production") {
|
|
415
|
+
dismissTimeoutId = setTimeout(() => {
|
|
416
|
+
console.warn(
|
|
417
|
+
`[gracefulerrors] Modal for "${String(current.code)}" was not dismissed within ${config.modalDismissTimeoutMs}ms. Ensure your adapter calls onDismiss().`
|
|
418
|
+
);
|
|
419
|
+
}, config.modalDismissTimeoutMs);
|
|
420
|
+
}
|
|
421
|
+
onDismiss = () => {
|
|
422
|
+
if (dismissTimeoutId !== void 0) clearTimeout(dismissTimeoutId);
|
|
423
|
+
stateManager.release(current.code);
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
config.renderer.render(intent, { onDismiss });
|
|
427
|
+
}
|
|
428
|
+
debugTrace(config.debug, { raw, normalized: current, action, entry, placement });
|
|
429
|
+
return {
|
|
430
|
+
handled: true,
|
|
431
|
+
error: current,
|
|
432
|
+
uiAction: action === "silent" ? null : action
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
function clear(code) {
|
|
436
|
+
stateManager.release(code);
|
|
437
|
+
config.renderer?.clear(code);
|
|
438
|
+
}
|
|
439
|
+
function clearAll() {
|
|
440
|
+
stateManager.clearAll();
|
|
441
|
+
config.renderer?.clearAll();
|
|
442
|
+
}
|
|
443
|
+
function subscribe(listener) {
|
|
444
|
+
return stateManager.subscribe(listener);
|
|
445
|
+
}
|
|
446
|
+
return { handle, clear, clearAll, subscribe };
|
|
447
|
+
}
|
|
448
|
+
function createFetch(engine, options = {}) {
|
|
449
|
+
const mode = options.mode ?? "throw";
|
|
450
|
+
return async function(input, init) {
|
|
451
|
+
try {
|
|
452
|
+
const response = await fetch(input, init);
|
|
453
|
+
if (!response.ok) {
|
|
454
|
+
if (mode === "silent") return response;
|
|
455
|
+
let errorPayload = response;
|
|
456
|
+
try {
|
|
457
|
+
const body = await response.clone().json();
|
|
458
|
+
if (body != null && typeof body === "object") {
|
|
459
|
+
errorPayload = { status: response.status, ...body };
|
|
460
|
+
}
|
|
461
|
+
} catch {
|
|
462
|
+
}
|
|
463
|
+
engine.handle(errorPayload);
|
|
464
|
+
if (mode === "handle") return void 0;
|
|
465
|
+
throw response;
|
|
466
|
+
}
|
|
467
|
+
return response;
|
|
468
|
+
} catch (error) {
|
|
469
|
+
if (error != null && error.name === "AbortError") throw error;
|
|
470
|
+
if (error instanceof Response) throw error;
|
|
471
|
+
if (mode === "silent") throw error;
|
|
472
|
+
engine.handle(error);
|
|
473
|
+
if (mode === "handle") return void 0;
|
|
474
|
+
throw error;
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export { builtInNormalizer, createErrorEngine, createFetch, mergeRegistries };
|
|
480
|
+
//# sourceMappingURL=index.js.map
|
|
481
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/normalizer.ts","../src/state-manager.ts","../src/registry.ts","../src/router.ts","../src/engine.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { AppError, Normalizer, SystemErrorCode } from './types'\n\n// Native JS error → stable code mapping\nfunction mapNativeError(raw: Error): AppError {\n if (raw instanceof TypeError) {\n return { code: 'NETWORK_ERROR', message: raw.message, raw }\n }\n if (raw instanceof DOMException && raw.name === 'NetworkError') {\n return { code: 'NETWORK_ERROR', message: raw.message, raw }\n }\n const code: SystemErrorCode = 'GRACEFULERRORS_UNKNOWN'\n return { code, message: raw.message, raw }\n}\n\n// Detect Axios-like error\nfunction isAxiosError(raw: unknown): raw is {\n isAxiosError?: boolean\n response?: { status?: number; data?: unknown }\n config?: unknown\n message?: string\n} {\n if (raw == null || typeof raw !== 'object') return false\n const r = raw as Record<string, unknown>\n return r['isAxiosError'] === true || (r['response'] !== undefined && r['config'] !== undefined)\n}\n\n// Detect structured custom error\nfunction isStructuredError(raw: unknown): raw is { code: string; status?: number; message?: string; context?: Record<string, unknown> } {\n if (raw == null || typeof raw !== 'object') return false\n const r = raw as Record<string, unknown>\n return typeof r['code'] === 'string'\n}\n\nexport function builtInNormalizer(raw: unknown, _current: AppError | null): AppError | null {\n // AbortError — pass-through, never an AppError\n if (raw instanceof Error && raw.name === 'AbortError') {\n return null\n }\n\n // HTTP Response (4xx/5xx)\n if (raw instanceof Response && !raw.ok) {\n const status = raw.status\n // Body may have been consumed — use sync fallback\n const code = `HTTP_${status}`\n return { code, status, message: raw.statusText, raw }\n }\n\n // Axios error\n if (isAxiosError(raw)) {\n const response = raw.response\n const status = response?.status\n const data = response?.data\n let code: string = status != null ? `HTTP_${status}` : 'GRACEFULERRORS_UNKNOWN'\n let message: string | undefined\n\n if (data != null && typeof data === 'object') {\n const d = data as Record<string, unknown>\n if (typeof d['code'] === 'string') code = d['code']\n if (typeof d['message'] === 'string') message = d['message']\n }\n\n return { code, status, message, raw }\n }\n\n // Structured custom error — plain object with { code: string }\n if (isStructuredError(raw)) {\n const { code, status, message, context } = raw\n return { code, status, message, context, raw }\n }\n\n // Native JS errors\n if (raw instanceof Error) {\n return mapNativeError(raw)\n }\n\n // Unknown\n return null\n}\n\nexport function mergeAppError<TCode extends string, TField extends string = string>(\n current: AppError<TCode, TField> | null,\n partial: AppError<TCode, TField>\n): AppError<TCode, TField> {\n if (current === null) {\n return partial\n }\n\n const merged: AppError<TCode, TField> = { ...current }\n\n // Later defined scalar fields win; undefined never overwrites an existing value\n if (partial.code !== undefined) merged.code = partial.code\n if (partial.status !== undefined) merged.status = partial.status\n if (partial.message !== undefined) merged.message = partial.message\n\n // context is shallow-merged with later keys winning\n if (partial.context !== undefined) {\n merged.context = { ...current.context, ...partial.context }\n }\n\n // raw remains the original input (never overwritten)\n // (already set from current)\n\n return merged\n}\n\nexport function runNormalizerPipeline<TCode extends string, TField extends string>(\n raw: unknown,\n customNormalizers: Normalizer<TCode, TField>[],\n builtIn: Normalizer<TCode, TField>,\n onError?: (raw: unknown) => void\n): AppError<TCode, TField> {\n let current: AppError<TCode, TField> | null = null\n\n // Run each custom normalizer\n for (const normalizer of customNormalizers) {\n try {\n const result = normalizer(raw, current)\n if (result !== null) {\n current = mergeAppError(current, result)\n }\n } catch (err) {\n onError?.(err)\n }\n }\n\n // Run built-in normalizer\n try {\n const result = builtIn(raw, current)\n if (result !== null) {\n current = mergeAppError(current, result as AppError<TCode, TField>)\n }\n } catch (err) {\n onError?.(err)\n }\n\n // If final current is null → synthesize GRACEFULERRORS_UNHANDLED\n if (current === null) {\n return { code: 'GRACEFULERRORS_UNHANDLED' as TCode, raw } as AppError<TCode, TField>\n }\n\n // If final current has no code → assign GRACEFULERRORS_UNKNOWN\n if (!current.code) {\n return { ...current, code: 'GRACEFULERRORS_UNKNOWN' as TCode }\n }\n\n return current\n}\n","import type { AppError, ErrorSlot, ErrorStateManager, StateListener, UIAction } from './types'\n\nexport interface StateManagerConfig<TCode extends string> {\n maxConcurrent?: number // default: 3\n maxQueue?: number // default: unbounded\n dedupeWindow?: number // ms, default: 300\n onDropped?: (error: AppError<TCode>, reason: 'dedupe' | 'ttl_expired' | 'queue_overflow') => void\n}\n\nexport function createStateManager<TCode extends string>(\n config: StateManagerConfig<TCode>\n): ErrorStateManager<TCode> {\n const maxConcurrent = config.maxConcurrent ?? 3\n const maxQueue = config.maxQueue\n const dedupeWindow = config.dedupeWindow ?? 300\n const { onDropped } = config\n\n type InternalSlot = ErrorSlot<TCode> & { ttl?: number; _pendingAction?: UIAction }\n\n const activeSlots = new Map<string, InternalSlot>()\n const queue: InternalSlot[] = []\n const dedupeMap = new Map<string, number>()\n const ttlTimers = new Map<string, ReturnType<typeof setTimeout>>()\n const listeners = new Set<StateListener<TCode>>()\n\n function notify(event: Parameters<StateListener<TCode>>[0]): void {\n for (const listener of listeners) {\n try {\n listener(event)\n } catch (err) {\n if (process.env['NODE_ENV'] !== 'production') {\n console.error('[gracefulerrors] StateManager listener threw:', err)\n }\n }\n }\n }\n\n function promoteFromQueue(): void {\n if (queue.length > 0 && activeSlots.size < maxConcurrent) {\n const next = queue.shift()!\n activateSlot(next, next._pendingAction as UIAction)\n }\n }\n\n function activateSlot(slot: InternalSlot, action: UIAction): void {\n slot.state = 'ACTIVE'\n activeSlots.set(slot.fingerprint, slot)\n\n // Start TTL timer if configured\n const ttl = slot.ttl\n if (ttl != null && ttl > 0) {\n const timer = setTimeout(() => {\n if (activeSlots.has(slot.fingerprint)) {\n activeSlots.delete(slot.fingerprint)\n ttlTimers.delete(slot.fingerprint)\n onDropped?.(slot.error, 'ttl_expired')\n promoteFromQueue()\n }\n }, ttl)\n ttlTimers.set(slot.fingerprint, timer)\n }\n\n notify({ type: 'ERROR_ADDED', error: slot.error, action })\n }\n\n function canHandle(fingerprint: string): boolean {\n const lastSeen = dedupeMap.get(fingerprint)\n if (lastSeen === undefined) return true\n return Date.now() - lastSeen >= dedupeWindow\n }\n\n function enqueue(slot: InternalSlot): 'active' | 'queued' | 'rejected' {\n if (!canHandle(slot.fingerprint)) {\n onDropped?.(slot.error, 'dedupe')\n return 'rejected'\n }\n\n dedupeMap.set(slot.fingerprint, Date.now())\n\n if (activeSlots.size < maxConcurrent) {\n activateSlot(slot, slot._pendingAction as UIAction)\n return 'active'\n }\n\n if (maxQueue !== undefined && queue.length >= maxQueue) {\n onDropped?.(slot.error, 'queue_overflow')\n return 'rejected'\n }\n\n slot.state = 'QUEUED'\n queue.push(slot)\n return 'queued'\n }\n\n function release(code: TCode): void {\n // Check active slots first\n let targetFingerprint: string | undefined\n\n for (const [fingerprint, slot] of activeSlots) {\n if (slot.error.code === code) {\n targetFingerprint = fingerprint\n break\n }\n }\n\n if (targetFingerprint !== undefined) {\n const slot = activeSlots.get(targetFingerprint)!\n slot.state = 'EXPIRED'\n activeSlots.delete(targetFingerprint)\n\n const timer = ttlTimers.get(targetFingerprint)\n if (timer !== undefined) {\n clearTimeout(timer)\n ttlTimers.delete(targetFingerprint)\n }\n\n notify({ type: 'ERROR_CLEARED', code })\n promoteFromQueue()\n return\n }\n\n // Check queue — spec: QUEUED | clear() → EXPIRED | remove from queue\n const queueIdx = queue.findIndex((slot) => slot.error.code === code)\n if (queueIdx !== -1) {\n const slot = queue[queueIdx]!\n slot.state = 'EXPIRED'\n queue.splice(queueIdx, 1)\n notify({ type: 'ERROR_CLEARED', code })\n }\n }\n\n function getActiveSlots(): ErrorSlot<TCode>[] {\n return Array.from(activeSlots.values())\n }\n\n function getQueueLength(): number {\n return queue.length\n }\n\n function clearAll(): void {\n for (const timer of ttlTimers.values()) {\n clearTimeout(timer)\n }\n ttlTimers.clear()\n activeSlots.clear()\n queue.length = 0\n notify({ type: 'ALL_CLEARED' })\n }\n\n function subscribe(listener: StateListener<TCode>): () => void {\n listeners.add(listener)\n return () => { listeners.delete(listener) }\n }\n\n return { canHandle, enqueue, release, getActiveSlots, getQueueLength, clearAll, subscribe }\n}\n\nexport type { ErrorStateManager, StateListener }\n","import type { AppError, ErrorRegistry, ErrorRegistryEntry, ErrorRegistryEntryFull } from './types'\n\nexport function lookupEntry<TCode extends string>(\n registry: ErrorRegistry<TCode>,\n code: TCode | string\n): ErrorRegistryEntryFull<TCode> | undefined {\n return (registry as Record<string, ErrorRegistryEntryFull<TCode> | undefined>)[code]\n}\n\nexport function resolveMessage<TCode extends string>(\n entry: ErrorRegistryEntry<TCode>,\n error: AppError<TCode>\n): string | undefined {\n if (entry.message === undefined) return undefined\n if (typeof entry.message === 'function') return entry.message(error)\n return entry.message\n}\n\nexport function mergeRegistries<TBase extends string, TExtended extends string>(\n base: ErrorRegistry<TBase>,\n override: ErrorRegistry<TExtended>\n): ErrorRegistry<TBase | TExtended> {\n return { ...base, ...override } as ErrorRegistry<TBase | TExtended>\n}\n\nexport type { ErrorRegistry, ErrorRegistryEntry, ErrorRegistryEntryFull }\n","import type { AppError, ErrorEngineConfig, ErrorRegistry, UIAction, UIRouter } from './types'\nimport { lookupEntry } from './registry'\n\nexport function createUIRouter<TCode extends string, TField extends string = string>(): UIRouter<TCode, TField> {\n function route(\n error: AppError<TCode, TField>,\n registry: ErrorRegistry<TCode>,\n config: Pick<ErrorEngineConfig<TCode, TField>, 'fallback' | 'requireRegistry' | 'allowFallback' | 'routingStrategy'> & {\n routingContext?: { activeCount: number; queueLength: number }\n }\n ): UIAction | null {\n const entry = lookupEntry(registry, error.code)\n\n if (entry == null && config.requireRegistry) {\n throw new Error(`[gracefulerrors] Registry entry required for code: ${error.code}`)\n }\n\n const routingContext = (config as { routingContext?: { activeCount: number; queueLength: number } }).routingContext ?? {\n activeCount: 0,\n queueLength: 0,\n }\n\n let strategyResult: UIAction | null | undefined\n if (config.routingStrategy) {\n try {\n strategyResult = config.routingStrategy(error, entry, routingContext)\n } catch (err) {\n if (process.env['NODE_ENV'] !== 'production') {\n console.error('[gracefulerrors] routingStrategy threw:', err)\n }\n strategyResult = null\n }\n }\n\n // routingStrategy returns non-null → use it (overrides registry)\n if (strategyResult != null) {\n return strategyResult\n }\n\n // Registry entry exists → use its ui\n if (entry != null) {\n return entry.ui\n }\n\n // No match — apply fallback logic\n // allowFallback defaults to true\n const allowFallback = config.allowFallback !== false\n\n if (allowFallback && config.fallback) {\n return config.fallback.ui\n }\n\n return 'toast'\n }\n\n return { route }\n}\n\nexport type { UIRouter }\n","import { runNormalizerPipeline, builtInNormalizer } from './normalizer'\nimport { createStateManager } from './state-manager'\nimport type { StateManagerConfig } from './state-manager'\nimport { createUIRouter } from './router'\nimport { lookupEntry } from './registry'\nimport type {\n AppError,\n ErrorEngineConfig,\n ErrorEngine,\n HandleResult,\n UIAction,\n RenderIntent,\n ErrorSlot,\n ErrorRegistryEntryFull,\n Normalizer,\n SuppressionDecision,\n TransformResult,\n StateListener,\n} from './types'\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction isSuppressionDecision(result: TransformResult): result is SuppressionDecision {\n return (\n result !== null &&\n typeof result === 'object' &&\n 'suppress' in result &&\n (result as SuppressionDecision).suppress === true\n )\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction safeCall(fn: ((...args: any[]) => any) | undefined, ...args: any[]): void {\n if (!fn) return\n try {\n fn(...args)\n } catch (err) {\n if (process.env['NODE_ENV'] !== 'production') {\n console.error('[gracefulerrors] Hook threw:', err)\n }\n }\n}\n\nfunction safeTransform<TCode extends string, TField extends string>(\n fn: ErrorEngineConfig<TCode, TField>['transform'],\n error: AppError<TCode, TField>\n): TransformResult<TCode, TField> {\n if (!fn) return null\n try {\n return fn(error, { raw: error.raw })\n } catch (err) {\n if (process.env['NODE_ENV'] !== 'production') {\n console.error('[gracefulerrors] transform threw:', err)\n }\n return null\n }\n}\n\nfunction defaultFingerprint(error: AppError): string {\n return `${error.code}:${error.status ?? ''}:${error.context?.field ?? ''}`\n}\n\nfunction buildFallbackEntry<TCode extends string, TField extends string = string>(\n action: UIAction,\n config: ErrorEngineConfig<TCode, TField>\n): ErrorRegistryEntryFull<TCode> {\n return { ui: action as never, message: config.fallback?.message ?? 'An error occurred' }\n}\n\nfunction debugTrace(\n debug: ErrorEngineConfig['debug'],\n trace: Record<string, unknown>\n): void {\n const shouldTrace =\n debug === true || (typeof debug === 'object' && debug !== null && debug.trace === true)\n if (shouldTrace && process.env['NODE_ENV'] !== 'production') {\n console.log('[gracefulerrors trace]', trace)\n }\n}\n\nfunction noOpNormalizer<C extends string, F extends string>(_raw: unknown, current: AppError<C, F> | null): AppError<C, F> | null {\n return current\n}\n\n// ---------------------------------------------------------------------------\n// createErrorEngine\n// ---------------------------------------------------------------------------\n\nexport function createErrorEngine<TCode extends string = string, TField extends string = string>(\n config: ErrorEngineConfig<TCode, TField>\n): ErrorEngine<TCode> {\n // ---- Init: resolve normalizer pipeline ----\n let customNormalizers: Normalizer<TCode, TField>[]\n let resolvedBuiltIn: Normalizer<TCode, TField>\n\n if (config.normalizer && config.normalizers) {\n if (process.env['NODE_ENV'] === 'development') {\n console.warn(\n '[gracefulerrors] Both normalizer and normalizers provided. normalizer takes precedence and normalizers is ignored.'\n )\n }\n customNormalizers = [config.normalizer]\n resolvedBuiltIn = noOpNormalizer\n } else if (config.normalizer) {\n customNormalizers = [config.normalizer]\n resolvedBuiltIn = noOpNormalizer\n } else if (config.normalizers) {\n customNormalizers = config.normalizers\n resolvedBuiltIn = builtInNormalizer as unknown as Normalizer<TCode, TField>\n } else {\n customNormalizers = []\n resolvedBuiltIn = builtInNormalizer as unknown as Normalizer<TCode, TField>\n }\n\n // ---- Init: state manager ----\n const stateManager = createStateManager<TCode>({\n maxConcurrent: config.maxConcurrent,\n maxQueue: config.maxQueue,\n dedupeWindow: config.dedupeWindow,\n onDropped: config.onDropped as StateManagerConfig<TCode>['onDropped'],\n })\n\n // ---- Init: router ----\n const router = createUIRouter<TCode, TField>()\n\n // ---- Init: aggregation ----\n const aggregationMap = new Map<string, number>()\n\n // ---------------------------------------------------------------------------\n // handle\n // ---------------------------------------------------------------------------\n\n function handle(raw: unknown): HandleResult<TCode> {\n // Step 1: onError — always first, even if normalization will fail\n safeCall(config.onError, raw)\n\n // Step 2: normalize\n const normalized = runNormalizerPipeline(\n raw,\n customNormalizers,\n resolvedBuiltIn,\n config.onError\n ) as AppError<TCode, TField>\n\n // Step 3: onNormalized + fire-and-forget async side-effect\n safeCall(config.onNormalized, normalized)\n\n if (config.onErrorAsync) {\n Promise.resolve(config.onErrorAsync(normalized)).catch((err) => {\n if (process.env['NODE_ENV'] === 'development') {\n console.error('[gracefulerrors] onErrorAsync rejected:', err)\n }\n })\n }\n\n // Step 4: transform\n const transformResult = safeTransform(config.transform, normalized)\n\n // Step 5: suppression or apply transform\n let current: AppError<TCode, TField> = normalized\n\n if (transformResult !== null) {\n if (isSuppressionDecision(transformResult)) {\n safeCall(config.onSuppressed, normalized, transformResult.reason)\n return { handled: false, error: normalized, uiAction: null }\n }\n current = transformResult as AppError<TCode, TField>\n }\n\n // Step 6: fingerprint\n const fingerprint = config.fingerprint\n ? config.fingerprint(current as AppError<TCode, TField>)\n : defaultFingerprint(current)\n\n // Step 7: registry lookup + route\n const entry = lookupEntry(config.registry, current.code)\n\n if (!entry && config.requireRegistry) {\n safeCall(config.onFallback, current)\n throw new Error(`[gracefulerrors] Registry entry required for code: ${current.code}`)\n }\n\n const routingContext = {\n activeCount: stateManager.getActiveSlots().length,\n queueLength: stateManager.getQueueLength(),\n }\n\n const action = router.route(\n current,\n config.registry,\n {\n fallback: config.fallback,\n requireRegistry: config.requireRegistry,\n allowFallback: config.allowFallback,\n routingStrategy: config.routingStrategy,\n routingContext,\n } as Parameters<typeof router.route>[2]\n ) as UIAction\n\n // Step 8: onFallback (no match, no requireRegistry) + onRouted\n if (!entry && !config.requireRegistry) {\n safeCall(config.onFallback, current)\n }\n safeCall(config.onRouted, current, action)\n\n // Step 8.5: aggregation check\n const aggConfig = config.aggregation\n if (aggConfig) {\n const aggEnabled =\n aggConfig === true ||\n (typeof aggConfig === 'object' && aggConfig.enabled)\n if (aggEnabled) {\n const aggWindow =\n typeof aggConfig === 'object' && aggConfig.window != null\n ? aggConfig.window\n : 300\n const aggKey = action\n const lastAgg = aggregationMap.get(aggKey)\n const now = Date.now()\n if (lastAgg !== undefined && now - lastAgg < aggWindow) {\n return { handled: false, error: current, uiAction: null }\n }\n aggregationMap.set(aggKey, now)\n }\n }\n\n // Step 9: enqueue into state manager\n const slot = {\n error: current,\n state: 'ACTIVE' as const,\n fingerprint,\n ttl: entry?.ttl,\n _pendingAction: action,\n } as ErrorSlot<TCode> & { ttl?: number; _pendingAction: UIAction }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const placement = stateManager.enqueue(slot as any)\n\n if (placement === 'rejected') {\n return { handled: false, error: current, uiAction: null }\n }\n\n // Step 10: renderer\n if (config.renderer && action !== 'silent' && action !== 'inline') {\n const intent: RenderIntent<TCode> = {\n ui: action,\n error: current,\n entry: entry ?? buildFallbackEntry(action, config),\n }\n\n let onDismiss: (() => void) | undefined\n if (action === 'modal') {\n let dismissTimeoutId: ReturnType<typeof setTimeout> | undefined\n if (config.modalDismissTimeoutMs != null && process.env['NODE_ENV'] !== 'production') {\n dismissTimeoutId = setTimeout(() => {\n console.warn(\n `[gracefulerrors] Modal for \"${String(current.code)}\" was not dismissed within ${config.modalDismissTimeoutMs}ms. Ensure your adapter calls onDismiss().`\n )\n }, config.modalDismissTimeoutMs)\n }\n onDismiss = () => {\n if (dismissTimeoutId !== undefined) clearTimeout(dismissTimeoutId)\n stateManager.release(current.code as TCode)\n }\n }\n\n config.renderer.render(intent, { onDismiss })\n }\n\n // Step 11: debug trace\n debugTrace(config.debug, { raw, normalized: current, action, entry, placement })\n\n // Step 12: return\n return {\n handled: true,\n error: current,\n uiAction: action === 'silent' ? null : action,\n }\n }\n\n // ---------------------------------------------------------------------------\n // clear / clearAll\n // ---------------------------------------------------------------------------\n\n function clear(code: TCode): void {\n stateManager.release(code)\n config.renderer?.clear(code)\n }\n\n function clearAll(): void {\n stateManager.clearAll()\n config.renderer?.clearAll()\n }\n\n function subscribe(listener: StateListener<TCode>): () => void {\n return stateManager.subscribe(listener)\n }\n\n return { handle, clear, clearAll, subscribe }\n}\n\n// ---------------------------------------------------------------------------\n// createFetch\n// ---------------------------------------------------------------------------\n\nexport function createFetch(\n engine: ErrorEngine,\n options: { mode?: 'throw' | 'handle' | 'silent' } = {}\n): (input: RequestInfo | URL, init?: RequestInit) => Promise<Response | undefined> {\n const mode = options.mode ?? 'throw'\n\n return async function (\n input: RequestInfo | URL,\n init?: RequestInit\n ): Promise<Response | undefined> {\n try {\n const response = await fetch(input, init)\n\n if (!response.ok) {\n if (mode === 'silent') return response\n let errorPayload: unknown = response\n try {\n const body = await response.clone().json()\n if (body != null && typeof body === 'object') {\n errorPayload = { status: response.status, ...(body as object) }\n }\n } catch {\n // body is not JSON — fall back to raw Response\n }\n engine.handle(errorPayload)\n if (mode === 'handle') return undefined\n throw response\n }\n\n return response\n } catch (error) {\n // AbortError → pass-through, never forwarded to engine\n // DOMException may not extend Error in all environments — check name directly\n if (error != null && (error as Error).name === 'AbortError') throw error\n\n // Re-thrown Response (4xx/5xx from the !response.ok branch above)\n if (error instanceof Response) throw error\n\n if (mode === 'silent') throw error\n\n engine.handle(error)\n\n if (mode === 'handle') return undefined\n throw error\n }\n }\n}\n\nexport type { ErrorEngine, ErrorEngineConfig, HandleResult }\n"]}
|