@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/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 retryable = !isWrite && RETRYABLE_CODES.has(err.code) && attempt < opts.maxRetries;
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 for all tracked quota families and `seat_id` (the
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
- /** Get details of a single chat. `GET /v1/{account_id}/chats/{chat_id}` */
573
- getChat(chatId) {
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.24.3",
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"