conduyt 1.69.0 → 1.70.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 +2 -0
- package/dist/client.js +54 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -131,6 +131,8 @@ out and named in `_omitted`. Every HTTP refusal takes this path (JSON calls, tex
|
|
|
131
131
|
Error: Conduyt API 503: Contact merge in progress [Retry-After: 5s] {"code":"merge_in_progress"}
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
+
The contact merge route (`POST /contacts/merge`, no command today) answers a retryable 503 with `Retry-After` and the code `merge_contacts_busy` (a contact stayed busy for the whole lock budget) or `merge_budget_exhausted` (the request's time budget ran out), nothing changed. A command that reaches it prints the refusal in this grammar, exits 1 and does not retry; run it again after the stated seconds.
|
|
135
|
+
|
|
134
136
|
## License
|
|
135
137
|
|
|
136
138
|
MIT
|
package/dist/client.js
CHANGED
|
@@ -83,6 +83,58 @@ export function renderErrorDetails(body) {
|
|
|
83
83
|
out._omitted = omitted;
|
|
84
84
|
return JSON.stringify(out);
|
|
85
85
|
}
|
|
86
|
+
const RA_MAX_SECONDS = 2_147_483_647; // delay-seconds is unbounded 1*DIGIT: a longer value saturates here instead of becoming null
|
|
87
|
+
const RA_MONTHS = { Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5, Jul: 6, Aug: 7, Sep: 8, Oct: 9, Nov: 10, Dec: 11 };
|
|
88
|
+
const RA_MON = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec";
|
|
89
|
+
const RA_IMF = new RegExp(`^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\\d{2}) (${RA_MON}) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) GMT$`);
|
|
90
|
+
const RA_850 = new RegExp(`^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\\d{2})-(${RA_MON})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2}) GMT$`);
|
|
91
|
+
const RA_ASC = new RegExp(`^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) (${RA_MON}) ([ \\d]\\d) (\\d{2}):(\\d{2}):(\\d{2}) (\\d{4})$`);
|
|
92
|
+
/** A UTC timestamp for the parts, or NaN when any part is out of range (31 Feb, 25:00, ...). */
|
|
93
|
+
function raUtc(y, mo, d, h, mi, s) {
|
|
94
|
+
if (y < 1900 || h > 23 || mi > 59 || s > 60)
|
|
95
|
+
return NaN; // RFC 5322: years below 1900 are not dates
|
|
96
|
+
const t = Date.UTC(y, mo, d, h, mi, Math.min(s, 59));
|
|
97
|
+
const back = new Date(t);
|
|
98
|
+
return back.getUTCFullYear() === y && back.getUTCMonth() === mo && back.getUTCDate() === d ? t : NaN;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The ONE Retry-After parser. RFC 9110: delay-seconds (digits; a value too long for a number saturates at 2^31-1) or an
|
|
102
|
+
* HTTP-date in one of the three §5.6.7 forms, parsed explicitly and always as UTC (never the host timezone): IMF-fixdate,
|
|
103
|
+
* RFC 850 (two-digit year: more than 50 years ahead of `nowMs` means the previous century) and asctime. A date becomes the
|
|
104
|
+
* remaining seconds, floored at 0. Absent, blank or anything else (ISO timestamps, other zones, "-3") is null.
|
|
105
|
+
*/
|
|
106
|
+
export function parseRetryAfter(raw, nowMs = Date.now()) {
|
|
107
|
+
if (raw === null || raw === undefined)
|
|
108
|
+
return null;
|
|
109
|
+
const v = raw.trim();
|
|
110
|
+
if (v === "")
|
|
111
|
+
return null;
|
|
112
|
+
if (/^\d+(\.\d+)?$/.test(v)) {
|
|
113
|
+
const n = Number(v);
|
|
114
|
+
return Number.isNaN(n) ? null : Math.min(n, RA_MAX_SECONDS);
|
|
115
|
+
}
|
|
116
|
+
let t = NaN;
|
|
117
|
+
let m;
|
|
118
|
+
if ((m = RA_IMF.exec(v)))
|
|
119
|
+
t = raUtc(Number(m[3]), RA_MONTHS[m[2]], Number(m[1]), Number(m[4]), Number(m[5]), Number(m[6]));
|
|
120
|
+
else if ((m = RA_850.exec(v))) {
|
|
121
|
+
const century = Math.floor(new Date(nowMs).getUTCFullYear() / 100) * 100;
|
|
122
|
+
const mo = RA_MONTHS[m[2]], d = Number(m[1]), h = Number(m[4]), mi = Number(m[5]), sec = Number(m[6]);
|
|
123
|
+
t = raUtc(century + Number(m[3]), mo, d, h, mi, sec);
|
|
124
|
+
// RFC 9110: a timestamp more than 50 years in the future (the same instant 50 calendar years on, UTC) is the previous century
|
|
125
|
+
// (the day is clamped to the month's last day: 29 Feb + 50 years is 28 Feb, never 1 Mar)
|
|
126
|
+
const n = new Date(nowMs);
|
|
127
|
+
const ly = n.getUTCFullYear() + 50, lm = n.getUTCMonth();
|
|
128
|
+
const limit = Date.UTC(ly, lm, Math.min(n.getUTCDate(), new Date(Date.UTC(ly, lm + 1, 0)).getUTCDate()), n.getUTCHours(), n.getUTCMinutes(), n.getUTCSeconds(), n.getUTCMilliseconds());
|
|
129
|
+
if (!Number.isNaN(t) && t > limit)
|
|
130
|
+
t = raUtc(century - 100 + Number(m[3]), mo, d, h, mi, sec);
|
|
131
|
+
}
|
|
132
|
+
else if ((m = RA_ASC.exec(v)))
|
|
133
|
+
t = raUtc(Number(m[6]), RA_MONTHS[m[1]], Number(m[2].trim()), Number(m[3]), Number(m[4]), Number(m[5]));
|
|
134
|
+
if (Number.isNaN(t))
|
|
135
|
+
return null;
|
|
136
|
+
return Math.max(0, Math.ceil((t - nowMs) / 1000));
|
|
137
|
+
}
|
|
86
138
|
/**
|
|
87
139
|
* The ONE place an HTTP refusal becomes an Error (request, postText and stream all use it). Grammar (stable):
|
|
88
140
|
* `Conduyt API <status>: <message>[ [Retry-After: <N>s]][ <details>]`; <details> is ONE JSON object and the FINAL token
|
|
@@ -90,9 +142,8 @@ export function renderErrorDetails(body) {
|
|
|
90
142
|
*/
|
|
91
143
|
export function formatHttpError(res, text) {
|
|
92
144
|
const json = text ? safeParse(text) : null;
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
const raTag = Number.isFinite(raN) && raN >= 0 ? ` [Retry-After: ${raN}s]` : "";
|
|
145
|
+
const raN = parseRetryAfter(res.headers.get("retry-after"));
|
|
146
|
+
const raTag = raN !== null ? ` [Retry-After: ${raN}s]` : "";
|
|
96
147
|
const obj = json && typeof json === "object" && !Array.isArray(json) ? json : null;
|
|
97
148
|
if (obj && typeof obj.error === "string" && obj.error) {
|
|
98
149
|
const extras = renderErrorDetails(obj);
|
package/package.json
CHANGED