@curviate/sdk 0.24.3 → 0.26.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 +174 -1
- package/README.md +78 -0
- package/dist/index.d.ts +2019 -294
- package/dist/index.js +118 -7
- package/package.json +3 -1
- package/src/generated/types.ts +2037 -462
package/dist/index.js
CHANGED
|
@@ -12,6 +12,14 @@ var ERROR_CODES = [
|
|
|
12
12
|
// linking again. Not retryable.
|
|
13
13
|
"ACCOUNT_ALREADY_LINKED",
|
|
14
14
|
"RESOURCE_NOT_FOUND",
|
|
15
|
+
// A read found nothing in Curviate's store and its mode may not fetch, so it
|
|
16
|
+
// refused rather than reaching LinkedIn. 422, and NOT RESOURCE_NOT_FOUND: the
|
|
17
|
+
// resource may be perfectly real, so the fix is another mode, not another id.
|
|
18
|
+
// Absent from RETRYABLE_CODES on purpose, nothing about the answer changes
|
|
19
|
+
// until the caller picks one.
|
|
20
|
+
//
|
|
21
|
+
// `cache_only` is the mode that raises it; re-read with `refill` or `auto`.
|
|
22
|
+
"NOT_STORED",
|
|
15
23
|
"RESOURCE_ACCESS_RESTRICTED",
|
|
16
24
|
// Search filter resolution: a plain-string filter value matched several
|
|
17
25
|
// LinkedIn taxonomy options and one has to be picked (422). The body carries
|
|
@@ -31,6 +39,14 @@ var ERROR_CODES = [
|
|
|
31
39
|
// response headers). Distinct from the account/tenant/platform trio above.
|
|
32
40
|
// Always retry-safe (retry_likely_to_succeed: true); see RETRYABLE_CODES.
|
|
33
41
|
"RATE_LIMITED",
|
|
42
|
+
// Curviate's OWN account-safety ceiling, not a request-rate limit and not
|
|
43
|
+
// LinkedIn refusing. A 429 that never reached LinkedIn and spent nothing:
|
|
44
|
+
// the budget row named by `budgetRow` is at the ceiling configured on
|
|
45
|
+
// `PATCH /v1/{account_id}/safety-policy`, or the account is outside its
|
|
46
|
+
// activity window. Deliberately absent from RETRYABLE_CODES: a monthly
|
|
47
|
+
// row's reset can be weeks out, so backing off is the wrong recovery.
|
|
48
|
+
// Read `resetAt`, `safetyReason` and `safetyHint` instead.
|
|
49
|
+
"BUDGET_EXHAUSTED",
|
|
34
50
|
// Platform errors
|
|
35
51
|
"PLATFORM_ERROR",
|
|
36
52
|
"PLATFORM_NOT_IMPLEMENTED",
|
|
@@ -60,6 +76,14 @@ var ERROR_CODES = [
|
|
|
60
76
|
"REAUTH_REQUIRED",
|
|
61
77
|
// LinkedIn-specific connect errors
|
|
62
78
|
"LINKEDIN_AUTH_FAILED",
|
|
79
|
+
// LinkedIn allows only one session at a time for some accounts, so a person
|
|
80
|
+
// signing in elsewhere breaks this one. A 401 like LINKEDIN_AUTH_FAILED and a
|
|
81
|
+
// separate code because the remedy differs and the auth one prescribes the
|
|
82
|
+
// wrong remedy: reconnecting does nothing while the other session is open.
|
|
83
|
+
// user_fixable, never retryable: a person has to close the other session.
|
|
84
|
+
// While it lasts, `account_states` on the account resource carries
|
|
85
|
+
// `recruiter_session_evicted`.
|
|
86
|
+
"LINKEDIN_SESSION_EVICTED",
|
|
63
87
|
"LINKEDIN_RATE_LIMITED",
|
|
64
88
|
"LINKEDIN_COOKIE_INVALID",
|
|
65
89
|
"LINKEDIN_SERVICE_UNAVAILABLE",
|
|
@@ -94,6 +118,12 @@ var CurviateError = class _CurviateError extends Error {
|
|
|
94
118
|
this.retryLikelyToSucceed = init.retryLikelyToSucceed;
|
|
95
119
|
this.requiredTier = init.requiredTier;
|
|
96
120
|
this.retryAfterMs = init.retryAfterMs;
|
|
121
|
+
this.budgetRow = init.budgetRow;
|
|
122
|
+
this.retryAfterSeconds = init.retryAfterSeconds;
|
|
123
|
+
this.resetAt = init.resetAt;
|
|
124
|
+
this.safetyHint = init.safetyHint;
|
|
125
|
+
this.safetyReason = init.safetyReason;
|
|
126
|
+
this.blocked = init.blocked;
|
|
97
127
|
Object.setPrototypeOf(this, _CurviateError.prototype);
|
|
98
128
|
}
|
|
99
129
|
/**
|
|
@@ -113,6 +143,12 @@ var CurviateError = class _CurviateError extends Error {
|
|
|
113
143
|
if (this.httpStatus !== void 0) json.httpStatus = this.httpStatus;
|
|
114
144
|
if (this.requiredTier !== void 0) json.requiredTier = this.requiredTier;
|
|
115
145
|
if (this.retryAfterMs !== void 0) json.retryAfterMs = this.retryAfterMs;
|
|
146
|
+
if (this.budgetRow !== void 0) json.budgetRow = this.budgetRow;
|
|
147
|
+
if (this.retryAfterSeconds !== void 0) json.retryAfterSeconds = this.retryAfterSeconds;
|
|
148
|
+
if (this.resetAt !== void 0) json.resetAt = this.resetAt;
|
|
149
|
+
if (this.safetyHint !== void 0) json.safetyHint = this.safetyHint;
|
|
150
|
+
if (this.safetyReason !== void 0) json.safetyReason = this.safetyReason;
|
|
151
|
+
if (this.blocked !== void 0) json.blocked = this.blocked;
|
|
116
152
|
return json;
|
|
117
153
|
}
|
|
118
154
|
};
|
|
@@ -152,6 +188,11 @@ var RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
|
152
188
|
"RATE_LIMIT_ACCOUNT",
|
|
153
189
|
"RATE_LIMIT_TENANT",
|
|
154
190
|
"RATE_LIMITED"
|
|
191
|
+
// BUDGET_EXHAUSTED is deliberately NOT here, even though it is a 429. It is
|
|
192
|
+
// Curviate's own account-safety ceiling rather than a request-rate limit:
|
|
193
|
+
// nothing reached LinkedIn, nothing was spent, and a monthly row's reset can
|
|
194
|
+
// be weeks out, so a backoff loop burns retries against a wall that will not
|
|
195
|
+
// move. The recovery is `resetAt` / `safetyHint`, not a sleep.
|
|
155
196
|
]);
|
|
156
197
|
var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PATCH", "PUT", "DELETE"]);
|
|
157
198
|
function defaultSleep(ms) {
|
|
@@ -170,6 +211,15 @@ function backoffDelay(n, jitter) {
|
|
|
170
211
|
function toErrorCode(code) {
|
|
171
212
|
return code && KNOWN_ERROR_CODES.has(code) ? code : "INTERNAL";
|
|
172
213
|
}
|
|
214
|
+
function toSafetyHint(hint) {
|
|
215
|
+
if (!hint || typeof hint !== "object") return void 0;
|
|
216
|
+
const { parameter, message } = hint;
|
|
217
|
+
if (typeof parameter !== "string" || typeof message !== "string") return void 0;
|
|
218
|
+
return { parameter, message };
|
|
219
|
+
}
|
|
220
|
+
function toSafetyReason(reason) {
|
|
221
|
+
return reason === "ceiling" || reason === "activity_window" ? reason : void 0;
|
|
222
|
+
}
|
|
173
223
|
function toRetryHint(hint) {
|
|
174
224
|
if (!hint || typeof hint !== "object") return null;
|
|
175
225
|
const kind = hint.kind;
|
|
@@ -187,6 +237,8 @@ async function errorFromResponse(res) {
|
|
|
187
237
|
env = void 0;
|
|
188
238
|
}
|
|
189
239
|
const requiredTier = env?.required_tier && KNOWN_REQUIRED_TIERS.has(env.required_tier) ? env.required_tier : void 0;
|
|
240
|
+
const safetyHint = toSafetyHint(env?.hint);
|
|
241
|
+
const safetyReason = toSafetyReason(env?.reason);
|
|
190
242
|
return new CurviateError({
|
|
191
243
|
code: toErrorCode(env?.code),
|
|
192
244
|
message: env?.message ?? `Request failed with status ${res.status}.`,
|
|
@@ -195,7 +247,23 @@ async function errorFromResponse(res) {
|
|
|
195
247
|
userFixable: env?.user_fixable ?? false,
|
|
196
248
|
retryLikelyToSucceed: env?.retry_likely_to_succeed ?? false,
|
|
197
249
|
...requiredTier !== void 0 ? { requiredTier } : {},
|
|
198
|
-
...retryAfterMs !== void 0 ? { retryAfterMs } : {}
|
|
250
|
+
...retryAfterMs !== void 0 ? { retryAfterMs } : {},
|
|
251
|
+
...typeof env?.row === "string" ? { budgetRow: env.row } : {},
|
|
252
|
+
// `retry_after` is DELIBERATELY NOT folded into `retryAfterMs`. Everything
|
|
253
|
+
// that reads `retryAfterMs` SLEEPS it, on reads and on writes; a paused
|
|
254
|
+
// budget row can be paused for an hour, and turning that into an hour-long
|
|
255
|
+
// sleep inside a client call would be a hang, not a backoff. It stays on
|
|
256
|
+
// `retryAfterSeconds` for the caller to decide about, next to `budgetRow`,
|
|
257
|
+
// which is the field that says what to do instead: switch work.
|
|
258
|
+
...typeof env?.retry_after === "number" && Number.isFinite(env.retry_after) ? { retryAfterSeconds: env.retry_after } : {},
|
|
259
|
+
// `reset_at` is null-BEARING: null means "no clock frees this" (the
|
|
260
|
+
// pending_invites gauge, or an InMail credit exhaustion), which is a
|
|
261
|
+
// different fact from the field being absent, so both survive the round
|
|
262
|
+
// trip.
|
|
263
|
+
...typeof env?.reset_at === "string" || env?.reset_at === null ? { resetAt: env.reset_at } : {},
|
|
264
|
+
...safetyHint !== void 0 ? { safetyHint } : {},
|
|
265
|
+
...safetyReason !== void 0 ? { safetyReason } : {},
|
|
266
|
+
...typeof env?.blocked === "boolean" ? { blocked: env.blocked } : {}
|
|
199
267
|
});
|
|
200
268
|
}
|
|
201
269
|
function retryDelay(err, attempt, jitter) {
|
|
@@ -278,7 +346,8 @@ async function execute(method, path, opts) {
|
|
|
278
346
|
return parseSuccess(res);
|
|
279
347
|
}
|
|
280
348
|
const err = await errorFromResponse(res);
|
|
281
|
-
const
|
|
349
|
+
const namesBudgetRow = err.budgetRow !== void 0;
|
|
350
|
+
const retryable = !isWrite && !namesBudgetRow && RETRYABLE_CODES.has(err.code) && attempt < opts.maxRetries;
|
|
282
351
|
if (retryable) {
|
|
283
352
|
await sleep(retryDelay(err, attempt + 1, jitterFn()));
|
|
284
353
|
attempt += 1;
|
|
@@ -388,8 +457,31 @@ var AccountsResource = class {
|
|
|
388
457
|
}
|
|
389
458
|
/**
|
|
390
459
|
* 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).
|
|
460
|
+
* central `quotas[]` account-safety view, `account_states[]` and `seat_id`
|
|
461
|
+
* (the seat this account occupies, `null` for an admin seatless account).
|
|
462
|
+
*
|
|
463
|
+
* `quotas[]` REPLACED its earlier `quota_name` / `remaining` / `total` /
|
|
464
|
+
* `reset_time` / `recommended_throttle_hint` shape. Each row now reports
|
|
465
|
+
* `budget_row`, `kind`, `used`, `ceiling`, `effective_ceiling`, `band`,
|
|
466
|
+
* `posture`, `over_default`, `halt` and the window fields. **Compare `used`
|
|
467
|
+
* against `effective_ceiling`, not `ceiling`**: the effective one is the
|
|
468
|
+
* ceiling with the account's warm-up ramp applied, and on a new account the
|
|
469
|
+
* two differ by roughly a factor of seven. `posture` says what happens at the
|
|
470
|
+
* ceiling and defaults to `warn`, so nothing here refuses until it is
|
|
471
|
+
* configured to. Two rows are not counters and say so in `kind`:
|
|
472
|
+
* `total_actions` (`tally`) and `pending_invites` (`gauge`, whose `used` is
|
|
473
|
+
* `null` until something has observed it).
|
|
474
|
+
*
|
|
475
|
+
* The per-minute REQUEST ceiling is a different thing and is deliberately
|
|
476
|
+
* NOT in this array: it protects Curviate's own servers rather than the
|
|
477
|
+
* LinkedIn account, and it is always enforced with `429` whatever the
|
|
478
|
+
* posture.
|
|
479
|
+
*
|
|
480
|
+
* `account_states[]` lists platform conditions the account is in right now,
|
|
481
|
+
* additive to `status` and independent of it: `recruiter_session_evicted`,
|
|
482
|
+
* `commercial_use_limited`, `profile_view_limited`. The last two are read on
|
|
483
|
+
* calls that SUCCEEDED, so check them before concluding thin results are
|
|
484
|
+
* wrong. Empty for an unaffected account.
|
|
393
485
|
*
|
|
394
486
|
* This is a stale-while-revalidate read; it always returns immediately
|
|
395
487
|
* from the cached row (never blocks on a live substrate call). The cached
|
|
@@ -569,11 +661,19 @@ var MessagingResource = class {
|
|
|
569
661
|
body
|
|
570
662
|
});
|
|
571
663
|
}
|
|
572
|
-
/**
|
|
573
|
-
|
|
664
|
+
/**
|
|
665
|
+
* Get details of a single chat. `GET /v1/{account_id}/chats/{chat_id}`
|
|
666
|
+
*
|
|
667
|
+
* A store-servable read: `{ mode, max_age }` says how willing it is to reach
|
|
668
|
+
* LinkedIn, and the response's `source` says which way the answer came. See
|
|
669
|
+
* "Retrieval modes" in the README for the four modes and what `max_age`
|
|
670
|
+
* overrides.
|
|
671
|
+
*/
|
|
672
|
+
getChat(chatId, params) {
|
|
574
673
|
return this.ctx.request({
|
|
575
674
|
method: "GET",
|
|
576
|
-
path: apiPath`/v1/{account_id}/chats/${chatId}
|
|
675
|
+
path: apiPath`/v1/{account_id}/chats/${chatId}`,
|
|
676
|
+
...params ? { query: params } : {}
|
|
577
677
|
});
|
|
578
678
|
}
|
|
579
679
|
/**
|
|
@@ -698,6 +798,16 @@ var MessagingResource = class {
|
|
|
698
798
|
* Free-text search the account's own inbox; matches participant names
|
|
699
799
|
* and message content. `GET /v1/{account_id}/chats/search`
|
|
700
800
|
*
|
|
801
|
+
* SERVED FROM CURVIATE'S STORE, not from LinkedIn: it costs no platform call
|
|
802
|
+
* and draws down no account-safety budget, and a chat that has never been
|
|
803
|
+
* retrieved for this account cannot match. Read `coverage` on the response to
|
|
804
|
+
* see how complete the searched corpus is before treating an empty result as
|
|
805
|
+
* "no such conversation".
|
|
806
|
+
*
|
|
807
|
+
* A result's `type` is `1to1`, `group` or `channel`. It used to be documented
|
|
808
|
+
* as never a channel; that is no longer true, so a caller switching on `type`
|
|
809
|
+
* needs the third arm.
|
|
810
|
+
*
|
|
701
811
|
* @param params - `query` (required free-text term) plus `limit` and an
|
|
702
812
|
* opaque `cursor` for pagination.
|
|
703
813
|
*/
|
|
@@ -2769,6 +2879,7 @@ async function constructEvent(rawBody, signatureHeader, secret, opts) {
|
|
|
2769
2879
|
export {
|
|
2770
2880
|
Curviate,
|
|
2771
2881
|
CurviateError,
|
|
2882
|
+
ERROR_CODES,
|
|
2772
2883
|
WebhookSignatureError,
|
|
2773
2884
|
constructEvent,
|
|
2774
2885
|
isCurviateError
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@curviate/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.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"
|