@curviate/sdk 0.1.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 ADDED
@@ -0,0 +1,1504 @@
1
+ // src/errors.ts
2
+ var CurviateError = class _CurviateError extends Error {
3
+ constructor(init) {
4
+ super(init.message);
5
+ this.name = "CurviateError";
6
+ this.code = init.code;
7
+ this.httpStatus = init.httpStatus;
8
+ this.retryHint = init.retryHint ?? null;
9
+ this.userFixable = init.userFixable;
10
+ this.retryLikelyToSucceed = init.retryLikelyToSucceed;
11
+ this.requiredTier = init.requiredTier;
12
+ this.retryAfterMs = init.retryAfterMs;
13
+ Object.setPrototypeOf(this, _CurviateError.prototype);
14
+ }
15
+ /**
16
+ * Explicit, credential-safe serialization. Only the documented structured
17
+ * fields are emitted — never a credential, auth header, or any reference to
18
+ * the client. `JSON.stringify(error)` calls this automatically.
19
+ */
20
+ toJSON() {
21
+ const json = {
22
+ name: "CurviateError",
23
+ code: this.code,
24
+ message: this.message,
25
+ retryHint: this.retryHint,
26
+ userFixable: this.userFixable,
27
+ retryLikelyToSucceed: this.retryLikelyToSucceed
28
+ };
29
+ if (this.httpStatus !== void 0) json.httpStatus = this.httpStatus;
30
+ if (this.requiredTier !== void 0) json.requiredTier = this.requiredTier;
31
+ if (this.retryAfterMs !== void 0) json.retryAfterMs = this.retryAfterMs;
32
+ return json;
33
+ }
34
+ };
35
+ function isCurviateError(err) {
36
+ return err instanceof CurviateError;
37
+ }
38
+
39
+ // src/config.ts
40
+ var DEFAULT_BASE_URL = "https://api.curviate.com";
41
+ var DEFAULT_TIMEOUT_MS = 3e4;
42
+ var DEFAULT_MAX_RETRIES = 3;
43
+ function resolveConfig(config) {
44
+ if (typeof config?.apiKey !== "string" || config.apiKey.length === 0) {
45
+ throw new CurviateError({
46
+ code: "INVALID_REQUEST",
47
+ message: "An apiKey is required to construct a Curviate client.",
48
+ userFixable: true,
49
+ retryLikelyToSucceed: false
50
+ });
51
+ }
52
+ return Object.freeze({
53
+ apiKey: config.apiKey,
54
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
55
+ timeout: config.timeout ?? DEFAULT_TIMEOUT_MS,
56
+ maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
57
+ fetch: config.fetch
58
+ });
59
+ }
60
+
61
+ // src/transport.ts
62
+ var BASE_DELAY_MS = 500;
63
+ var MAX_DELAY_MS = 3e4;
64
+ var RETRYABLE_CODES = /* @__PURE__ */ new Set([
65
+ "INTERNAL",
66
+ "PLATFORM_ERROR",
67
+ "PLATFORM_RATE_LIMIT",
68
+ "RATE_LIMIT_ACCOUNT",
69
+ "RATE_LIMIT_TENANT"
70
+ ]);
71
+ var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PATCH", "PUT", "DELETE"]);
72
+ var KNOWN_CODES = /* @__PURE__ */ new Set([
73
+ "UNAUTHORIZED",
74
+ "INVALID_REQUEST",
75
+ "UNSUPPORTED_MEDIA_TYPE",
76
+ "PAYLOAD_TOO_LARGE",
77
+ "ACCOUNT_NOT_FOUND",
78
+ "ACCOUNT_RESTRICTED",
79
+ "RESOURCE_NOT_FOUND",
80
+ "TIER_NOT_ACTIVE",
81
+ "LINKEDIN_FEATURE_NOT_SUBSCRIBED",
82
+ "RATE_LIMIT_ACCOUNT",
83
+ "RATE_LIMIT_TENANT",
84
+ "PLATFORM_RATE_LIMIT",
85
+ "PLATFORM_ERROR",
86
+ "PLATFORM_NOT_IMPLEMENTED",
87
+ "CHECKPOINT_NOT_FOUND",
88
+ "CHECKPOINT_EXPIRED",
89
+ "CHECKPOINT_INVALID_CODE",
90
+ "CHECKPOINT_MAX_ATTEMPTS",
91
+ "CHECKPOINT_ALREADY_RESOLVED",
92
+ "CHECKPOINT_UNSUPPORTED",
93
+ "CONNECTION_IN_PROGRESS",
94
+ "LINKEDIN_AUTH_FAILED",
95
+ "LINKEDIN_RATE_LIMITED",
96
+ "LINKEDIN_COOKIE_INVALID",
97
+ "LINKEDIN_SERVICE_UNAVAILABLE",
98
+ "MESSAGE_WINDOW_EXPIRED",
99
+ "RECIPIENT_UNREACHABLE",
100
+ "PAYMENT_REQUIRED",
101
+ "PAYMENT_FAILED",
102
+ "SUBSCRIPTION_BUSY",
103
+ "SUBSCRIPTION_NOT_FOUND",
104
+ "SEAT_NOT_FOUND",
105
+ "SEAT_CANCELLED",
106
+ "INTERNAL"
107
+ ]);
108
+ var REQUIRED_TIERS = /* @__PURE__ */ new Set([
109
+ "core",
110
+ "sn",
111
+ "sales_nav",
112
+ "recruiter"
113
+ ]);
114
+ function defaultSleep(ms) {
115
+ return new Promise((resolve) => setTimeout(resolve, ms));
116
+ }
117
+ function parseRetryAfterMs(header) {
118
+ if (header == null) return void 0;
119
+ const seconds = Number(header);
120
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
121
+ return 3e4;
122
+ }
123
+ function backoffDelay(n, jitter) {
124
+ const exp = Math.min(BASE_DELAY_MS * 2 ** (n - 1), MAX_DELAY_MS);
125
+ return exp + jitter;
126
+ }
127
+ function toErrorCode(code) {
128
+ return code && KNOWN_CODES.has(code) ? code : "INTERNAL";
129
+ }
130
+ function toRetryHint(hint) {
131
+ if (!hint || typeof hint !== "object") return null;
132
+ const kind = hint.kind;
133
+ if (kind !== "delay" && kind !== "backoff" && kind !== "never") return null;
134
+ const out = { kind };
135
+ if (typeof hint.delay_ms === "number") out.delayMs = hint.delay_ms;
136
+ return out;
137
+ }
138
+ async function errorFromResponse(res) {
139
+ const retryAfterMs = parseRetryAfterMs(res.headers.get("Retry-After"));
140
+ let env;
141
+ try {
142
+ env = await res.clone().json();
143
+ } catch {
144
+ env = void 0;
145
+ }
146
+ const requiredTier = env?.required_tier && REQUIRED_TIERS.has(env.required_tier) ? env.required_tier : void 0;
147
+ return new CurviateError({
148
+ code: toErrorCode(env?.code),
149
+ message: env?.message ?? `Request failed with status ${res.status}.`,
150
+ httpStatus: res.status,
151
+ retryHint: toRetryHint(env?.retry_hint),
152
+ userFixable: env?.user_fixable ?? false,
153
+ retryLikelyToSucceed: env?.retry_likely_to_succeed ?? false,
154
+ ...requiredTier !== void 0 ? { requiredTier } : {},
155
+ ...retryAfterMs !== void 0 ? { retryAfterMs } : {}
156
+ });
157
+ }
158
+ function retryDelay(err, attempt, jitter) {
159
+ if (err.retryAfterMs !== void 0) return err.retryAfterMs;
160
+ if (err.retryHint?.kind === "delay" && err.retryHint.delayMs !== void 0) {
161
+ return err.retryHint.delayMs;
162
+ }
163
+ return backoffDelay(attempt, jitter);
164
+ }
165
+ function buildInit(method, opts, signal) {
166
+ const headers = {
167
+ authorization: `Bearer ${opts.apiKey}`
168
+ };
169
+ const init = { method, headers, signal };
170
+ if (method === "GET" || method === "HEAD" || opts.body === void 0) {
171
+ return init;
172
+ }
173
+ if (opts.body instanceof FormData) {
174
+ init.body = opts.body;
175
+ return init;
176
+ }
177
+ headers["content-type"] = "application/json";
178
+ init.body = JSON.stringify(opts.body);
179
+ return init;
180
+ }
181
+ function buildUrl(path, opts) {
182
+ const url = new URL(path, opts.baseUrl);
183
+ if (opts.query) {
184
+ for (const [key, value] of Object.entries(opts.query)) {
185
+ if (value !== void 0 && value !== null) url.searchParams.set(key, String(value));
186
+ }
187
+ }
188
+ return url.toString();
189
+ }
190
+ async function parseSuccess(res) {
191
+ const ct = res.headers.get("content-type") ?? "";
192
+ if (ct.includes("application/json")) {
193
+ return await res.json();
194
+ }
195
+ return await res.arrayBuffer();
196
+ }
197
+ async function execute(method, path, opts) {
198
+ const doFetch = opts.fetch ?? globalThis.fetch;
199
+ const sleep = opts._sleepFn ?? defaultSleep;
200
+ const jitterFn = opts._jitterFn ?? (() => Math.random() * 200);
201
+ const url = buildUrl(path, opts);
202
+ const isWrite = WRITE_METHODS.has(method);
203
+ let attempt = 0;
204
+ for (; ; ) {
205
+ const controller = new AbortController();
206
+ const timer = setTimeout(() => controller.abort(), opts.timeout);
207
+ let res;
208
+ try {
209
+ res = await doFetch(url, buildInit(method, opts, controller.signal));
210
+ } catch (cause) {
211
+ clearTimeout(timer);
212
+ const aborted = controller.signal.aborted;
213
+ const err2 = new CurviateError({
214
+ code: "INTERNAL",
215
+ message: aborted ? "Request timed out." : "Network error.",
216
+ userFixable: false,
217
+ retryLikelyToSucceed: true
218
+ });
219
+ if (!isWrite && attempt < opts.maxRetries) {
220
+ await sleep(backoffDelay(attempt + 1, jitterFn()));
221
+ attempt += 1;
222
+ continue;
223
+ }
224
+ throw err2;
225
+ }
226
+ clearTimeout(timer);
227
+ if (res.ok) {
228
+ return parseSuccess(res);
229
+ }
230
+ const err = await errorFromResponse(res);
231
+ const retryable = !isWrite && RETRYABLE_CODES.has(err.code) && attempt < opts.maxRetries;
232
+ if (retryable) {
233
+ await sleep(retryDelay(err, attempt + 1, jitterFn()));
234
+ attempt += 1;
235
+ continue;
236
+ }
237
+ if (isWrite && err.retryAfterMs !== void 0) {
238
+ await sleep(err.retryAfterMs);
239
+ }
240
+ throw err;
241
+ }
242
+ }
243
+
244
+ // src/internal/context.ts
245
+ function createContext(config, accountId) {
246
+ const request = (args) => {
247
+ const query = accountId !== void 0 ? { account_id: accountId, ...args.query } : args.query;
248
+ return execute(args.method, args.path, {
249
+ apiKey: config.apiKey,
250
+ baseUrl: config.baseUrl,
251
+ timeout: config.timeout,
252
+ maxRetries: config.maxRetries,
253
+ ...config.fetch ? { fetch: config.fetch } : {},
254
+ ...query !== void 0 ? { query } : {},
255
+ ...args.body !== void 0 ? { body: args.body } : {}
256
+ });
257
+ };
258
+ return { request, accountId };
259
+ }
260
+
261
+ // src/internal/paginate.ts
262
+ async function* paginate(fn, params) {
263
+ let cursor = void 0;
264
+ for (; ; ) {
265
+ const callParams = cursor != null ? { ...params, cursor } : { ...params };
266
+ const page = await fn(callParams);
267
+ const items = page.items ?? page.data ?? [];
268
+ for (const item of items) {
269
+ yield item;
270
+ }
271
+ cursor = page.cursor;
272
+ if (cursor == null) break;
273
+ }
274
+ }
275
+
276
+ // src/resources/accounts.ts
277
+ var AccountsResource = class {
278
+ constructor(ctx) {
279
+ this.ctx = ctx;
280
+ }
281
+ /**
282
+ * List the tenant's connected LinkedIn accounts, cursor-paginated.
283
+ *
284
+ * @param params - optional `limit` (1–250) and `cursor` (from a prior page).
285
+ * @returns a page of accounts and the next-page `cursor` (null when exhausted).
286
+ *
287
+ * @example
288
+ * const page = await curviate.accounts.list({ limit: 50 });
289
+ * for (const acc of page.items ?? []) console.log(acc.account_id);
290
+ */
291
+ list(params) {
292
+ return this.ctx.request({
293
+ method: "GET",
294
+ path: "/v1/accounts",
295
+ ...params !== void 0 ? { query: params } : {}
296
+ });
297
+ }
298
+ /**
299
+ * Connect a LinkedIn account to an empty seat.
300
+ *
301
+ * Returns an account (201) or a checkpoint challenge (202) when LinkedIn
302
+ * requires verification. Callers discriminate on `result.object`.
303
+ */
304
+ link(body) {
305
+ return this.ctx.request({
306
+ method: "POST",
307
+ path: "/v1/accounts/link",
308
+ body
309
+ });
310
+ }
311
+ /**
312
+ * Submit an OTP or 2FA code to resolve a checkpoint challenge.
313
+ */
314
+ submitCheckpoint(body) {
315
+ return this.ctx.request({
316
+ method: "POST",
317
+ path: "/v1/accounts/checkpoints/submit",
318
+ body
319
+ });
320
+ }
321
+ /**
322
+ * Poll for mobile-app approval of a pending checkpoint challenge.
323
+ */
324
+ pollCheckpoint(body) {
325
+ return this.ctx.request({
326
+ method: "POST",
327
+ path: "/v1/accounts/checkpoints/poll",
328
+ body
329
+ });
330
+ }
331
+ /**
332
+ * Generate a one-time hosted connection link the end user opens to authorize a
333
+ * LinkedIn connection without credentials transiting the API or LLM context.
334
+ */
335
+ createConnectLink(body) {
336
+ return this.ctx.request({
337
+ method: "POST",
338
+ path: "/v1/accounts/connect-link",
339
+ body
340
+ });
341
+ }
342
+ /**
343
+ * Return metadata and current state for one connected account, including the
344
+ * central `quotas[]` view for all tracked quota families.
345
+ */
346
+ get(accountId) {
347
+ return this.ctx.request({
348
+ method: "GET",
349
+ path: `/v1/accounts/${accountId}`
350
+ });
351
+ }
352
+ /**
353
+ * Re-authorize a disconnected account in place (same account_id, same seat).
354
+ */
355
+ reconnect(accountId, body) {
356
+ return this.ctx.request({
357
+ method: "POST",
358
+ path: `/v1/accounts/${accountId}/reconnect`,
359
+ body
360
+ });
361
+ }
362
+ /**
363
+ * Refresh frozen account sources (re-sync conversations, connection lists, etc.).
364
+ */
365
+ refresh(accountId) {
366
+ return this.ctx.request({
367
+ method: "POST",
368
+ path: `/v1/accounts/${accountId}/refresh`
369
+ });
370
+ }
371
+ /**
372
+ * Update managed-proxy configuration for an account.
373
+ */
374
+ update(accountId, body) {
375
+ return this.ctx.request({
376
+ method: "PATCH",
377
+ path: `/v1/accounts/${accountId}`,
378
+ body
379
+ });
380
+ }
381
+ /**
382
+ * Hard-disconnect a LinkedIn account; releases the attached seat.
383
+ */
384
+ disconnect(accountId) {
385
+ return this.ctx.request({
386
+ method: "DELETE",
387
+ path: `/v1/accounts/${accountId}`
388
+ });
389
+ }
390
+ };
391
+
392
+ // src/resources/messaging.ts
393
+ function buildFormData(scalars, attachments) {
394
+ const form = new FormData();
395
+ for (const [key, value] of Object.entries(scalars)) {
396
+ if (value !== void 0 && value !== null) {
397
+ form.append(key, String(value));
398
+ }
399
+ }
400
+ for (const attachment of attachments) {
401
+ if (attachment instanceof File) {
402
+ form.append("attachments", attachment);
403
+ } else {
404
+ form.append("attachments", new Blob([attachment]));
405
+ }
406
+ }
407
+ return form;
408
+ }
409
+ var MessagingResource = class {
410
+ constructor(ctx) {
411
+ this.ctx = ctx;
412
+ }
413
+ /** List paginated chats for the account. `GET /v1/chats` */
414
+ listChats(params) {
415
+ return this.ctx.request({
416
+ method: "GET",
417
+ path: "/v1/chats",
418
+ ...params ? { query: params } : {}
419
+ });
420
+ }
421
+ /**
422
+ * Start a new chat. Accepts optional `attachments[]` — when present, the
423
+ * request is sent as `multipart/form-data`. `POST /v1/chats`
424
+ */
425
+ startChat(body) {
426
+ const { attachments, ...scalars } = body;
427
+ if (attachments && attachments.length > 0) {
428
+ return this.ctx.request({
429
+ method: "POST",
430
+ path: "/v1/chats",
431
+ body: buildFormData(scalars, attachments)
432
+ });
433
+ }
434
+ return this.ctx.request({
435
+ method: "POST",
436
+ path: "/v1/chats",
437
+ body: scalars
438
+ });
439
+ }
440
+ /** Get details of a single chat. `GET /v1/chats/{chat_id}` */
441
+ getChat(chatId) {
442
+ return this.ctx.request({
443
+ method: "GET",
444
+ path: `/v1/chats/${chatId}`
445
+ });
446
+ }
447
+ /** List messages in a chat, cursor-paginated. `GET /v1/chats/{chat_id}/messages` */
448
+ listMessages(chatId, params) {
449
+ return this.ctx.request({
450
+ method: "GET",
451
+ path: `/v1/chats/${chatId}/messages`,
452
+ ...params ? { query: params } : {}
453
+ });
454
+ }
455
+ /**
456
+ * Send a message in a chat. Multipart when `attachments[]` are provided.
457
+ * `POST /v1/chats/{chat_id}/messages`
458
+ */
459
+ sendMessage(chatId, body) {
460
+ const { attachments, ...scalars } = body;
461
+ if (attachments && attachments.length > 0) {
462
+ return this.ctx.request({
463
+ method: "POST",
464
+ path: `/v1/chats/${chatId}/messages`,
465
+ body: buildFormData(scalars, attachments)
466
+ });
467
+ }
468
+ return this.ctx.request({
469
+ method: "POST",
470
+ path: `/v1/chats/${chatId}/messages`,
471
+ body: scalars
472
+ });
473
+ }
474
+ /** Trigger a re-sync of a specific chat's message history. `GET /v1/chats/{chat_id}/sync` */
475
+ syncChat(chatId) {
476
+ return this.ctx.request({
477
+ method: "GET",
478
+ path: `/v1/chats/${chatId}/sync`
479
+ });
480
+ }
481
+ /** Get a single message by ID. `GET /v1/messages/{message_id}` */
482
+ getMessage(messageId) {
483
+ return this.ctx.request({
484
+ method: "GET",
485
+ path: `/v1/messages/${messageId}`
486
+ });
487
+ }
488
+ /** Edit a message within the ~60-minute edit window. `PATCH /v1/messages/{message_id}` */
489
+ editMessage(messageId, body) {
490
+ return this.ctx.request({
491
+ method: "PATCH",
492
+ path: `/v1/messages/${messageId}`,
493
+ body
494
+ });
495
+ }
496
+ /** Delete a message within the delete window. `DELETE /v1/messages/{message_id}` */
497
+ deleteMessage(messageId) {
498
+ return this.ctx.request({
499
+ method: "DELETE",
500
+ path: `/v1/messages/${messageId}`
501
+ });
502
+ }
503
+ /**
504
+ * Download a message attachment. Returns raw binary.
505
+ * `GET /v1/messages/{message_id}/attachments/{attachment_id}`
506
+ * Returns `ArrayBuffer` — binary response; the SDK does not cache or store it.
507
+ */
508
+ getAttachment(messageId, attachmentId) {
509
+ return this.ctx.request({
510
+ method: "GET",
511
+ path: `/v1/messages/${messageId}/attachments/${attachmentId}`
512
+ });
513
+ }
514
+ /** Add a reaction to a message. `POST /v1/messages/{message_id}/reactions` */
515
+ addReaction(messageId, body) {
516
+ return this.ctx.request({
517
+ method: "POST",
518
+ path: `/v1/messages/${messageId}/reactions`,
519
+ body
520
+ });
521
+ }
522
+ /** Send an InMail. `POST /v1/messages/inmail` */
523
+ sendInMail(body) {
524
+ return this.ctx.request({
525
+ method: "POST",
526
+ path: "/v1/messages/inmail",
527
+ body
528
+ });
529
+ }
530
+ /** Get the account's InMail credit balance. `GET /v1/messaging/inmail-balance` */
531
+ getInMailBalance(params) {
532
+ return this.ctx.request({
533
+ method: "GET",
534
+ path: "/v1/messaging/inmail-balance",
535
+ ...params ? { query: params } : {}
536
+ });
537
+ }
538
+ /** Re-sync account message history. `GET /v1/messages/sync` */
539
+ syncMessages(params) {
540
+ return this.ctx.request({
541
+ method: "GET",
542
+ path: "/v1/messages/sync",
543
+ ...params ? { query: params } : {}
544
+ });
545
+ }
546
+ };
547
+
548
+ // src/resources/profiles.ts
549
+ var ProfilesResource = class {
550
+ constructor(ctx) {
551
+ this.ctx = ctx;
552
+ }
553
+ /** Get the account's own LinkedIn profile. `GET /v1/profiles/me` */
554
+ getMe() {
555
+ return this.ctx.request({
556
+ method: "GET",
557
+ path: "/v1/profiles/me"
558
+ });
559
+ }
560
+ /**
561
+ * Get a LinkedIn member profile by id or public handle.
562
+ * `GET /v1/profiles/{profile_id}`
563
+ * Pass `{ notify: true }` to signal a profile view.
564
+ * The `account_id` is injected by the account-scoped context.
565
+ */
566
+ get(profileId, params) {
567
+ return this.ctx.request({
568
+ method: "GET",
569
+ path: `/v1/profiles/${profileId}`,
570
+ ...params ? { query: params } : {}
571
+ });
572
+ }
573
+ /** List the account's 1st-degree connections. `GET /v1/profiles/relations` */
574
+ listConnections(params) {
575
+ return this.ctx.request({
576
+ method: "GET",
577
+ path: "/v1/profiles/relations",
578
+ ...params ? { query: params } : {}
579
+ });
580
+ }
581
+ /** List a profile's followers. `GET /v1/profiles/{profile_id}/followers` */
582
+ listFollowers(profileId, params) {
583
+ return this.ctx.request({
584
+ method: "GET",
585
+ path: `/v1/profiles/${profileId}/followers`,
586
+ ...params ? { query: params } : {}
587
+ });
588
+ }
589
+ /**
590
+ * List a profile's posts. `GET /v1/profiles/{profile_id}/posts`
591
+ * Pass `{ is_company: true }` for company posts.
592
+ */
593
+ listPosts(profileId, params) {
594
+ return this.ctx.request({
595
+ method: "GET",
596
+ path: `/v1/profiles/${profileId}/posts`,
597
+ ...params ? { query: params } : {}
598
+ });
599
+ }
600
+ /** List a profile's comments. `GET /v1/profiles/{profile_id}/comments` */
601
+ listComments(profileId, params) {
602
+ return this.ctx.request({
603
+ method: "GET",
604
+ path: `/v1/profiles/${profileId}/comments`,
605
+ ...params ? { query: params } : {}
606
+ });
607
+ }
608
+ /** List a profile's reactions. `GET /v1/profiles/{profile_id}/reactions` */
609
+ listReactions(profileId, params) {
610
+ return this.ctx.request({
611
+ method: "GET",
612
+ path: `/v1/profiles/${profileId}/reactions`,
613
+ ...params ? { query: params } : {}
614
+ });
615
+ }
616
+ /** Get a company profile. `GET /v1/profiles/companies/{company_id}` */
617
+ getCompany(companyId) {
618
+ return this.ctx.request({
619
+ method: "GET",
620
+ path: `/v1/profiles/companies/${companyId}`
621
+ });
622
+ }
623
+ /** Endorse a skill on a profile. `POST /v1/profiles/{profile_id}/endorse` */
624
+ endorse(profileId, body) {
625
+ return this.ctx.request({
626
+ method: "POST",
627
+ path: `/v1/profiles/${profileId}/endorse`,
628
+ body
629
+ });
630
+ }
631
+ };
632
+
633
+ // src/resources/invites.ts
634
+ var InvitesResource = class {
635
+ constructor(ctx) {
636
+ this.ctx = ctx;
637
+ }
638
+ /** Send a connection invitation. `POST /v1/invites` */
639
+ send(body) {
640
+ return this.ctx.request({
641
+ method: "POST",
642
+ path: "/v1/invites",
643
+ body
644
+ });
645
+ }
646
+ /** List sent invitations. `GET /v1/invites/sent` */
647
+ listSent(params) {
648
+ return this.ctx.request({
649
+ method: "GET",
650
+ path: "/v1/invites/sent",
651
+ ...params ? { query: params } : {}
652
+ });
653
+ }
654
+ /** List received invitations. `GET /v1/invites/received` */
655
+ listReceived(params) {
656
+ return this.ctx.request({
657
+ method: "GET",
658
+ path: "/v1/invites/received",
659
+ ...params ? { query: params } : {}
660
+ });
661
+ }
662
+ /**
663
+ * Accept or decline a received invitation.
664
+ * `POST /v1/invites/received/{invitation_id}`
665
+ */
666
+ respond(invitationId, body) {
667
+ return this.ctx.request({
668
+ method: "POST",
669
+ path: `/v1/invites/received/${invitationId}`,
670
+ body
671
+ });
672
+ }
673
+ /** Cancel a sent invitation. `DELETE /v1/invites/{invitation_id}` */
674
+ cancel(invitationId) {
675
+ return this.ctx.request({
676
+ method: "DELETE",
677
+ path: `/v1/invites/${invitationId}`
678
+ });
679
+ }
680
+ };
681
+
682
+ // src/resources/search.ts
683
+ var SearchResource = class {
684
+ constructor(ctx) {
685
+ this.ctx = ctx;
686
+ }
687
+ /**
688
+ * Resolve human-readable terms to opaque filter IDs for structured search.
689
+ * `GET /v1/search/parameters`
690
+ */
691
+ getParameters(query) {
692
+ return this.ctx.request({
693
+ method: "GET",
694
+ path: "/v1/search/parameters",
695
+ query
696
+ });
697
+ }
698
+ /**
699
+ * Search LinkedIn people with structured filters or a pasted URL.
700
+ * `POST /v1/search/people`
701
+ * Cursor passed via query param (not body) — matches the OpenAPI spec.
702
+ */
703
+ people(body) {
704
+ const { cursor, limit, ...rest } = body;
705
+ return this.ctx.request({
706
+ method: "POST",
707
+ path: "/v1/search/people",
708
+ body: rest,
709
+ ...cursor !== void 0 || limit !== void 0 ? { query: { ...cursor !== void 0 ? { cursor } : {}, ...limit !== void 0 ? { limit } : {} } } : {}
710
+ });
711
+ }
712
+ /**
713
+ * Search LinkedIn companies.
714
+ * `POST /v1/search/companies`
715
+ */
716
+ companies(body) {
717
+ const { cursor, limit, ...rest } = body;
718
+ return this.ctx.request({
719
+ method: "POST",
720
+ path: "/v1/search/companies",
721
+ body: rest,
722
+ ...cursor !== void 0 || limit !== void 0 ? { query: { ...cursor !== void 0 ? { cursor } : {}, ...limit !== void 0 ? { limit } : {} } } : {}
723
+ });
724
+ }
725
+ /**
726
+ * Search LinkedIn posts.
727
+ * `POST /v1/search/posts`
728
+ */
729
+ posts(body) {
730
+ const { cursor, limit, ...rest } = body;
731
+ return this.ctx.request({
732
+ method: "POST",
733
+ path: "/v1/search/posts",
734
+ body: rest,
735
+ ...cursor !== void 0 || limit !== void 0 ? { query: { ...cursor !== void 0 ? { cursor } : {}, ...limit !== void 0 ? { limit } : {} } } : {}
736
+ });
737
+ }
738
+ /**
739
+ * Search LinkedIn jobs.
740
+ * `POST /v1/search/jobs`
741
+ */
742
+ jobs(body) {
743
+ const { cursor, limit, ...rest } = body;
744
+ return this.ctx.request({
745
+ method: "POST",
746
+ path: "/v1/search/jobs",
747
+ body: rest,
748
+ ...cursor !== void 0 || limit !== void 0 ? { query: { ...cursor !== void 0 ? { cursor } : {}, ...limit !== void 0 ? { limit } : {} } } : {}
749
+ });
750
+ }
751
+ };
752
+
753
+ // src/resources/posts.ts
754
+ function buildFormData2(scalars, attachments) {
755
+ const form = new FormData();
756
+ for (const [key, value] of Object.entries(scalars)) {
757
+ if (value !== void 0 && value !== null) {
758
+ form.append(key, String(value));
759
+ }
760
+ }
761
+ for (const attachment of attachments ?? []) {
762
+ if (attachment instanceof File) {
763
+ form.append("attachments", attachment);
764
+ } else {
765
+ form.append("attachments", new Blob([attachment]));
766
+ }
767
+ }
768
+ return form;
769
+ }
770
+ var PostsResource = class {
771
+ constructor(ctx) {
772
+ this.ctx = ctx;
773
+ }
774
+ /** List the account's own posts. `GET /v1/posts` */
775
+ list(params) {
776
+ return this.ctx.request({
777
+ method: "GET",
778
+ path: "/v1/posts",
779
+ ...params ? { query: params } : {}
780
+ });
781
+ }
782
+ /**
783
+ * Create a new post. Always sent as multipart/form-data (the API only accepts
784
+ * multipart for this endpoint). `POST /v1/posts`
785
+ */
786
+ create(body) {
787
+ const { attachments, video_thumbnail, ...scalars } = body;
788
+ const form = buildFormData2(scalars, attachments);
789
+ if (video_thumbnail) {
790
+ if (video_thumbnail instanceof File) {
791
+ form.append("video_thumbnail", video_thumbnail);
792
+ } else {
793
+ form.append("video_thumbnail", new Blob([video_thumbnail]));
794
+ }
795
+ }
796
+ return this.ctx.request({
797
+ method: "POST",
798
+ path: "/v1/posts",
799
+ body: form
800
+ });
801
+ }
802
+ /** Get a single post. `GET /v1/posts/{post_id}` */
803
+ get(postId) {
804
+ return this.ctx.request({
805
+ method: "GET",
806
+ path: `/v1/posts/${postId}`
807
+ });
808
+ }
809
+ /** List comments on a post. `GET /v1/posts/{post_id}/comments` */
810
+ listComments(postId, params) {
811
+ return this.ctx.request({
812
+ method: "GET",
813
+ path: `/v1/posts/${postId}/comments`,
814
+ ...params ? { query: params } : {}
815
+ });
816
+ }
817
+ /**
818
+ * Comment on a post. Always sent as multipart/form-data. `POST /v1/posts/{post_id}/comments`
819
+ */
820
+ comment(postId, body) {
821
+ const { attachments, ...scalars } = body;
822
+ return this.ctx.request({
823
+ method: "POST",
824
+ path: `/v1/posts/${postId}/comments`,
825
+ body: buildFormData2(scalars, attachments)
826
+ });
827
+ }
828
+ /** List reactions on a post. `GET /v1/posts/{post_id}/reactions` */
829
+ listReactions(postId, params) {
830
+ return this.ctx.request({
831
+ method: "GET",
832
+ path: `/v1/posts/${postId}/reactions`,
833
+ ...params ? { query: params } : {}
834
+ });
835
+ }
836
+ /** React to a post. `POST /v1/posts/{post_id}/reactions` */
837
+ react(postId, body) {
838
+ return this.ctx.request({
839
+ method: "POST",
840
+ path: `/v1/posts/${postId}/reactions`,
841
+ body
842
+ });
843
+ }
844
+ };
845
+
846
+ // src/resources/sales-navigator.ts
847
+ function buildFormData3(scalars, attachments, voice_message, video_message) {
848
+ const form = new FormData();
849
+ for (const [key, value] of Object.entries(scalars)) {
850
+ if (value !== void 0 && value !== null) {
851
+ if (Array.isArray(value)) {
852
+ for (const item of value) {
853
+ form.append(key, String(item));
854
+ }
855
+ } else {
856
+ form.append(key, String(value));
857
+ }
858
+ }
859
+ }
860
+ for (const attachment of attachments ?? []) {
861
+ if (attachment instanceof File) {
862
+ form.append("attachments", attachment);
863
+ } else {
864
+ form.append("attachments", new Blob([attachment]));
865
+ }
866
+ }
867
+ if (voice_message) {
868
+ if (voice_message instanceof File) {
869
+ form.append("voice_message", voice_message);
870
+ } else {
871
+ form.append("voice_message", new Blob([voice_message]));
872
+ }
873
+ }
874
+ if (video_message) {
875
+ if (video_message instanceof File) {
876
+ form.append("video_message", video_message);
877
+ } else {
878
+ form.append("video_message", new Blob([video_message]));
879
+ }
880
+ }
881
+ return form;
882
+ }
883
+ var SalesNavigatorResource = class {
884
+ constructor(ctx) {
885
+ this.ctx = ctx;
886
+ }
887
+ /**
888
+ * Search LinkedIn members using the full Sales Navigator filter set.
889
+ * `POST /v1/sales-navigator/search/people`
890
+ * Requires tier `sn`. Returns `TIER_NOT_ACTIVE` (403) when the seat lacks it.
891
+ */
892
+ searchPeople(body, params) {
893
+ return this.ctx.request({
894
+ method: "POST",
895
+ path: "/v1/sales-navigator/search/people",
896
+ body,
897
+ ...params ? { query: params } : {}
898
+ });
899
+ }
900
+ /**
901
+ * Search LinkedIn companies using the full Sales Navigator company filter set.
902
+ * `POST /v1/sales-navigator/search/companies`
903
+ */
904
+ searchCompanies(body, params) {
905
+ return this.ctx.request({
906
+ method: "POST",
907
+ path: "/v1/sales-navigator/search/companies",
908
+ body,
909
+ ...params ? { query: params } : {}
910
+ });
911
+ }
912
+ /**
913
+ * Resolve human-readable terms to opaque Sales Navigator filter IDs.
914
+ * `GET /v1/sales-navigator/search/parameters`
915
+ */
916
+ getParameters(params) {
917
+ return this.ctx.request({
918
+ method: "GET",
919
+ path: "/v1/sales-navigator/search/parameters",
920
+ query: params
921
+ });
922
+ }
923
+ /**
924
+ * Start a new Sales Navigator chat. Accepts optional `attachments[]`,
925
+ * `voice_message`, and `video_message` — when present, the request is sent
926
+ * as `multipart/form-data`. `POST /v1/sales-navigator/chats`
927
+ */
928
+ startChat(body) {
929
+ const { attachments, voice_message, video_message, ...scalars } = body;
930
+ const hasFiles = attachments && attachments.length > 0 || voice_message || video_message;
931
+ if (hasFiles) {
932
+ return this.ctx.request({
933
+ method: "POST",
934
+ path: "/v1/sales-navigator/chats",
935
+ body: buildFormData3(
936
+ scalars,
937
+ attachments,
938
+ voice_message,
939
+ video_message
940
+ )
941
+ });
942
+ }
943
+ return this.ctx.request({
944
+ method: "POST",
945
+ path: "/v1/sales-navigator/chats",
946
+ body: scalars
947
+ });
948
+ }
949
+ /**
950
+ * Retrieve a LinkedIn profile with Sales Navigator enrichment.
951
+ * `GET /v1/sales-navigator/profiles/{identifier}`
952
+ */
953
+ getProfile(identifier, params) {
954
+ return this.ctx.request({
955
+ method: "GET",
956
+ path: `/v1/sales-navigator/profiles/${identifier}`,
957
+ // cast needed: linkedin_sections is string[] but transport encodes arrays as repeated params
958
+ ...params ? { query: params } : {}
959
+ });
960
+ }
961
+ /**
962
+ * Save a Sales Navigator member as a lead. `POST /v1/sales-navigator/leads/{user_id}`
963
+ */
964
+ saveLead(userId, body) {
965
+ return this.ctx.request({
966
+ method: "POST",
967
+ path: `/v1/sales-navigator/leads/${userId}`,
968
+ body
969
+ });
970
+ }
971
+ /**
972
+ * Trigger a re-sync of the account's Sales Navigator message history.
973
+ * `GET /v1/sales-navigator/messages/sync`
974
+ */
975
+ syncMessages(params) {
976
+ return this.ctx.request({
977
+ method: "GET",
978
+ path: "/v1/sales-navigator/messages/sync",
979
+ query: params
980
+ });
981
+ }
982
+ };
983
+
984
+ // src/resources/recruiter.ts
985
+ function buildFormData4(scalars, attachments, voice_message, video_message) {
986
+ const form = new FormData();
987
+ for (const [key, value] of Object.entries(scalars)) {
988
+ if (value !== void 0 && value !== null) {
989
+ if (Array.isArray(value)) {
990
+ for (const item of value) {
991
+ form.append(key, String(item));
992
+ }
993
+ } else {
994
+ form.append(key, String(value));
995
+ }
996
+ }
997
+ }
998
+ for (const attachment of attachments ?? []) {
999
+ if (attachment instanceof File) {
1000
+ form.append("attachments", attachment);
1001
+ } else {
1002
+ form.append("attachments", new Blob([attachment]));
1003
+ }
1004
+ }
1005
+ if (voice_message) {
1006
+ if (voice_message instanceof File) {
1007
+ form.append("voice_message", voice_message);
1008
+ } else {
1009
+ form.append("voice_message", new Blob([voice_message]));
1010
+ }
1011
+ }
1012
+ if (video_message) {
1013
+ if (video_message instanceof File) {
1014
+ form.append("video_message", video_message);
1015
+ } else {
1016
+ form.append("video_message", new Blob([video_message]));
1017
+ }
1018
+ }
1019
+ return form;
1020
+ }
1021
+ var RecruiterResource = class {
1022
+ constructor(ctx) {
1023
+ this.ctx = ctx;
1024
+ }
1025
+ /**
1026
+ * Trigger a re-sync of the account's Recruiter message history.
1027
+ * `GET /v1/recruiter/messages/sync`
1028
+ */
1029
+ syncMessages(params) {
1030
+ return this.ctx.request({
1031
+ method: "GET",
1032
+ path: "/v1/recruiter/messages/sync",
1033
+ query: params
1034
+ });
1035
+ }
1036
+ /**
1037
+ * Start a Recruiter chat (InMail). Accepts optional `attachments[]`,
1038
+ * `voice_message`, and `video_message` — when present, the request is sent
1039
+ * as `multipart/form-data`. `POST /v1/recruiter/chats`
1040
+ */
1041
+ startChat(body) {
1042
+ const { attachments, voice_message, video_message, ...scalars } = body;
1043
+ const hasFiles = attachments && attachments.length > 0 || voice_message || video_message;
1044
+ if (hasFiles) {
1045
+ return this.ctx.request({
1046
+ method: "POST",
1047
+ path: "/v1/recruiter/chats",
1048
+ body: buildFormData4(
1049
+ scalars,
1050
+ attachments,
1051
+ voice_message,
1052
+ video_message
1053
+ )
1054
+ });
1055
+ }
1056
+ return this.ctx.request({
1057
+ method: "POST",
1058
+ path: "/v1/recruiter/chats",
1059
+ body: scalars
1060
+ });
1061
+ }
1062
+ /**
1063
+ * Retrieve a LinkedIn profile with Recruiter enrichment.
1064
+ * `GET /v1/recruiter/profiles/{identifier}`
1065
+ */
1066
+ getProfile(identifier, params) {
1067
+ return this.ctx.request({
1068
+ method: "GET",
1069
+ path: `/v1/recruiter/profiles/${identifier}`,
1070
+ // cast needed: linkedin_sections is string[] but transport encodes arrays as repeated params
1071
+ ...params ? { query: params } : {}
1072
+ });
1073
+ }
1074
+ /**
1075
+ * Search LinkedIn members using Recruiter filters.
1076
+ * `POST /v1/recruiter/search/people`
1077
+ */
1078
+ searchPeople(body, params) {
1079
+ return this.ctx.request({
1080
+ method: "POST",
1081
+ path: "/v1/recruiter/search/people",
1082
+ body,
1083
+ ...params ? { query: params } : {}
1084
+ });
1085
+ }
1086
+ /**
1087
+ * Resolve human-readable terms to opaque Recruiter filter IDs.
1088
+ * `GET /v1/recruiter/search/parameters`
1089
+ */
1090
+ getParameters(params) {
1091
+ return this.ctx.request({
1092
+ method: "GET",
1093
+ path: "/v1/recruiter/search/parameters",
1094
+ query: params
1095
+ });
1096
+ }
1097
+ /**
1098
+ * List Recruiter hiring projects. `GET /v1/recruiter/projects`
1099
+ */
1100
+ listProjects(params) {
1101
+ return this.ctx.request({
1102
+ method: "GET",
1103
+ path: "/v1/recruiter/projects",
1104
+ ...params ? { query: params } : {}
1105
+ });
1106
+ }
1107
+ /**
1108
+ * Get a single Recruiter hiring project. `GET /v1/recruiter/projects/{project_id}`
1109
+ */
1110
+ getProject(projectId) {
1111
+ return this.ctx.request({
1112
+ method: "GET",
1113
+ path: `/v1/recruiter/projects/${projectId}`
1114
+ });
1115
+ }
1116
+ /**
1117
+ * Add a member to a hiring project pipeline as a candidate.
1118
+ * `POST /v1/recruiter/projects/candidates/{user_id}`
1119
+ */
1120
+ addCandidate(userId, body) {
1121
+ return this.ctx.request({
1122
+ method: "POST",
1123
+ path: `/v1/recruiter/projects/candidates/${userId}`,
1124
+ body
1125
+ });
1126
+ }
1127
+ /**
1128
+ * Add a member to a hiring project pipeline as an applicant.
1129
+ * `POST /v1/recruiter/projects/applicants/{user_id}`
1130
+ */
1131
+ addApplicant(userId, body) {
1132
+ return this.ctx.request({
1133
+ method: "POST",
1134
+ path: `/v1/recruiter/projects/applicants/${userId}`,
1135
+ body
1136
+ });
1137
+ }
1138
+ /**
1139
+ * Reject an applicant from a hiring project.
1140
+ * `POST /v1/recruiter/projects/applicants/{user_id}/reject`
1141
+ */
1142
+ rejectApplicant(userId, body) {
1143
+ return this.ctx.request({
1144
+ method: "POST",
1145
+ path: `/v1/recruiter/projects/applicants/${userId}/reject`,
1146
+ body
1147
+ });
1148
+ }
1149
+ /**
1150
+ * List Recruiter job postings. `GET /v1/recruiter/jobs`
1151
+ */
1152
+ listJobs(params) {
1153
+ return this.ctx.request({
1154
+ method: "GET",
1155
+ path: "/v1/recruiter/jobs",
1156
+ ...params ? { query: params } : {}
1157
+ });
1158
+ }
1159
+ /**
1160
+ * Create a Recruiter job posting draft. `POST /v1/recruiter/jobs`
1161
+ */
1162
+ createJob(body) {
1163
+ return this.ctx.request({
1164
+ method: "POST",
1165
+ path: "/v1/recruiter/jobs",
1166
+ body
1167
+ });
1168
+ }
1169
+ /**
1170
+ * Publish a Recruiter job posting draft.
1171
+ * `POST /v1/recruiter/jobs/{job_id}/publish`
1172
+ */
1173
+ publishJob(jobId, body) {
1174
+ return this.ctx.request({
1175
+ method: "POST",
1176
+ path: `/v1/recruiter/jobs/${jobId}/publish`,
1177
+ body
1178
+ });
1179
+ }
1180
+ /**
1181
+ * Solve a publish verification checkpoint.
1182
+ * `POST /v1/recruiter/jobs/{job_id}/checkpoint`
1183
+ */
1184
+ solveJobCheckpoint(jobId, body) {
1185
+ return this.ctx.request({
1186
+ method: "POST",
1187
+ path: `/v1/recruiter/jobs/${jobId}/checkpoint`,
1188
+ body
1189
+ });
1190
+ }
1191
+ /**
1192
+ * List applicants for a job posting.
1193
+ * `GET /v1/recruiter/jobs/{job_id}/applicants`
1194
+ */
1195
+ listApplicants(jobId, params) {
1196
+ return this.ctx.request({
1197
+ method: "GET",
1198
+ path: `/v1/recruiter/jobs/${jobId}/applicants`,
1199
+ // cast needed: include_degree / exclude_degree can be string[] but transport encodes arrays as repeated params
1200
+ ...params ? { query: params } : {}
1201
+ });
1202
+ }
1203
+ /**
1204
+ * Get one job applicant. `GET /v1/recruiter/jobs/applicants/{applicant_id}`
1205
+ */
1206
+ getApplicant(applicantId) {
1207
+ return this.ctx.request({
1208
+ method: "GET",
1209
+ path: `/v1/recruiter/jobs/applicants/${applicantId}`
1210
+ });
1211
+ }
1212
+ /**
1213
+ * Download an applicant's resume as raw binary.
1214
+ * `GET /v1/recruiter/jobs/applicants/{applicant_id}/resume`
1215
+ * Returns `ArrayBuffer` — binary response; the SDK does not cache or store it.
1216
+ */
1217
+ downloadResume(applicantId) {
1218
+ return this.ctx.request({
1219
+ method: "GET",
1220
+ path: `/v1/recruiter/jobs/applicants/${applicantId}/resume`
1221
+ });
1222
+ }
1223
+ };
1224
+
1225
+ // src/resources/webhooks.ts
1226
+ var WebhooksResource = class {
1227
+ constructor(ctx) {
1228
+ this.ctx = ctx;
1229
+ }
1230
+ /**
1231
+ * Register a new webhook endpoint to receive real-time events.
1232
+ * `POST /v1/webhooks`
1233
+ * The HMAC signing secret is returned exactly once in the 201 response.
1234
+ * `account_ids` is required (each webhook is scoped to specific accounts).
1235
+ */
1236
+ create(body) {
1237
+ return this.ctx.request({
1238
+ method: "POST",
1239
+ path: "/v1/webhooks",
1240
+ body
1241
+ });
1242
+ }
1243
+ /**
1244
+ * List the tenant's registered webhooks, cursor-paginated.
1245
+ * `GET /v1/webhooks`
1246
+ */
1247
+ list(params) {
1248
+ return this.ctx.request({
1249
+ method: "GET",
1250
+ path: "/v1/webhooks",
1251
+ ...params ? { query: params } : {}
1252
+ });
1253
+ }
1254
+ /**
1255
+ * Return the complete canonical webhook event catalogue (21 events, grouped by source).
1256
+ * `GET /v1/webhooks/events`
1257
+ */
1258
+ listEvents() {
1259
+ return this.ctx.request({
1260
+ method: "GET",
1261
+ path: "/v1/webhooks/events"
1262
+ });
1263
+ }
1264
+ /**
1265
+ * Update a webhook in place. `source` is immutable.
1266
+ * `PATCH /v1/webhooks/{id}`
1267
+ */
1268
+ update(id, body) {
1269
+ return this.ctx.request({
1270
+ method: "PATCH",
1271
+ path: `/v1/webhooks/${id}`,
1272
+ body
1273
+ });
1274
+ }
1275
+ /**
1276
+ * Permanently remove a webhook subscription.
1277
+ * `DELETE /v1/webhooks/{id}`
1278
+ */
1279
+ delete(id) {
1280
+ return this.ctx.request({
1281
+ method: "DELETE",
1282
+ path: `/v1/webhooks/${id}`
1283
+ });
1284
+ }
1285
+ /**
1286
+ * Return the set of changes since the last known version for a connected account.
1287
+ * Enables event-driven state sync without polling the account endpoint.
1288
+ * `GET /v1/accounts/{id}/state-diff`
1289
+ */
1290
+ getStateDiff(accountId, params) {
1291
+ return this.ctx.request({
1292
+ method: "GET",
1293
+ path: `/v1/accounts/${accountId}/state-diff`,
1294
+ ...params ? { query: params } : {}
1295
+ });
1296
+ }
1297
+ };
1298
+
1299
+ // src/resources/index.ts
1300
+ function buildNamespaces(ctx) {
1301
+ return {
1302
+ accounts: new AccountsResource(ctx),
1303
+ messaging: new MessagingResource(ctx),
1304
+ profiles: new ProfilesResource(ctx),
1305
+ invites: new InvitesResource(ctx),
1306
+ search: new SearchResource(ctx),
1307
+ posts: new PostsResource(ctx),
1308
+ salesNavigator: new SalesNavigatorResource(ctx),
1309
+ recruiter: new RecruiterResource(ctx),
1310
+ webhooks: new WebhooksResource(ctx)
1311
+ };
1312
+ }
1313
+ function buildAccountScopedNamespaces(ctx) {
1314
+ const { accounts: _accounts, ...rest } = buildNamespaces(ctx);
1315
+ void _accounts;
1316
+ return rest;
1317
+ }
1318
+
1319
+ // src/client.ts
1320
+ var Curviate = class {
1321
+ constructor(config) {
1322
+ this.config = resolveConfig(config);
1323
+ const ctx = createContext(this.config);
1324
+ const ns = buildNamespaces(ctx);
1325
+ this.accounts = ns.accounts;
1326
+ this.messaging = ns.messaging;
1327
+ this.profiles = ns.profiles;
1328
+ this.invites = ns.invites;
1329
+ this.search = ns.search;
1330
+ this.posts = ns.posts;
1331
+ this.salesNavigator = ns.salesNavigator;
1332
+ this.recruiter = ns.recruiter;
1333
+ this.webhooks = ns.webhooks;
1334
+ }
1335
+ /**
1336
+ * Return an account-scoped accessor that fixes `account_id` on every call,
1337
+ * so callers need not repeat it per method.
1338
+ *
1339
+ * @param accountId - a Curviate account id (`acc_…`). Empty throws
1340
+ * `CurviateError({ code: 'INVALID_REQUEST' })` synchronously, no network call.
1341
+ *
1342
+ * @example
1343
+ * await curviate.account("acc_123").messaging.listChats();
1344
+ */
1345
+ account(accountId) {
1346
+ if (typeof accountId !== "string" || accountId.length === 0) {
1347
+ throw new CurviateError({
1348
+ code: "INVALID_REQUEST",
1349
+ message: "account(accountId) requires a non-empty account id.",
1350
+ userFixable: true,
1351
+ retryLikelyToSucceed: false
1352
+ });
1353
+ }
1354
+ const ctx = createContext(this.config, accountId);
1355
+ return buildAccountScopedNamespaces(ctx);
1356
+ }
1357
+ /**
1358
+ * Transparent cursor-pagination iterator.
1359
+ *
1360
+ * Calls `fn` repeatedly, injecting the `cursor` from each response into the
1361
+ * next call, yielding individual items until `cursor` is null.
1362
+ *
1363
+ * @param fn - a bound resource method (e.g. `profiles.listConnections.bind(profiles)`)
1364
+ * @param params - the initial params (minus cursor)
1365
+ *
1366
+ * @example
1367
+ * for await (const profile of curviate.paginate(
1368
+ * curviate.account('acc_123').profiles.listConnections,
1369
+ * { limit: 50 }
1370
+ * )) {
1371
+ * console.log(profile);
1372
+ * }
1373
+ */
1374
+ paginate(fn, params) {
1375
+ return paginate(fn, params);
1376
+ }
1377
+ };
1378
+
1379
+ // src/webhooks.ts
1380
+ var WebhookSignatureError = class _WebhookSignatureError extends Error {
1381
+ constructor(reason, message) {
1382
+ super(message);
1383
+ this.name = "WebhookSignatureError";
1384
+ this.reason = reason;
1385
+ Object.setPrototypeOf(this, _WebhookSignatureError.prototype);
1386
+ }
1387
+ };
1388
+ function parseHeader(header) {
1389
+ const parts = header.split(",");
1390
+ let tStr;
1391
+ let v1;
1392
+ for (const part of parts) {
1393
+ const eq = part.indexOf("=");
1394
+ if (eq === -1) continue;
1395
+ const key = part.slice(0, eq).trim();
1396
+ const val = part.slice(eq + 1).trim();
1397
+ if (key === "t") tStr = val;
1398
+ else if (key === "v1") v1 = val;
1399
+ }
1400
+ if (tStr === void 0 || v1 === void 0) {
1401
+ throw new WebhookSignatureError(
1402
+ "malformed_header",
1403
+ 'Webhook signature header must contain both "t=<timestamp>" and "v1=<hmac>".'
1404
+ );
1405
+ }
1406
+ const timestamp = Number(tStr);
1407
+ if (!Number.isFinite(timestamp) || isNaN(timestamp)) {
1408
+ throw new WebhookSignatureError(
1409
+ "malformed_header",
1410
+ "Webhook signature header timestamp is not a valid number."
1411
+ );
1412
+ }
1413
+ return { timestamp, v1 };
1414
+ }
1415
+ function hexToBytes(hex) {
1416
+ if (hex.length % 2 !== 0) {
1417
+ throw new WebhookSignatureError("malformed_header", "HMAC hex has odd length.");
1418
+ }
1419
+ const bytes = new Uint8Array(hex.length / 2);
1420
+ for (let i = 0; i < bytes.length; i++) {
1421
+ const byte = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
1422
+ if (isNaN(byte)) {
1423
+ throw new WebhookSignatureError("malformed_header", "HMAC hex contains non-hex character.");
1424
+ }
1425
+ bytes[i] = byte;
1426
+ }
1427
+ return bytes;
1428
+ }
1429
+ function bytesToHex(bytes) {
1430
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1431
+ }
1432
+ function constantTimeEqual(a, b) {
1433
+ const len = Math.max(a.length, b.length);
1434
+ let diff = a.length ^ b.length;
1435
+ for (let i = 0; i < len; i++) {
1436
+ const ai = i < a.length ? a[i] : 0;
1437
+ const bi = i < b.length ? b[i] : 0;
1438
+ diff |= ai ^ bi;
1439
+ }
1440
+ return diff === 0;
1441
+ }
1442
+ async function hmacWebCrypto(secret, payload) {
1443
+ const enc = new TextEncoder();
1444
+ const keyMaterial = await globalThis.crypto.subtle.importKey(
1445
+ "raw",
1446
+ enc.encode(secret),
1447
+ { name: "HMAC", hash: "SHA-256" },
1448
+ false,
1449
+ ["sign"]
1450
+ );
1451
+ const sig = await globalThis.crypto.subtle.sign("HMAC", keyMaterial, enc.encode(payload));
1452
+ return bytesToHex(new Uint8Array(sig));
1453
+ }
1454
+ function verifyAndParse(computedHex, v1, timestamp, bodyStr, replayWindowSecs) {
1455
+ let providedBytes;
1456
+ try {
1457
+ providedBytes = hexToBytes(v1);
1458
+ } catch {
1459
+ throw new WebhookSignatureError(
1460
+ "invalid_signature",
1461
+ "Webhook signature v1 value is not valid hex."
1462
+ );
1463
+ }
1464
+ const computedBytes = hexToBytes(computedHex);
1465
+ if (!constantTimeEqual(computedBytes, providedBytes)) {
1466
+ throw new WebhookSignatureError(
1467
+ "invalid_signature",
1468
+ "Webhook signature does not match. Verify your signing secret."
1469
+ );
1470
+ }
1471
+ const nowSecs = Date.now() / 1e3;
1472
+ const ageSecs = Math.abs(nowSecs - timestamp);
1473
+ if (ageSecs > replayWindowSecs) {
1474
+ throw new WebhookSignatureError(
1475
+ "replay_detected",
1476
+ `Webhook event is outside the replay window (${Math.floor(ageSecs)}s ago/ahead, window is ${replayWindowSecs}s).`
1477
+ );
1478
+ }
1479
+ let parsed;
1480
+ try {
1481
+ parsed = JSON.parse(bodyStr);
1482
+ } catch {
1483
+ throw new WebhookSignatureError("malformed_header", "Webhook body is not valid JSON.");
1484
+ }
1485
+ if (typeof parsed !== "object" || parsed === null || typeof parsed["type"] !== "string") {
1486
+ throw new WebhookSignatureError("malformed_header", 'Webhook payload missing "type" field.');
1487
+ }
1488
+ return parsed;
1489
+ }
1490
+ async function constructEvent(rawBody, signatureHeader, secret, opts) {
1491
+ const replayWindowSecs = opts?.replayWindowSecs ?? 300;
1492
+ const { timestamp, v1 } = parseHeader(signatureHeader);
1493
+ const bodyStr = typeof rawBody === "string" ? rawBody : rawBody.toString("utf8");
1494
+ const hmacPayload = `${timestamp}.${bodyStr}`;
1495
+ const computedHex = await hmacWebCrypto(secret, hmacPayload);
1496
+ return verifyAndParse(computedHex, v1, timestamp, bodyStr, replayWindowSecs);
1497
+ }
1498
+ export {
1499
+ Curviate,
1500
+ CurviateError,
1501
+ WebhookSignatureError,
1502
+ constructEvent,
1503
+ isCurviateError
1504
+ };