@goodandready/dsh-voice 0.9.45 → 0.9.47

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/lib/chain.js CHANGED
@@ -1,21 +1,18 @@
1
1
  // Provider fallback chain. No network and no cordis — a pure function so
2
2
  // failure behaviour is unit-testable.
3
-
4
- function normalizeError(err) {
5
- const cause = err?.cause?.message || err?.message || String(err)
6
- return cause.slice(0, 200)
7
- }
3
+ import { classifyProviderOutcome, shouldFallbackError, ChainExhaustedError } from './errors.js'
8
4
 
9
5
  /**
10
6
  * @param order {string[]} provider keys in attempt order
11
- * @param providers {Record<string, () => Promise<{ok, provider?, text?, reason?}>>}
12
- * @param onAttempt {(provider: string, res: {ok: boolean, tookMs: number, reason?: string}) => void} optional stats callback
7
+ * @param providers {Record<string, () => Promise<{ok, provider?, text?, reason?, code?, fallbackEligible?}>>}
8
+ * @param onAttempt {(provider: string, res: {ok: boolean, tookMs: number, reason?: string, code?: string}) => void} optional stats callback
13
9
  * @returns {Promise<{provider: string, text: string, tookMs: number}>}
14
10
  */
15
11
  export async function runChain(order, providers, onAttempt) {
16
12
  const t0 = Date.now()
17
13
  const keys = (Array.isArray(order) ? order : []).filter((k) => typeof providers[k] === 'function')
18
14
  const errors = []
15
+ const attempts = []
19
16
  for (const key of keys) {
20
17
  const startT = Date.now()
21
18
  try {
@@ -27,19 +24,55 @@ export async function runChain(order, providers, onAttempt) {
27
24
  }
28
25
  return { provider: out.provider || key, text: out.text, tookMs: Date.now() - t0 }
29
26
  }
30
- const reason = (out && out.reason) || 'unknown'
27
+ const classified = classifyProviderOutcome((out && out.provider) || key, out || { reason: 'unknown' })
28
+ const pName = classified.provider || key
31
29
  if (typeof onAttempt === 'function') {
32
- onAttempt((out && out.provider) || key, { ok: false, tookMs, reason })
30
+ onAttempt(pName, { ok: false, tookMs, reason: classified.message, code: classified.code })
31
+ }
32
+ attempts.push({
33
+ provider: pName,
34
+ code: classified.code,
35
+ reason: classified.message,
36
+ tookMs,
37
+ isRetryable: classified.isRetryable,
38
+ })
39
+ errors.push(`${pName}: ${classified.message}`)
40
+ if (!shouldFallbackError(classified)) {
41
+ throw classified
33
42
  }
34
- errors.push(`${(out && out.provider) || key}: ${reason}`)
35
43
  } catch (err) {
36
44
  const tookMs = Date.now() - startT
37
- const normErr = normalizeError(err)
45
+ const classified = classifyProviderOutcome(key, err)
46
+ if (!shouldFallbackError(classified)) {
47
+ const alreadyRecorded = attempts.some(
48
+ (a) => a.provider === (classified.provider || key) && a.reason === classified.message
49
+ )
50
+ if (!alreadyRecorded && typeof onAttempt === 'function') {
51
+ onAttempt(classified.provider || key, {
52
+ ok: false,
53
+ tookMs,
54
+ reason: classified.message,
55
+ code: classified.code,
56
+ })
57
+ }
58
+ throw classified
59
+ }
38
60
  if (typeof onAttempt === 'function') {
39
- onAttempt(key, { ok: false, tookMs, reason: normErr })
61
+ onAttempt(key, { ok: false, tookMs, reason: classified.message, code: classified.code })
40
62
  }
41
- errors.push(`${key}: ${normErr}`)
63
+ attempts.push({
64
+ provider: key,
65
+ code: classified.code,
66
+ reason: classified.message,
67
+ tookMs,
68
+ isRetryable: classified.isRetryable,
69
+ })
70
+ errors.push(`${key}: ${classified.message}`)
42
71
  }
43
72
  }
44
- throw new Error(`all providers failed (${errors.join('; ')})`)
73
+ const anyRetryable = attempts.some((a) => a.isRetryable)
74
+ throw new ChainExhaustedError(`all providers failed (${errors.join('; ')})`, {
75
+ attempts,
76
+ isRetryable: anyRetryable,
77
+ })
45
78
  }