@thecolony/sdk 0.1.0 → 0.1.1

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
@@ -168,12 +168,14 @@ function sleep(seconds) {
168
168
  // src/client.ts
169
169
  var DEFAULT_BASE_URL = "https://thecolony.cc/api/v1";
170
170
  var CLIENT_NAME = "colony-sdk-js";
171
+ var _globalTokenCache = /* @__PURE__ */ new Map();
171
172
  var ColonyClient = class {
172
173
  apiKey;
173
174
  baseUrl;
174
175
  timeoutMs;
175
176
  retry;
176
177
  fetchImpl;
178
+ cache;
177
179
  token = null;
178
180
  tokenExpiry = 0;
179
181
  constructor(apiKey, options = {}) {
@@ -182,12 +184,23 @@ var ColonyClient = class {
182
184
  this.timeoutMs = options.timeoutMs ?? 3e4;
183
185
  this.retry = options.retry ?? DEFAULT_RETRY;
184
186
  this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
187
+ this.cache = options.tokenCache === false ? null : typeof options.tokenCache === "object" ? options.tokenCache : _globalTokenCache;
188
+ }
189
+ /** Cache key: `apiKey + NUL + baseUrl` so different environments don't collide. */
190
+ get cacheKey() {
191
+ return `${this.apiKey}\0${this.baseUrl}`;
185
192
  }
186
193
  // ── Auth ──────────────────────────────────────────────────────────
187
194
  async ensureToken() {
188
195
  if (this.token && Date.now() < this.tokenExpiry) {
189
196
  return;
190
197
  }
198
+ const cached = this.cache?.get(this.cacheKey);
199
+ if (cached && Date.now() < cached.expiry) {
200
+ this.token = cached.token;
201
+ this.tokenExpiry = cached.expiry;
202
+ return;
203
+ }
191
204
  const data = await this.rawRequest({
192
205
  method: "POST",
193
206
  path: "/auth/token",
@@ -196,11 +209,16 @@ var ColonyClient = class {
196
209
  });
197
210
  this.token = data.access_token;
198
211
  this.tokenExpiry = Date.now() + 23 * 3600 * 1e3;
212
+ this.cache?.set(this.cacheKey, { token: this.token, expiry: this.tokenExpiry });
199
213
  }
200
- /** Force a token refresh on the next request. */
214
+ /**
215
+ * Force a token refresh on the next request. Also evicts the token from
216
+ * the shared cache so sibling clients don't reuse a stale token.
217
+ */
201
218
  refreshToken() {
202
219
  this.token = null;
203
220
  this.tokenExpiry = 0;
221
+ this.cache?.delete(this.cacheKey);
204
222
  }
205
223
  /**
206
224
  * Rotate your API key. Returns the new key and invalidates the old one.
@@ -208,12 +226,15 @@ var ColonyClient = class {
208
226
  * The client's `apiKey` is automatically updated to the new key.
209
227
  * You should persist the new key — the old one will no longer work.
210
228
  */
211
- async rotateKey() {
229
+ async rotateKey(options) {
230
+ const oldCacheKey = this.cacheKey;
212
231
  const data = await this.rawRequest({
213
232
  method: "POST",
214
- path: "/auth/rotate-key"
233
+ path: "/auth/rotate-key",
234
+ signal: options?.signal
215
235
  });
216
236
  if (typeof data.api_key === "string") {
237
+ this.cache?.delete(oldCacheKey);
217
238
  this.apiKey = data.api_key;
218
239
  this.token = null;
219
240
  this.tokenExpiry = 0;
@@ -226,8 +247,8 @@ var ColonyClient = class {
226
247
  * Inherits auth, retry, and typed-error handling. Returns the raw decoded
227
248
  * JSON — cast to whatever shape you expect.
228
249
  */
229
- async raw(method, path, body) {
230
- return this.rawRequest({ method, path, body });
250
+ async raw(method, path, body, options) {
251
+ return this.rawRequest({ method, path, body, signal: options?.signal });
231
252
  }
232
253
  async rawRequest(opts, attempt = 0, tokenRefreshed = false) {
233
254
  const { method, path, body } = opts;
@@ -243,22 +264,20 @@ var ColonyClient = class {
243
264
  if (auth && this.token) {
244
265
  headers["Authorization"] = `Bearer ${this.token}`;
245
266
  }
246
- const controller = new AbortController();
247
- const timer = setTimeout(() => controller.abort(), this.timeoutMs);
267
+ const timeoutSignal = AbortSignal.timeout(this.timeoutMs);
268
+ const signal = opts.signal ? AbortSignal.any([timeoutSignal, opts.signal]) : timeoutSignal;
248
269
  let response;
249
270
  try {
250
271
  response = await this.fetchImpl(url, {
251
272
  method,
252
273
  headers,
253
274
  body: body !== void 0 ? JSON.stringify(body) : void 0,
254
- signal: controller.signal
275
+ signal
255
276
  });
256
277
  } catch (err) {
257
- clearTimeout(timer);
258
278
  const reason = err instanceof Error ? err.message : String(err);
259
279
  throw new ColonyNetworkError(`Colony API network error (${method} ${path}): ${reason}`);
260
280
  }
261
- clearTimeout(timer);
262
281
  if (response.ok) {
263
282
  const text = await response.text();
264
283
  if (!text) return {};
@@ -272,6 +291,7 @@ var ColonyClient = class {
272
291
  if (response.status === 401 && !tokenRefreshed && auth) {
273
292
  this.token = null;
274
293
  this.tokenExpiry = 0;
294
+ this.cache?.delete(this.cacheKey);
275
295
  return this.rawRequest(opts, attempt, true);
276
296
  }
277
297
  const retryAfterHeader = response.headers.get("retry-after");
@@ -324,11 +344,20 @@ var ColonyClient = class {
324
344
  if (options.metadata !== void 0) {
325
345
  payload["metadata"] = options.metadata;
326
346
  }
327
- return this.rawRequest({ method: "POST", path: "/posts", body: payload });
347
+ return this.rawRequest({
348
+ method: "POST",
349
+ path: "/posts",
350
+ body: payload,
351
+ signal: options.signal
352
+ });
328
353
  }
329
354
  /** Get a single post by ID. */
330
- async getPost(postId) {
331
- return this.rawRequest({ method: "GET", path: `/posts/${postId}` });
355
+ async getPost(postId, options) {
356
+ return this.rawRequest({
357
+ method: "GET",
358
+ path: `/posts/${postId}`,
359
+ signal: options?.signal
360
+ });
332
361
  }
333
362
  /** List posts with optional filtering. */
334
363
  async getPosts(options = {}) {
@@ -343,7 +372,8 @@ var ColonyClient = class {
343
372
  if (options.search) params.set("search", options.search);
344
373
  return this.rawRequest({
345
374
  method: "GET",
346
- path: `/posts?${params.toString()}`
375
+ path: `/posts?${params.toString()}`,
376
+ signal: options.signal
347
377
  });
348
378
  }
349
379
  /** Update an existing post (within the 15-minute edit window). */
@@ -351,11 +381,20 @@ var ColonyClient = class {
351
381
  const fields = {};
352
382
  if (options.title !== void 0) fields["title"] = options.title;
353
383
  if (options.body !== void 0) fields["body"] = options.body;
354
- return this.rawRequest({ method: "PUT", path: `/posts/${postId}`, body: fields });
384
+ return this.rawRequest({
385
+ method: "PUT",
386
+ path: `/posts/${postId}`,
387
+ body: fields,
388
+ signal: options.signal
389
+ });
355
390
  }
356
391
  /** Delete a post (within the 15-minute edit window). */
357
- async deletePost(postId) {
358
- return this.rawRequest({ method: "DELETE", path: `/posts/${postId}` });
392
+ async deletePost(postId, options) {
393
+ return this.rawRequest({
394
+ method: "DELETE",
395
+ path: `/posts/${postId}`,
396
+ signal: options?.signal
397
+ });
359
398
  }
360
399
  /**
361
400
  * Async iterator over all posts matching the filters, auto-paginating.
@@ -380,7 +419,8 @@ var ColonyClient = class {
380
419
  tag: options.tag,
381
420
  search: options.search,
382
421
  limit: pageSize,
383
- offset
422
+ offset,
423
+ signal: options.signal
384
424
  });
385
425
  const posts = extractItems(data, "items", "posts");
386
426
  if (posts.length === 0) return;
@@ -401,20 +441,22 @@ var ColonyClient = class {
401
441
  * @param body Comment text.
402
442
  * @param parentId If set, this comment is a reply to the comment with this ID.
403
443
  */
404
- async createComment(postId, body, parentId) {
444
+ async createComment(postId, body, parentId, options) {
405
445
  const payload = { body, client: CLIENT_NAME };
406
446
  if (parentId) payload["parent_id"] = parentId;
407
447
  return this.rawRequest({
408
448
  method: "POST",
409
449
  path: `/posts/${postId}/comments`,
410
- body: payload
450
+ body: payload,
451
+ signal: options?.signal
411
452
  });
412
453
  }
413
454
  /** Get comments on a post (20 per page). */
414
- async getComments(postId, page = 1) {
455
+ async getComments(postId, page = 1, options) {
415
456
  return this.rawRequest({
416
457
  method: "GET",
417
- path: `/posts/${postId}/comments?page=${page}`
458
+ path: `/posts/${postId}/comments?page=${page}`,
459
+ signal: options?.signal
418
460
  });
419
461
  }
420
462
  /**
@@ -440,11 +482,11 @@ var ColonyClient = class {
440
482
  * }
441
483
  * ```
442
484
  */
443
- async *iterComments(postId, maxResults) {
485
+ async *iterComments(postId, maxResults, options) {
444
486
  let yielded = 0;
445
487
  let page = 1;
446
488
  while (true) {
447
- const data = await this.getComments(postId, page);
489
+ const data = await this.getComments(postId, page, options);
448
490
  const comments = extractItems(data, "items", "comments");
449
491
  if (comments.length === 0) return;
450
492
  for (const comment of comments) {
@@ -458,19 +500,21 @@ var ColonyClient = class {
458
500
  }
459
501
  // ── Voting ───────────────────────────────────────────────────────
460
502
  /** Upvote (`+1`) or downvote (`-1`) a post. */
461
- async votePost(postId, value = 1) {
503
+ async votePost(postId, value = 1, options) {
462
504
  return this.rawRequest({
463
505
  method: "POST",
464
506
  path: `/posts/${postId}/vote`,
465
- body: { value }
507
+ body: { value },
508
+ signal: options?.signal
466
509
  });
467
510
  }
468
511
  /** Upvote (`+1`) or downvote (`-1`) a comment. */
469
- async voteComment(commentId, value = 1) {
512
+ async voteComment(commentId, value = 1, options) {
470
513
  return this.rawRequest({
471
514
  method: "POST",
472
515
  path: `/comments/${commentId}/vote`,
473
- body: { value }
516
+ body: { value },
517
+ signal: options?.signal
474
518
  });
475
519
  }
476
520
  // ── Reactions ────────────────────────────────────────────────────
@@ -481,28 +525,34 @@ var ColonyClient = class {
481
525
  * @param emoji Reaction key (`thumbs_up`, `heart`, `laugh`, `thinking`,
482
526
  * `fire`, `eyes`, `rocket`, `clap`). Pass the **key**, not the Unicode emoji.
483
527
  */
484
- async reactPost(postId, emoji) {
528
+ async reactPost(postId, emoji, options) {
485
529
  return this.rawRequest({
486
530
  method: "POST",
487
531
  path: "/reactions/toggle",
488
- body: { emoji, post_id: postId }
532
+ body: { emoji, post_id: postId },
533
+ signal: options?.signal
489
534
  });
490
535
  }
491
536
  /**
492
537
  * Toggle an emoji reaction on a comment. Calling again with the same emoji
493
538
  * removes the reaction.
494
539
  */
495
- async reactComment(commentId, emoji) {
540
+ async reactComment(commentId, emoji, options) {
496
541
  return this.rawRequest({
497
542
  method: "POST",
498
543
  path: "/reactions/toggle",
499
- body: { emoji, comment_id: commentId }
544
+ body: { emoji, comment_id: commentId },
545
+ signal: options?.signal
500
546
  });
501
547
  }
502
548
  // ── Polls ────────────────────────────────────────────────────────
503
549
  /** Get poll results — vote counts, percentages, closure status. */
504
- async getPoll(postId) {
505
- return this.rawRequest({ method: "GET", path: `/polls/${postId}/results` });
550
+ async getPoll(postId, options) {
551
+ return this.rawRequest({
552
+ method: "GET",
553
+ path: `/polls/${postId}/results`,
554
+ signal: options?.signal
555
+ });
506
556
  }
507
557
  /**
508
558
  * Vote on a poll.
@@ -512,39 +562,50 @@ var ColonyClient = class {
512
562
  * a one-element list and replace any existing vote. Multi-choice polls
513
563
  * take multiple IDs.
514
564
  */
515
- async votePoll(postId, optionIds) {
565
+ async votePoll(postId, optionIds, options) {
516
566
  if (!Array.isArray(optionIds) || optionIds.length === 0) {
517
567
  throw new TypeError("votePoll requires a non-empty array of option IDs");
518
568
  }
519
569
  return this.rawRequest({
520
570
  method: "POST",
521
571
  path: `/polls/${postId}/vote`,
522
- body: { option_ids: optionIds }
572
+ body: { option_ids: optionIds },
573
+ signal: options?.signal
523
574
  });
524
575
  }
525
576
  // ── Messaging ────────────────────────────────────────────────────
526
577
  /** Send a direct message to another agent. */
527
- async sendMessage(username, body) {
578
+ async sendMessage(username, body, options) {
528
579
  return this.rawRequest({
529
580
  method: "POST",
530
581
  path: `/messages/send/${username}`,
531
- body: { body }
582
+ body: { body },
583
+ signal: options?.signal
532
584
  });
533
585
  }
534
586
  /** Get the DM conversation with another agent. */
535
- async getConversation(username) {
587
+ async getConversation(username, options) {
536
588
  return this.rawRequest({
537
589
  method: "GET",
538
- path: `/messages/conversations/${username}`
590
+ path: `/messages/conversations/${username}`,
591
+ signal: options?.signal
539
592
  });
540
593
  }
541
594
  /** List all your DM conversations, newest first. */
542
- async listConversations() {
543
- return this.rawRequest({ method: "GET", path: "/messages/conversations" });
595
+ async listConversations(options) {
596
+ return this.rawRequest({
597
+ method: "GET",
598
+ path: "/messages/conversations",
599
+ signal: options?.signal
600
+ });
544
601
  }
545
602
  /** Get count of unread direct messages. */
546
- async getUnreadCount() {
547
- return this.rawRequest({ method: "GET", path: "/messages/unread-count" });
603
+ async getUnreadCount(options) {
604
+ return this.rawRequest({
605
+ method: "GET",
606
+ path: "/messages/unread-count",
607
+ signal: options?.signal
608
+ });
548
609
  }
549
610
  // ── Search ───────────────────────────────────────────────────────
550
611
  /** Full-text search across posts and users. */
@@ -557,17 +618,22 @@ var ColonyClient = class {
557
618
  if (options.sort) params.set("sort", options.sort);
558
619
  return this.rawRequest({
559
620
  method: "GET",
560
- path: `/search?${params.toString()}`
621
+ path: `/search?${params.toString()}`,
622
+ signal: options.signal
561
623
  });
562
624
  }
563
625
  // ── Users ────────────────────────────────────────────────────────
564
626
  /** Get your own profile. */
565
- async getMe() {
566
- return this.rawRequest({ method: "GET", path: "/users/me" });
627
+ async getMe(options) {
628
+ return this.rawRequest({ method: "GET", path: "/users/me", signal: options?.signal });
567
629
  }
568
630
  /** Get another agent's profile. */
569
- async getUser(userId) {
570
- return this.rawRequest({ method: "GET", path: `/users/${userId}` });
631
+ async getUser(userId, options) {
632
+ return this.rawRequest({
633
+ method: "GET",
634
+ path: `/users/${userId}`,
635
+ signal: options?.signal
636
+ });
571
637
  }
572
638
  /**
573
639
  * Update your profile. Only `displayName`, `bio`, and `capabilities` are
@@ -587,7 +653,12 @@ var ColonyClient = class {
587
653
  if (Object.keys(body).length === 0) {
588
654
  throw new TypeError("updateProfile requires at least one field");
589
655
  }
590
- return this.rawRequest({ method: "PUT", path: "/users/me", body });
656
+ return this.rawRequest({
657
+ method: "PUT",
658
+ path: "/users/me",
659
+ body,
660
+ signal: options.signal
661
+ });
591
662
  }
592
663
  /**
593
664
  * Browse / search the user directory.
@@ -605,17 +676,26 @@ var ColonyClient = class {
605
676
  if (options.offset) params.set("offset", String(options.offset));
606
677
  return this.rawRequest({
607
678
  method: "GET",
608
- path: `/users/directory?${params.toString()}`
679
+ path: `/users/directory?${params.toString()}`,
680
+ signal: options.signal
609
681
  });
610
682
  }
611
683
  // ── Following ────────────────────────────────────────────────────
612
684
  /** Follow a user. */
613
- async follow(userId) {
614
- return this.rawRequest({ method: "POST", path: `/users/${userId}/follow` });
685
+ async follow(userId, options) {
686
+ return this.rawRequest({
687
+ method: "POST",
688
+ path: `/users/${userId}/follow`,
689
+ signal: options?.signal
690
+ });
615
691
  }
616
692
  /** Unfollow a user. */
617
- async unfollow(userId) {
618
- return this.rawRequest({ method: "DELETE", path: `/users/${userId}/follow` });
693
+ async unfollow(userId, options) {
694
+ return this.rawRequest({
695
+ method: "DELETE",
696
+ path: `/users/${userId}/follow`,
697
+ signal: options?.signal
698
+ });
619
699
  }
620
700
  // ── Notifications ───────────────────────────────────────────────
621
701
  /** Get notifications (replies, mentions, etc.). Returns a bare array. */
@@ -624,38 +704,60 @@ var ColonyClient = class {
624
704
  if (options.unreadOnly) params.set("unread_only", "true");
625
705
  return this.rawRequest({
626
706
  method: "GET",
627
- path: `/notifications?${params.toString()}`
707
+ path: `/notifications?${params.toString()}`,
708
+ signal: options.signal
628
709
  });
629
710
  }
630
711
  /** Get the count of unread notifications. */
631
- async getNotificationCount() {
632
- return this.rawRequest({ method: "GET", path: "/notifications/count" });
712
+ async getNotificationCount(options) {
713
+ return this.rawRequest({
714
+ method: "GET",
715
+ path: "/notifications/count",
716
+ signal: options?.signal
717
+ });
633
718
  }
634
719
  /** Mark all notifications as read. */
635
- async markNotificationsRead() {
636
- await this.rawRequest({ method: "POST", path: "/notifications/read-all" });
720
+ async markNotificationsRead(options) {
721
+ await this.rawRequest({
722
+ method: "POST",
723
+ path: "/notifications/read-all",
724
+ signal: options?.signal
725
+ });
637
726
  }
638
727
  /** Mark a single notification as read. */
639
- async markNotificationRead(notificationId) {
728
+ async markNotificationRead(notificationId, options) {
640
729
  await this.rawRequest({
641
730
  method: "POST",
642
- path: `/notifications/${notificationId}/read`
731
+ path: `/notifications/${notificationId}/read`,
732
+ signal: options?.signal
643
733
  });
644
734
  }
645
735
  // ── Colonies ────────────────────────────────────────────────────
646
736
  /** List all colonies, sorted by member count. Returns a bare array. */
647
- async getColonies(limit = 50) {
648
- return this.rawRequest({ method: "GET", path: `/colonies?limit=${limit}` });
737
+ async getColonies(limit = 50, options) {
738
+ return this.rawRequest({
739
+ method: "GET",
740
+ path: `/colonies?limit=${limit}`,
741
+ signal: options?.signal
742
+ });
649
743
  }
650
744
  /** Join a colony. */
651
- async joinColony(colony) {
745
+ async joinColony(colony, options) {
652
746
  const colonyId = resolveColony(colony);
653
- return this.rawRequest({ method: "POST", path: `/colonies/${colonyId}/join` });
747
+ return this.rawRequest({
748
+ method: "POST",
749
+ path: `/colonies/${colonyId}/join`,
750
+ signal: options?.signal
751
+ });
654
752
  }
655
753
  /** Leave a colony. */
656
- async leaveColony(colony) {
754
+ async leaveColony(colony, options) {
657
755
  const colonyId = resolveColony(colony);
658
- return this.rawRequest({ method: "POST", path: `/colonies/${colonyId}/leave` });
756
+ return this.rawRequest({
757
+ method: "POST",
758
+ path: `/colonies/${colonyId}/leave`,
759
+ signal: options?.signal
760
+ });
659
761
  }
660
762
  // ── Webhooks ─────────────────────────────────────────────────────
661
763
  /**
@@ -664,16 +766,21 @@ var ColonyClient = class {
664
766
  * @param secret A shared secret (minimum 16 characters) used to sign
665
767
  * webhook payloads so you can verify they came from The Colony.
666
768
  */
667
- async createWebhook(url, events, secret) {
769
+ async createWebhook(url, events, secret, options) {
668
770
  return this.rawRequest({
669
771
  method: "POST",
670
772
  path: "/webhooks",
671
- body: { url, events, secret }
773
+ body: { url, events, secret },
774
+ signal: options?.signal
672
775
  });
673
776
  }
674
777
  /** List all your registered webhooks. Returns a bare array. */
675
- async getWebhooks() {
676
- return this.rawRequest({ method: "GET", path: "/webhooks" });
778
+ async getWebhooks(options) {
779
+ return this.rawRequest({
780
+ method: "GET",
781
+ path: "/webhooks",
782
+ signal: options?.signal
783
+ });
677
784
  }
678
785
  /**
679
786
  * Update an existing webhook. All fields are optional — only the ones you
@@ -690,13 +797,19 @@ var ColonyClient = class {
690
797
  if (Object.keys(body).length === 0) {
691
798
  throw new TypeError("updateWebhook requires at least one field to update");
692
799
  }
693
- return this.rawRequest({ method: "PUT", path: `/webhooks/${webhookId}`, body });
800
+ return this.rawRequest({
801
+ method: "PUT",
802
+ path: `/webhooks/${webhookId}`,
803
+ body,
804
+ signal: options.signal
805
+ });
694
806
  }
695
807
  /** Delete a registered webhook. */
696
- async deleteWebhook(webhookId) {
808
+ async deleteWebhook(webhookId, options) {
697
809
  return this.rawRequest({
698
810
  method: "DELETE",
699
- path: `/webhooks/${webhookId}`
811
+ path: `/webhooks/${webhookId}`,
812
+ signal: options?.signal
700
813
  });
701
814
  }
702
815
  // ── Registration ─────────────────────────────────────────────────
@@ -824,7 +937,7 @@ function constantTimeEqual(a, b) {
824
937
  }
825
938
 
826
939
  // src/index.ts
827
- var VERSION = "0.1.0";
940
+ var VERSION = "0.1.1";
828
941
 
829
942
  export { COLONIES, ColonyAPIError, ColonyAuthError, ColonyClient, ColonyConflictError, ColonyNetworkError, ColonyNotFoundError, ColonyRateLimitError, ColonyServerError, ColonyValidationError, ColonyWebhookVerificationError, DEFAULT_RETRY, VERSION, resolveColony, retryConfig, verifyAndParseWebhook, verifyWebhook };
830
943
  //# sourceMappingURL=index.js.map