@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 +47 -14
- package/lib/client.js +321 -717
- package/lib/config-compat.js +2 -0
- package/lib/errors.js +252 -0
- package/lib/host-routes.js +10 -1
- package/lib/index.js +40 -34
- package/lib/provider-http.js +13 -0
- package/lib/providers.js +7 -7
- package/lib/schema.js +65 -0
- package/locale/en.json +6 -0
- package/locale/ru.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +5 -3
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
|
|
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(
|
|
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
|
|
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:
|
|
61
|
+
onAttempt(key, { ok: false, tookMs, reason: classified.message, code: classified.code })
|
|
40
62
|
}
|
|
41
|
-
|
|
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
|
-
|
|
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
|
}
|