@boldvideo/bold-js 1.17.0 → 1.18.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 CHANGED
@@ -1,5 +1,34 @@
1
1
  # @boldvideo/bold-js
2
2
 
3
+ ## 1.18.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 48d8b40: Add Community API for posts, comments, and reactions
8
+
9
+ New `bold.community` namespace with full CRUD operations:
10
+
11
+ **Posts:**
12
+
13
+ - `bold.community.posts.list(opts?)` - List posts with category/limit/offset filters
14
+ - `bold.community.posts.get(id, viewerId?)` - Get single post with comments
15
+ - `bold.community.posts.create(viewerId, data)` - Create a post (markdown supported)
16
+ - `bold.community.posts.update(viewerId, id, data)` - Update post (owner/admin only)
17
+ - `bold.community.posts.delete(viewerId, id)` - Delete post (owner/admin only)
18
+ - `bold.community.posts.react(viewerId, id)` - Toggle reaction (like/unlike)
19
+
20
+ **Comments:**
21
+
22
+ - `bold.community.comments.create(viewerId, postId, data)` - Create comment with optional `parentId` for nested replies
23
+ - `bold.community.comments.delete(viewerId, id)` - Delete comment (owner/admin only)
24
+ - `bold.community.comments.react(viewerId, id)` - Toggle reaction
25
+
26
+ **New Types:**
27
+
28
+ - `Post`, `PostAuthor`, `Comment`, `ReactionResponse`
29
+ - `ListPostsOptions`, `CreatePostData`, `UpdatePostData`, `CreateCommentData`
30
+ - `CommunityAPIError` - Error class for Community API operations
31
+
3
32
  ## 1.17.0
4
33
 
5
34
  ### Minor Changes
package/README.md CHANGED
@@ -178,6 +178,69 @@ console.log(`Completed ${meta.completed} of ${meta.total} videos`);
178
178
 
179
179
  ---
180
180
 
181
+ ## Community API
182
+
183
+ Build community features with posts, comments, and reactions. All write operations require a `viewerId` (the viewer performing the action).
184
+
185
+ ### Posts
186
+
187
+ ```typescript
188
+ // List posts (optionally filter by category)
189
+ const { data: posts } = await bold.community.posts.list({
190
+ category: 'announcements',
191
+ limit: 20,
192
+ offset: 0,
193
+ viewerId: 'viewer-uuid' // Include viewerReacted in response
194
+ });
195
+
196
+ // Get a single post with comments
197
+ const { data: post } = await bold.community.posts.get('post-id', 'viewer-uuid');
198
+
199
+ // Create a post (requires viewerId)
200
+ const { data: newPost } = await bold.community.posts.create('viewer-uuid', {
201
+ content: 'Hello community! **Markdown** supported.',
202
+ category: 'general'
203
+ });
204
+
205
+ // Update a post (owner or admin only)
206
+ await bold.community.posts.update('viewer-uuid', 'post-id', {
207
+ content: 'Updated content'
208
+ });
209
+
210
+ // Delete a post (owner or admin only)
211
+ await bold.community.posts.delete('viewer-uuid', 'post-id');
212
+
213
+ // React to a post (toggle like/unlike)
214
+ const reaction = await bold.community.posts.react('viewer-uuid', 'post-id');
215
+ console.log(reaction.reacted, reaction.reactionsCount);
216
+ ```
217
+
218
+ ### Comments
219
+
220
+ ```typescript
221
+ // Create a comment on a post
222
+ const { data: comment } = await bold.community.comments.create(
223
+ 'viewer-uuid',
224
+ 'post-id',
225
+ { content: 'Great post!' }
226
+ );
227
+
228
+ // Reply to a comment (nested)
229
+ const { data: reply } = await bold.community.comments.create(
230
+ 'viewer-uuid',
231
+ 'post-id',
232
+ { content: 'I agree!', parentId: 'parent-comment-id' }
233
+ );
234
+
235
+ // Delete a comment (owner or admin only)
236
+ await bold.community.comments.delete('viewer-uuid', 'comment-id');
237
+
238
+ // React to a comment (toggle)
239
+ const reaction = await bold.community.comments.react('viewer-uuid', 'comment-id');
240
+ ```
241
+
242
+ ---
243
+
181
244
  ## AI Methods
182
245
 
183
246
  All AI methods support both streaming (default) and non-streaming modes.
@@ -378,7 +441,16 @@ import type {
378
441
  ListProgressOptions,
379
442
  ListVideosOptions,
380
443
  ListVideosLatestOptions,
381
- ListVideosIndexOptions
444
+ ListVideosIndexOptions,
445
+ // Community API
446
+ Post,
447
+ PostAuthor,
448
+ Comment,
449
+ ReactionResponse,
450
+ ListPostsOptions,
451
+ CreatePostData,
452
+ UpdatePostData,
453
+ CreateCommentData
382
454
  } from '@boldvideo/bold-js';
383
455
  ```
384
456
 
package/dist/index.cjs CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
+ CommunityAPIError: () => CommunityAPIError,
33
34
  DEFAULT_API_BASE_URL: () => DEFAULT_API_BASE_URL,
34
35
  DEFAULT_INTERNAL_API_BASE_URL: () => DEFAULT_INTERNAL_API_BASE_URL,
35
36
  ViewerAPIError: () => ViewerAPIError,
@@ -38,7 +39,7 @@ __export(src_exports, {
38
39
  module.exports = __toCommonJS(src_exports);
39
40
 
40
41
  // src/lib/client.ts
41
- var import_axios2 = __toESM(require("axios"), 1);
42
+ var import_axios3 = __toESM(require("axios"), 1);
42
43
 
43
44
  // src/util/camelize.ts
44
45
  var isPlainObject = (value) => value !== null && typeof value === "object" && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
@@ -330,6 +331,188 @@ function saveProgress(client) {
330
331
  };
331
332
  }
332
333
 
334
+ // src/lib/community.ts
335
+ var import_axios2 = require("axios");
336
+ var CommunityAPIError = class extends Error {
337
+ constructor(method, url, error) {
338
+ var __super = (...args) => {
339
+ super(...args);
340
+ };
341
+ if (error instanceof import_axios2.AxiosError) {
342
+ const status = error.response?.status;
343
+ const message = error.response?.data?.error || error.message;
344
+ __super(`${method} ${url} failed (${status}): ${message}`);
345
+ this.status = status;
346
+ this.originalError = error;
347
+ } else if (error instanceof Error) {
348
+ __super(`${method} ${url} failed: ${error.message}`);
349
+ this.originalError = error;
350
+ } else {
351
+ __super(`${method} ${url} failed: ${String(error)}`);
352
+ }
353
+ this.name = "CommunityAPIError";
354
+ }
355
+ };
356
+ function requireViewerId(viewerId) {
357
+ if (!viewerId)
358
+ throw new Error("Viewer ID is required (X-Viewer-ID)");
359
+ }
360
+ function viewerHeaders(viewerId) {
361
+ return viewerId ? { "X-Viewer-ID": viewerId } : void 0;
362
+ }
363
+ function toQuery2(params) {
364
+ const qs = new URLSearchParams();
365
+ for (const [k, v] of Object.entries(params)) {
366
+ if (v !== void 0 && v !== null)
367
+ qs.set(k, String(v));
368
+ }
369
+ const s = qs.toString();
370
+ return s ? `?${s}` : "";
371
+ }
372
+ async function get3(client, url, viewerId) {
373
+ try {
374
+ const res = await client.get(url, { headers: viewerHeaders(viewerId) });
375
+ return camelizeKeys(res.data);
376
+ } catch (error) {
377
+ throw new CommunityAPIError("GET", url, error);
378
+ }
379
+ }
380
+ async function post2(client, url, data, viewerId) {
381
+ try {
382
+ const res = await client.post(url, data, { headers: viewerHeaders(viewerId) });
383
+ return camelizeKeys(res.data);
384
+ } catch (error) {
385
+ throw new CommunityAPIError("POST", url, error);
386
+ }
387
+ }
388
+ async function put(client, url, data, viewerId) {
389
+ try {
390
+ const res = await client.put(url, data, { headers: viewerHeaders(viewerId) });
391
+ return camelizeKeys(res.data);
392
+ } catch (error) {
393
+ throw new CommunityAPIError("PUT", url, error);
394
+ }
395
+ }
396
+ async function del(client, url, viewerId) {
397
+ try {
398
+ const res = await client.delete(url, { headers: viewerHeaders(viewerId) });
399
+ return camelizeKeys(res.data);
400
+ } catch (error) {
401
+ throw new CommunityAPIError("DELETE", url, error);
402
+ }
403
+ }
404
+ function listPosts(client) {
405
+ return async (opts = {}) => {
406
+ return get3(
407
+ client,
408
+ `community/posts${toQuery2({
409
+ category: opts.category,
410
+ limit: opts.limit,
411
+ offset: opts.offset
412
+ })}`,
413
+ opts.viewerId
414
+ );
415
+ };
416
+ }
417
+ function getPost(client) {
418
+ return async (id, viewerId) => {
419
+ if (!id)
420
+ throw new Error("Post ID is required");
421
+ return get3(client, `community/posts/${id}`, viewerId);
422
+ };
423
+ }
424
+ function createPost(client) {
425
+ return async (viewerId, data) => {
426
+ requireViewerId(viewerId);
427
+ if (!data?.content)
428
+ throw new Error("Post content is required");
429
+ return post2(
430
+ client,
431
+ "community/posts",
432
+ { post: { content: data.content, category: data.category } },
433
+ viewerId
434
+ );
435
+ };
436
+ }
437
+ function updatePost(client) {
438
+ return async (viewerId, id, data) => {
439
+ requireViewerId(viewerId);
440
+ if (!id)
441
+ throw new Error("Post ID is required");
442
+ const body = {};
443
+ if (data.content !== void 0)
444
+ body.content = data.content;
445
+ if (data.category !== void 0)
446
+ body.category = data.category;
447
+ return put(
448
+ client,
449
+ `community/posts/${id}`,
450
+ { post: body },
451
+ viewerId
452
+ );
453
+ };
454
+ }
455
+ function deletePost(client) {
456
+ return async (viewerId, id) => {
457
+ requireViewerId(viewerId);
458
+ if (!id)
459
+ throw new Error("Post ID is required");
460
+ return del(client, `community/posts/${id}`, viewerId);
461
+ };
462
+ }
463
+ function reactToPost(client) {
464
+ return async (viewerId, id) => {
465
+ requireViewerId(viewerId);
466
+ if (!id)
467
+ throw new Error("Post ID is required");
468
+ return post2(
469
+ client,
470
+ `community/posts/${id}/react`,
471
+ void 0,
472
+ viewerId
473
+ );
474
+ };
475
+ }
476
+ function createComment(client) {
477
+ return async (viewerId, postId, data) => {
478
+ requireViewerId(viewerId);
479
+ if (!postId)
480
+ throw new Error("Post ID is required");
481
+ if (!data?.content)
482
+ throw new Error("Comment content is required");
483
+ const body = { content: data.content };
484
+ if (data.parentId)
485
+ body.parent_id = data.parentId;
486
+ return post2(
487
+ client,
488
+ `community/posts/${postId}/comments`,
489
+ { comment: body },
490
+ viewerId
491
+ );
492
+ };
493
+ }
494
+ function deleteComment(client) {
495
+ return async (viewerId, id) => {
496
+ requireViewerId(viewerId);
497
+ if (!id)
498
+ throw new Error("Comment ID is required");
499
+ return del(client, `community/comments/${id}`, viewerId);
500
+ };
501
+ }
502
+ function reactToComment(client) {
503
+ return async (viewerId, id) => {
504
+ requireViewerId(viewerId);
505
+ if (!id)
506
+ throw new Error("Comment ID is required");
507
+ return post2(
508
+ client,
509
+ `community/comments/${id}/react`,
510
+ void 0,
511
+ viewerId
512
+ );
513
+ };
514
+ }
515
+
333
516
  // src/util/throttle.ts
334
517
  var throttle = (fn, delay) => {
335
518
  let wait = false;
@@ -631,7 +814,7 @@ function createClient(apiKey, options = {}) {
631
814
  };
632
815
  let apiClient;
633
816
  try {
634
- apiClient = import_axios2.default.create(apiClientOptions);
817
+ apiClient = import_axios3.default.create(apiClientOptions);
635
818
  } catch (error) {
636
819
  console.error("Error creating API client", error);
637
820
  throw error;
@@ -663,12 +846,28 @@ function createClient(apiKey, options = {}) {
663
846
  saveProgress: saveProgress(apiClient)
664
847
  },
665
848
  ai: createAI(aiConfig),
849
+ community: {
850
+ posts: {
851
+ list: listPosts(apiClient),
852
+ get: getPost(apiClient),
853
+ create: createPost(apiClient),
854
+ update: updatePost(apiClient),
855
+ delete: deletePost(apiClient),
856
+ react: reactToPost(apiClient)
857
+ },
858
+ comments: {
859
+ create: createComment(apiClient),
860
+ delete: deleteComment(apiClient),
861
+ react: reactToComment(apiClient)
862
+ }
863
+ },
666
864
  trackEvent: trackEvent(apiClient, userId, { debug }),
667
865
  trackPageView: trackPageView(apiClient, userId, { debug })
668
866
  };
669
867
  }
670
868
  // Annotate the CommonJS export names for ESM import in node:
671
869
  0 && (module.exports = {
870
+ CommunityAPIError,
672
871
  DEFAULT_API_BASE_URL,
673
872
  DEFAULT_INTERNAL_API_BASE_URL,
674
873
  ViewerAPIError,
package/dist/index.d.ts CHANGED
@@ -511,6 +511,98 @@ type ListVideosOptions = (ListVideosLatestOptions & {
511
511
  limit?: never;
512
512
  viewerId?: never;
513
513
  });
514
+ /**
515
+ * Author information for posts and comments
516
+ */
517
+ type PostAuthor = {
518
+ id: string;
519
+ name: string;
520
+ email?: string;
521
+ isAdmin: boolean;
522
+ };
523
+ /**
524
+ * A comment on a community post
525
+ */
526
+ type Comment = {
527
+ id: string;
528
+ content: string;
529
+ /** Nesting depth (0 = top-level) */
530
+ depth: number;
531
+ reactionsCount: number;
532
+ /** Whether current viewer has reacted (only present when X-Viewer-ID header sent) */
533
+ viewerReacted?: boolean;
534
+ viewer: PostAuthor;
535
+ /** Nested replies */
536
+ replies: Comment[];
537
+ createdAt: string;
538
+ updatedAt: string;
539
+ };
540
+ /**
541
+ * A community post
542
+ */
543
+ type Post = {
544
+ id: string;
545
+ content: string;
546
+ category: string;
547
+ pinned: boolean;
548
+ pinnedAt?: string | null;
549
+ reactionsCount: number;
550
+ commentsCount: number;
551
+ /** Whether current viewer has reacted (only present when X-Viewer-ID header sent) */
552
+ viewerReacted?: boolean;
553
+ viewer: PostAuthor;
554
+ /** Comments (only present on single post fetch) */
555
+ comments?: Comment[];
556
+ createdAt: string;
557
+ updatedAt: string;
558
+ };
559
+ /**
560
+ * Response from react endpoints (toggle)
561
+ */
562
+ type ReactionResponse = {
563
+ reacted: boolean;
564
+ reactionsCount: number;
565
+ };
566
+ /**
567
+ * Options for listing community posts
568
+ */
569
+ type ListPostsOptions = {
570
+ /** Filter by category (e.g., "announcements", "general") */
571
+ category?: string;
572
+ /** Max posts to return (default: 20) */
573
+ limit?: number;
574
+ /** Pagination offset */
575
+ offset?: number;
576
+ /** Viewer ID to include viewerReacted in response */
577
+ viewerId?: string;
578
+ };
579
+ /**
580
+ * Data for creating a community post
581
+ */
582
+ type CreatePostData = {
583
+ /** Post content (supports markdown) */
584
+ content: string;
585
+ /** Category for the post */
586
+ category?: string;
587
+ };
588
+ /**
589
+ * Data for updating a community post
590
+ */
591
+ type UpdatePostData = {
592
+ /** Post content (supports markdown) */
593
+ content?: string;
594
+ /** Category for the post */
595
+ category?: string;
596
+ };
597
+ /**
598
+ * Data for creating a comment
599
+ */
600
+ type CreateCommentData = {
601
+ /** Comment content */
602
+ content: string;
603
+ /** Parent comment ID for replies */
604
+ parentId?: string;
605
+ };
514
606
 
515
607
  /**
516
608
  * AI client interface for type-safe method overloading
@@ -693,6 +785,35 @@ declare function createClient(apiKey: string, options?: ClientOptions): {
693
785
  }>;
694
786
  };
695
787
  ai: AIClient;
788
+ community: {
789
+ posts: {
790
+ list: (opts?: ListPostsOptions) => Promise<{
791
+ data: Post[];
792
+ }>;
793
+ get: (id: string, viewerId?: string | undefined) => Promise<{
794
+ data: Post;
795
+ }>;
796
+ create: (viewerId: string, data: CreatePostData) => Promise<{
797
+ data: Post;
798
+ }>;
799
+ update: (viewerId: string, id: string, data: UpdatePostData) => Promise<{
800
+ data: Post;
801
+ }>;
802
+ delete: (viewerId: string, id: string) => Promise<{
803
+ data?: unknown;
804
+ }>;
805
+ react: (viewerId: string, id: string) => Promise<ReactionResponse>;
806
+ };
807
+ comments: {
808
+ create: (viewerId: string, postId: string, data: CreateCommentData) => Promise<{
809
+ data: Comment;
810
+ }>;
811
+ delete: (viewerId: string, id: string) => Promise<{
812
+ data?: unknown;
813
+ }>;
814
+ react: (viewerId: string, id: string) => Promise<ReactionResponse>;
815
+ };
816
+ };
696
817
  trackEvent: (video: any, event: Event) => void;
697
818
  trackPageView: (title: string) => void;
698
819
  };
@@ -706,4 +827,13 @@ declare const DEFAULT_API_BASE_URL = "https://app.boldvideo.io/api/v1/";
706
827
  */
707
828
  declare const DEFAULT_INTERNAL_API_BASE_URL = "https://app.boldvideo.io/i/v1/";
708
829
 
709
- export { AIContextMessage, AIEvent, AIResponse, AIUsage, Account, AccountAI, AnalyticsProvider, AskOptions, AssistantConfig, ChatOptions, Citation, ClientOptions, Conversation, ConversationMessage, ConversationMetadata, CreateViewerData, CustomRedirect, DEFAULT_API_BASE_URL, DEFAULT_INTERNAL_API_BASE_URL, ListProgressOptions, ListVideosIndexOptions, ListVideosLatestOptions, ListVideosOptions, MenuItem, Playlist, Portal, PortalDisplay, PortalHero, PortalLayout, PortalNavigation, PortalTheme, ProgressListMeta, RecommendOptions, RecommendResponse, Recommendation, RecommendationVideo, RecommendationsOptions, RecommendationsResponse, SaveProgressData, SearchOptions, Segment, Settings, Source, ThemeColors, ThemeConfig, UpdateViewerData, Video, VideoAttachment, VideoDownloadUrls, VideoMetadata, VideoSubtitles, VideoTranscript, Viewer, ViewerAPIError, ViewerLookupParams, ViewerProgress, createClient };
830
+ /**
831
+ * Error thrown by Community API operations
832
+ */
833
+ declare class CommunityAPIError extends Error {
834
+ readonly status?: number;
835
+ readonly originalError?: Error;
836
+ constructor(method: string, url: string, error: unknown);
837
+ }
838
+
839
+ export { AIContextMessage, AIEvent, AIResponse, AIUsage, Account, AccountAI, AnalyticsProvider, AskOptions, AssistantConfig, ChatOptions, Citation, ClientOptions, Comment, CommunityAPIError, Conversation, ConversationMessage, ConversationMetadata, CreateCommentData, CreatePostData, CreateViewerData, CustomRedirect, DEFAULT_API_BASE_URL, DEFAULT_INTERNAL_API_BASE_URL, ListPostsOptions, ListProgressOptions, ListVideosIndexOptions, ListVideosLatestOptions, ListVideosOptions, MenuItem, Playlist, Portal, PortalDisplay, PortalHero, PortalLayout, PortalNavigation, PortalTheme, Post, PostAuthor, ProgressListMeta, ReactionResponse, RecommendOptions, RecommendResponse, Recommendation, RecommendationVideo, RecommendationsOptions, RecommendationsResponse, SaveProgressData, SearchOptions, Segment, Settings, Source, ThemeColors, ThemeConfig, UpdatePostData, UpdateViewerData, Video, VideoAttachment, VideoDownloadUrls, VideoMetadata, VideoSubtitles, VideoTranscript, Viewer, ViewerAPIError, ViewerLookupParams, ViewerProgress, createClient };
package/dist/index.js CHANGED
@@ -291,6 +291,188 @@ function saveProgress(client) {
291
291
  };
292
292
  }
293
293
 
294
+ // src/lib/community.ts
295
+ import { AxiosError as AxiosError2 } from "axios";
296
+ var CommunityAPIError = class extends Error {
297
+ constructor(method, url, error) {
298
+ var __super = (...args) => {
299
+ super(...args);
300
+ };
301
+ if (error instanceof AxiosError2) {
302
+ const status = error.response?.status;
303
+ const message = error.response?.data?.error || error.message;
304
+ __super(`${method} ${url} failed (${status}): ${message}`);
305
+ this.status = status;
306
+ this.originalError = error;
307
+ } else if (error instanceof Error) {
308
+ __super(`${method} ${url} failed: ${error.message}`);
309
+ this.originalError = error;
310
+ } else {
311
+ __super(`${method} ${url} failed: ${String(error)}`);
312
+ }
313
+ this.name = "CommunityAPIError";
314
+ }
315
+ };
316
+ function requireViewerId(viewerId) {
317
+ if (!viewerId)
318
+ throw new Error("Viewer ID is required (X-Viewer-ID)");
319
+ }
320
+ function viewerHeaders(viewerId) {
321
+ return viewerId ? { "X-Viewer-ID": viewerId } : void 0;
322
+ }
323
+ function toQuery2(params) {
324
+ const qs = new URLSearchParams();
325
+ for (const [k, v] of Object.entries(params)) {
326
+ if (v !== void 0 && v !== null)
327
+ qs.set(k, String(v));
328
+ }
329
+ const s = qs.toString();
330
+ return s ? `?${s}` : "";
331
+ }
332
+ async function get3(client, url, viewerId) {
333
+ try {
334
+ const res = await client.get(url, { headers: viewerHeaders(viewerId) });
335
+ return camelizeKeys(res.data);
336
+ } catch (error) {
337
+ throw new CommunityAPIError("GET", url, error);
338
+ }
339
+ }
340
+ async function post2(client, url, data, viewerId) {
341
+ try {
342
+ const res = await client.post(url, data, { headers: viewerHeaders(viewerId) });
343
+ return camelizeKeys(res.data);
344
+ } catch (error) {
345
+ throw new CommunityAPIError("POST", url, error);
346
+ }
347
+ }
348
+ async function put(client, url, data, viewerId) {
349
+ try {
350
+ const res = await client.put(url, data, { headers: viewerHeaders(viewerId) });
351
+ return camelizeKeys(res.data);
352
+ } catch (error) {
353
+ throw new CommunityAPIError("PUT", url, error);
354
+ }
355
+ }
356
+ async function del(client, url, viewerId) {
357
+ try {
358
+ const res = await client.delete(url, { headers: viewerHeaders(viewerId) });
359
+ return camelizeKeys(res.data);
360
+ } catch (error) {
361
+ throw new CommunityAPIError("DELETE", url, error);
362
+ }
363
+ }
364
+ function listPosts(client) {
365
+ return async (opts = {}) => {
366
+ return get3(
367
+ client,
368
+ `community/posts${toQuery2({
369
+ category: opts.category,
370
+ limit: opts.limit,
371
+ offset: opts.offset
372
+ })}`,
373
+ opts.viewerId
374
+ );
375
+ };
376
+ }
377
+ function getPost(client) {
378
+ return async (id, viewerId) => {
379
+ if (!id)
380
+ throw new Error("Post ID is required");
381
+ return get3(client, `community/posts/${id}`, viewerId);
382
+ };
383
+ }
384
+ function createPost(client) {
385
+ return async (viewerId, data) => {
386
+ requireViewerId(viewerId);
387
+ if (!data?.content)
388
+ throw new Error("Post content is required");
389
+ return post2(
390
+ client,
391
+ "community/posts",
392
+ { post: { content: data.content, category: data.category } },
393
+ viewerId
394
+ );
395
+ };
396
+ }
397
+ function updatePost(client) {
398
+ return async (viewerId, id, data) => {
399
+ requireViewerId(viewerId);
400
+ if (!id)
401
+ throw new Error("Post ID is required");
402
+ const body = {};
403
+ if (data.content !== void 0)
404
+ body.content = data.content;
405
+ if (data.category !== void 0)
406
+ body.category = data.category;
407
+ return put(
408
+ client,
409
+ `community/posts/${id}`,
410
+ { post: body },
411
+ viewerId
412
+ );
413
+ };
414
+ }
415
+ function deletePost(client) {
416
+ return async (viewerId, id) => {
417
+ requireViewerId(viewerId);
418
+ if (!id)
419
+ throw new Error("Post ID is required");
420
+ return del(client, `community/posts/${id}`, viewerId);
421
+ };
422
+ }
423
+ function reactToPost(client) {
424
+ return async (viewerId, id) => {
425
+ requireViewerId(viewerId);
426
+ if (!id)
427
+ throw new Error("Post ID is required");
428
+ return post2(
429
+ client,
430
+ `community/posts/${id}/react`,
431
+ void 0,
432
+ viewerId
433
+ );
434
+ };
435
+ }
436
+ function createComment(client) {
437
+ return async (viewerId, postId, data) => {
438
+ requireViewerId(viewerId);
439
+ if (!postId)
440
+ throw new Error("Post ID is required");
441
+ if (!data?.content)
442
+ throw new Error("Comment content is required");
443
+ const body = { content: data.content };
444
+ if (data.parentId)
445
+ body.parent_id = data.parentId;
446
+ return post2(
447
+ client,
448
+ `community/posts/${postId}/comments`,
449
+ { comment: body },
450
+ viewerId
451
+ );
452
+ };
453
+ }
454
+ function deleteComment(client) {
455
+ return async (viewerId, id) => {
456
+ requireViewerId(viewerId);
457
+ if (!id)
458
+ throw new Error("Comment ID is required");
459
+ return del(client, `community/comments/${id}`, viewerId);
460
+ };
461
+ }
462
+ function reactToComment(client) {
463
+ return async (viewerId, id) => {
464
+ requireViewerId(viewerId);
465
+ if (!id)
466
+ throw new Error("Comment ID is required");
467
+ return post2(
468
+ client,
469
+ `community/comments/${id}/react`,
470
+ void 0,
471
+ viewerId
472
+ );
473
+ };
474
+ }
475
+
294
476
  // src/util/throttle.ts
295
477
  var throttle = (fn, delay) => {
296
478
  let wait = false;
@@ -624,11 +806,27 @@ function createClient(apiKey, options = {}) {
624
806
  saveProgress: saveProgress(apiClient)
625
807
  },
626
808
  ai: createAI(aiConfig),
809
+ community: {
810
+ posts: {
811
+ list: listPosts(apiClient),
812
+ get: getPost(apiClient),
813
+ create: createPost(apiClient),
814
+ update: updatePost(apiClient),
815
+ delete: deletePost(apiClient),
816
+ react: reactToPost(apiClient)
817
+ },
818
+ comments: {
819
+ create: createComment(apiClient),
820
+ delete: deleteComment(apiClient),
821
+ react: reactToComment(apiClient)
822
+ }
823
+ },
627
824
  trackEvent: trackEvent(apiClient, userId, { debug }),
628
825
  trackPageView: trackPageView(apiClient, userId, { debug })
629
826
  };
630
827
  }
631
828
  export {
829
+ CommunityAPIError,
632
830
  DEFAULT_API_BASE_URL,
633
831
  DEFAULT_INTERNAL_API_BASE_URL,
634
832
  ViewerAPIError,
package/llms.txt CHANGED
@@ -111,6 +111,33 @@ const { data: progress, meta } = await bold.viewers.listProgress(viewer.id, { co
111
111
  console.log(`Completed ${meta.completed} of ${meta.total} videos`);
112
112
  ```
113
113
 
114
+ ## Community API
115
+
116
+ Build community features with posts, comments, and reactions. Write operations require `viewerId`.
117
+
118
+ ### Posts
119
+
120
+ - `bold.community.posts.list(opts?)` - List posts with optional filters
121
+ - `bold.community.posts.get(id, viewerId?)` - Get post with comments
122
+ - `bold.community.posts.create(viewerId, data)` - Create a post
123
+ - `bold.community.posts.update(viewerId, id, data)` - Update post (owner/admin)
124
+ - `bold.community.posts.delete(viewerId, id)` - Delete post (owner/admin)
125
+ - `bold.community.posts.react(viewerId, id)` - Toggle reaction
126
+
127
+ ### Comments
128
+
129
+ - `bold.community.comments.create(viewerId, postId, data)` - Create comment
130
+ - `bold.community.comments.delete(viewerId, id)` - Delete comment (owner/admin)
131
+ - `bold.community.comments.react(viewerId, id)` - Toggle reaction
132
+
133
+ ```typescript
134
+ // Example
135
+ const { data: posts } = await bold.community.posts.list({ category: 'general', limit: 20 });
136
+ await bold.community.posts.create(viewerId, { content: 'Hello!', category: 'general' });
137
+ await bold.community.posts.react(viewerId, posts[0].id);
138
+ await bold.community.comments.create(viewerId, posts[0].id, { content: 'Great post!' });
139
+ ```
140
+
114
141
  ## AI Methods
115
142
 
116
143
  All AI methods return `AsyncIterable<AIEvent>` (streaming) or `Promise<AIResponse>` (non-streaming).
@@ -275,9 +302,44 @@ type AIEvent =
275
302
  }
276
303
  ```
277
304
 
305
+ ### Post
306
+
307
+ ```typescript
308
+ {
309
+ id: string;
310
+ content: string; // Markdown content
311
+ category: string;
312
+ pinned: boolean;
313
+ pinnedAt?: string | null;
314
+ reactionsCount: number;
315
+ commentsCount: number;
316
+ viewerReacted?: boolean; // Only with X-Viewer-ID
317
+ viewer: PostAuthor;
318
+ comments?: Comment[]; // Only on single post fetch
319
+ createdAt: string;
320
+ updatedAt: string;
321
+ }
322
+ ```
323
+
324
+ ### Comment
325
+
326
+ ```typescript
327
+ {
328
+ id: string;
329
+ content: string;
330
+ depth: number; // Nesting level (0 = top-level)
331
+ reactionsCount: number;
332
+ viewerReacted?: boolean;
333
+ viewer: PostAuthor;
334
+ replies: Comment[]; // Nested replies
335
+ createdAt: string;
336
+ updatedAt: string;
337
+ }
338
+ ```
339
+
278
340
  ### Other types exported
279
341
 
280
- `Playlist`, `Settings`, `Portal`, `MenuItem`, `AIResponse`, `AIUsage`, `AIContextMessage`, `Conversation`, `ConversationMessage`, `RecommendationsResponse`, `Viewer`, `ViewerProgress`, `ViewerLookupParams`, `ListProgressOptions`, `ListVideosOptions`, `ListVideosLatestOptions`, `ListVideosIndexOptions`
342
+ `Playlist`, `Settings`, `Portal`, `MenuItem`, `AIResponse`, `AIUsage`, `AIContextMessage`, `Conversation`, `ConversationMessage`, `RecommendationsResponse`, `Viewer`, `ViewerProgress`, `ViewerLookupParams`, `ListProgressOptions`, `ListVideosOptions`, `ListVideosLatestOptions`, `ListVideosIndexOptions`, `Post`, `PostAuthor`, `Comment`, `ReactionResponse`, `ListPostsOptions`, `CreatePostData`, `UpdatePostData`, `CreateCommentData`, `CommunityAPIError`
281
343
 
282
344
  ## Error Handling
283
345
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@boldvideo/bold-js",
3
3
  "license": "MIT",
4
- "version": "1.17.0",
4
+ "version": "1.18.0",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",