@curviate/sdk 0.24.2 → 0.25.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/CHANGELOG.md +124 -1
- package/README.md +10 -0
- package/dist/index.d.ts +2559 -459
- package/dist/index.js +99 -4
- package/package.json +3 -1
- package/src/generated/types.ts +2486 -528
package/dist/index.js
CHANGED
|
@@ -31,6 +31,14 @@ var ERROR_CODES = [
|
|
|
31
31
|
// response headers). Distinct from the account/tenant/platform trio above.
|
|
32
32
|
// Always retry-safe (retry_likely_to_succeed: true); see RETRYABLE_CODES.
|
|
33
33
|
"RATE_LIMITED",
|
|
34
|
+
// Curviate's OWN account-safety ceiling, not a request-rate limit and not
|
|
35
|
+
// LinkedIn refusing. A 429 that never reached LinkedIn and spent nothing:
|
|
36
|
+
// the budget row named by `budgetRow` is at the ceiling configured on
|
|
37
|
+
// `PATCH /v1/{account_id}/safety-policy`, or the account is outside its
|
|
38
|
+
// activity window. Deliberately absent from RETRYABLE_CODES: a monthly
|
|
39
|
+
// row's reset can be weeks out, so backing off is the wrong recovery.
|
|
40
|
+
// Read `resetAt`, `safetyReason` and `safetyHint` instead.
|
|
41
|
+
"BUDGET_EXHAUSTED",
|
|
34
42
|
// Platform errors
|
|
35
43
|
"PLATFORM_ERROR",
|
|
36
44
|
"PLATFORM_NOT_IMPLEMENTED",
|
|
@@ -60,6 +68,14 @@ var ERROR_CODES = [
|
|
|
60
68
|
"REAUTH_REQUIRED",
|
|
61
69
|
// LinkedIn-specific connect errors
|
|
62
70
|
"LINKEDIN_AUTH_FAILED",
|
|
71
|
+
// LinkedIn allows only one session at a time for some accounts, so a person
|
|
72
|
+
// signing in elsewhere breaks this one. A 401 like LINKEDIN_AUTH_FAILED and a
|
|
73
|
+
// separate code because the remedy differs and the auth one prescribes the
|
|
74
|
+
// wrong remedy: reconnecting does nothing while the other session is open.
|
|
75
|
+
// user_fixable, never retryable: a person has to close the other session.
|
|
76
|
+
// While it lasts, `account_states` on the account resource carries
|
|
77
|
+
// `recruiter_session_evicted`.
|
|
78
|
+
"LINKEDIN_SESSION_EVICTED",
|
|
63
79
|
"LINKEDIN_RATE_LIMITED",
|
|
64
80
|
"LINKEDIN_COOKIE_INVALID",
|
|
65
81
|
"LINKEDIN_SERVICE_UNAVAILABLE",
|
|
@@ -94,6 +110,12 @@ var CurviateError = class _CurviateError extends Error {
|
|
|
94
110
|
this.retryLikelyToSucceed = init.retryLikelyToSucceed;
|
|
95
111
|
this.requiredTier = init.requiredTier;
|
|
96
112
|
this.retryAfterMs = init.retryAfterMs;
|
|
113
|
+
this.budgetRow = init.budgetRow;
|
|
114
|
+
this.retryAfterSeconds = init.retryAfterSeconds;
|
|
115
|
+
this.resetAt = init.resetAt;
|
|
116
|
+
this.safetyHint = init.safetyHint;
|
|
117
|
+
this.safetyReason = init.safetyReason;
|
|
118
|
+
this.blocked = init.blocked;
|
|
97
119
|
Object.setPrototypeOf(this, _CurviateError.prototype);
|
|
98
120
|
}
|
|
99
121
|
/**
|
|
@@ -113,6 +135,12 @@ var CurviateError = class _CurviateError extends Error {
|
|
|
113
135
|
if (this.httpStatus !== void 0) json.httpStatus = this.httpStatus;
|
|
114
136
|
if (this.requiredTier !== void 0) json.requiredTier = this.requiredTier;
|
|
115
137
|
if (this.retryAfterMs !== void 0) json.retryAfterMs = this.retryAfterMs;
|
|
138
|
+
if (this.budgetRow !== void 0) json.budgetRow = this.budgetRow;
|
|
139
|
+
if (this.retryAfterSeconds !== void 0) json.retryAfterSeconds = this.retryAfterSeconds;
|
|
140
|
+
if (this.resetAt !== void 0) json.resetAt = this.resetAt;
|
|
141
|
+
if (this.safetyHint !== void 0) json.safetyHint = this.safetyHint;
|
|
142
|
+
if (this.safetyReason !== void 0) json.safetyReason = this.safetyReason;
|
|
143
|
+
if (this.blocked !== void 0) json.blocked = this.blocked;
|
|
116
144
|
return json;
|
|
117
145
|
}
|
|
118
146
|
};
|
|
@@ -152,6 +180,11 @@ var RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
|
152
180
|
"RATE_LIMIT_ACCOUNT",
|
|
153
181
|
"RATE_LIMIT_TENANT",
|
|
154
182
|
"RATE_LIMITED"
|
|
183
|
+
// BUDGET_EXHAUSTED is deliberately NOT here, even though it is a 429. It is
|
|
184
|
+
// Curviate's own account-safety ceiling rather than a request-rate limit:
|
|
185
|
+
// nothing reached LinkedIn, nothing was spent, and a monthly row's reset can
|
|
186
|
+
// be weeks out, so a backoff loop burns retries against a wall that will not
|
|
187
|
+
// move. The recovery is `resetAt` / `safetyHint`, not a sleep.
|
|
155
188
|
]);
|
|
156
189
|
var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PATCH", "PUT", "DELETE"]);
|
|
157
190
|
function defaultSleep(ms) {
|
|
@@ -170,6 +203,15 @@ function backoffDelay(n, jitter) {
|
|
|
170
203
|
function toErrorCode(code) {
|
|
171
204
|
return code && KNOWN_ERROR_CODES.has(code) ? code : "INTERNAL";
|
|
172
205
|
}
|
|
206
|
+
function toSafetyHint(hint) {
|
|
207
|
+
if (!hint || typeof hint !== "object") return void 0;
|
|
208
|
+
const { parameter, message } = hint;
|
|
209
|
+
if (typeof parameter !== "string" || typeof message !== "string") return void 0;
|
|
210
|
+
return { parameter, message };
|
|
211
|
+
}
|
|
212
|
+
function toSafetyReason(reason) {
|
|
213
|
+
return reason === "ceiling" || reason === "activity_window" ? reason : void 0;
|
|
214
|
+
}
|
|
173
215
|
function toRetryHint(hint) {
|
|
174
216
|
if (!hint || typeof hint !== "object") return null;
|
|
175
217
|
const kind = hint.kind;
|
|
@@ -187,6 +229,8 @@ async function errorFromResponse(res) {
|
|
|
187
229
|
env = void 0;
|
|
188
230
|
}
|
|
189
231
|
const requiredTier = env?.required_tier && KNOWN_REQUIRED_TIERS.has(env.required_tier) ? env.required_tier : void 0;
|
|
232
|
+
const safetyHint = toSafetyHint(env?.hint);
|
|
233
|
+
const safetyReason = toSafetyReason(env?.reason);
|
|
190
234
|
return new CurviateError({
|
|
191
235
|
code: toErrorCode(env?.code),
|
|
192
236
|
message: env?.message ?? `Request failed with status ${res.status}.`,
|
|
@@ -195,7 +239,23 @@ async function errorFromResponse(res) {
|
|
|
195
239
|
userFixable: env?.user_fixable ?? false,
|
|
196
240
|
retryLikelyToSucceed: env?.retry_likely_to_succeed ?? false,
|
|
197
241
|
...requiredTier !== void 0 ? { requiredTier } : {},
|
|
198
|
-
...retryAfterMs !== void 0 ? { retryAfterMs } : {}
|
|
242
|
+
...retryAfterMs !== void 0 ? { retryAfterMs } : {},
|
|
243
|
+
...typeof env?.row === "string" ? { budgetRow: env.row } : {},
|
|
244
|
+
// `retry_after` is DELIBERATELY NOT folded into `retryAfterMs`. Everything
|
|
245
|
+
// that reads `retryAfterMs` SLEEPS it, on reads and on writes; a paused
|
|
246
|
+
// budget row can be paused for an hour, and turning that into an hour-long
|
|
247
|
+
// sleep inside a client call would be a hang, not a backoff. It stays on
|
|
248
|
+
// `retryAfterSeconds` for the caller to decide about, next to `budgetRow`,
|
|
249
|
+
// which is the field that says what to do instead: switch work.
|
|
250
|
+
...typeof env?.retry_after === "number" && Number.isFinite(env.retry_after) ? { retryAfterSeconds: env.retry_after } : {},
|
|
251
|
+
// `reset_at` is null-BEARING: null means "no clock frees this" (the
|
|
252
|
+
// pending_invites gauge, or an InMail credit exhaustion), which is a
|
|
253
|
+
// different fact from the field being absent, so both survive the round
|
|
254
|
+
// trip.
|
|
255
|
+
...typeof env?.reset_at === "string" || env?.reset_at === null ? { resetAt: env.reset_at } : {},
|
|
256
|
+
...safetyHint !== void 0 ? { safetyHint } : {},
|
|
257
|
+
...safetyReason !== void 0 ? { safetyReason } : {},
|
|
258
|
+
...typeof env?.blocked === "boolean" ? { blocked: env.blocked } : {}
|
|
199
259
|
});
|
|
200
260
|
}
|
|
201
261
|
function retryDelay(err, attempt, jitter) {
|
|
@@ -278,7 +338,8 @@ async function execute(method, path, opts) {
|
|
|
278
338
|
return parseSuccess(res);
|
|
279
339
|
}
|
|
280
340
|
const err = await errorFromResponse(res);
|
|
281
|
-
const
|
|
341
|
+
const namesBudgetRow = err.budgetRow !== void 0;
|
|
342
|
+
const retryable = !isWrite && !namesBudgetRow && RETRYABLE_CODES.has(err.code) && attempt < opts.maxRetries;
|
|
282
343
|
if (retryable) {
|
|
283
344
|
await sleep(retryDelay(err, attempt + 1, jitterFn()));
|
|
284
345
|
attempt += 1;
|
|
@@ -388,8 +449,31 @@ var AccountsResource = class {
|
|
|
388
449
|
}
|
|
389
450
|
/**
|
|
390
451
|
* Return metadata and current state for one connected account, including the
|
|
391
|
-
* central `quotas[]` view
|
|
392
|
-
* seat this account occupies, `null` for an admin seatless account).
|
|
452
|
+
* central `quotas[]` account-safety view, `account_states[]` and `seat_id`
|
|
453
|
+
* (the seat this account occupies, `null` for an admin seatless account).
|
|
454
|
+
*
|
|
455
|
+
* `quotas[]` REPLACED its earlier `quota_name` / `remaining` / `total` /
|
|
456
|
+
* `reset_time` / `recommended_throttle_hint` shape. Each row now reports
|
|
457
|
+
* `budget_row`, `kind`, `used`, `ceiling`, `effective_ceiling`, `band`,
|
|
458
|
+
* `posture`, `over_default`, `halt` and the window fields. **Compare `used`
|
|
459
|
+
* against `effective_ceiling`, not `ceiling`**: the effective one is the
|
|
460
|
+
* ceiling with the account's warm-up ramp applied, and on a new account the
|
|
461
|
+
* two differ by roughly a factor of seven. `posture` says what happens at the
|
|
462
|
+
* ceiling and defaults to `warn`, so nothing here refuses until it is
|
|
463
|
+
* configured to. Two rows are not counters and say so in `kind`:
|
|
464
|
+
* `total_actions` (`tally`) and `pending_invites` (`gauge`, whose `used` is
|
|
465
|
+
* `null` until something has observed it).
|
|
466
|
+
*
|
|
467
|
+
* The per-minute REQUEST ceiling is a different thing and is deliberately
|
|
468
|
+
* NOT in this array: it protects Curviate's own servers rather than the
|
|
469
|
+
* LinkedIn account, and it is always enforced with `429` whatever the
|
|
470
|
+
* posture.
|
|
471
|
+
*
|
|
472
|
+
* `account_states[]` lists platform conditions the account is in right now,
|
|
473
|
+
* additive to `status` and independent of it: `recruiter_session_evicted`,
|
|
474
|
+
* `commercial_use_limited`, `profile_view_limited`. The last two are read on
|
|
475
|
+
* calls that SUCCEEDED, so check them before concluding thin results are
|
|
476
|
+
* wrong. Empty for an unaffected account.
|
|
393
477
|
*
|
|
394
478
|
* This is a stale-while-revalidate read; it always returns immediately
|
|
395
479
|
* from the cached row (never blocks on a live substrate call). The cached
|
|
@@ -698,6 +782,16 @@ var MessagingResource = class {
|
|
|
698
782
|
* Free-text search the account's own inbox; matches participant names
|
|
699
783
|
* and message content. `GET /v1/{account_id}/chats/search`
|
|
700
784
|
*
|
|
785
|
+
* SERVED FROM CURVIATE'S STORE, not from LinkedIn: it costs no platform call
|
|
786
|
+
* and draws down no account-safety budget, and a chat that has never been
|
|
787
|
+
* retrieved for this account cannot match. Read `coverage` on the response to
|
|
788
|
+
* see how complete the searched corpus is before treating an empty result as
|
|
789
|
+
* "no such conversation".
|
|
790
|
+
*
|
|
791
|
+
* A result's `type` is `1to1`, `group` or `channel`. It used to be documented
|
|
792
|
+
* as never a channel; that is no longer true, so a caller switching on `type`
|
|
793
|
+
* needs the third arm.
|
|
794
|
+
*
|
|
701
795
|
* @param params - `query` (required free-text term) plus `limit` and an
|
|
702
796
|
* opaque `cursor` for pagination.
|
|
703
797
|
*/
|
|
@@ -2769,6 +2863,7 @@ async function constructEvent(rawBody, signatureHeader, secret, opts) {
|
|
|
2769
2863
|
export {
|
|
2770
2864
|
Curviate,
|
|
2771
2865
|
CurviateError,
|
|
2866
|
+
ERROR_CODES,
|
|
2772
2867
|
WebhookSignatureError,
|
|
2773
2868
|
constructEvent,
|
|
2774
2869
|
isCurviateError
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@curviate/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "TypeScript SDK for the Curviate API.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "tsup",
|
|
40
40
|
"prepack": "node scripts/check-clean.mjs && node scripts/check-copy.mjs && tsup && node scripts/check-clean.mjs --dist",
|
|
41
|
+
"check:cli-notice": "node scripts/check-cli-notice.mjs",
|
|
41
42
|
"test": "vitest run",
|
|
42
43
|
"test:watch": "vitest",
|
|
43
44
|
"typecheck": "tsc --noEmit",
|
|
@@ -58,6 +59,7 @@
|
|
|
58
59
|
"@types/node": "^22.15.21",
|
|
59
60
|
"msw": "^2.7.0",
|
|
60
61
|
"openapi-typescript": "7.6.1",
|
|
62
|
+
"semver": "^7.8.5",
|
|
61
63
|
"tsup": "^8.3.5",
|
|
62
64
|
"typescript": "^5.7.2",
|
|
63
65
|
"vitest": "^3.2.2"
|