@fastrelay/js-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/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # fastrelay Feed JS SDK
2
+
3
+ JavaScript/TypeScript SDK for [fastrelay](https://fastrelay.io) activity feeds — feeds, activities, reactions, comments, polls, video uploads, moderation, and realtime updates over WebSocket.
4
+
5
+ - Feed-first API: `fastrelay.feed(group, id)`
6
+ - Client-only by design: user (JWT) auth, no server secrets in the app
7
+ - Zero runtime dependencies — native `fetch`, `FormData`, `WebSocket`
8
+ - Realtime subscriptions with automatic reconnect + token refresh
9
+ - Polling fallback for non-realtime environments
10
+ - Direct-to-storage video uploads (tus / Cloudflare Stream)
11
+ - Structured `FastrelayApiError` with rate-limit metadata
12
+
13
+ ## Requirements
14
+
15
+ - Node.js >= 18 (>= 22 for native `WebSocket`, or pass `socketFactory`), or any modern browser.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install @fastrelay/js-sdk
21
+ ```
22
+
23
+ ## Quick start
24
+
25
+ ```ts
26
+ import { FastrelayClient } from '@fastrelay/js-sdk';
27
+
28
+ const fastrelay = new FastrelayClient({
29
+ apiKey: 'your_api_key',
30
+ baseUrl: 'https://api.fastrelay.io',
31
+ });
32
+
33
+ await fastrelay.connectUser({ id: 'john' }, userJwt, {
34
+ upsertUser: true,
35
+ realtime: true,
36
+ tokenProvider: async () => fetchFreshJwtFromYourBackend(),
37
+ });
38
+
39
+ const timeline = fastrelay.feed('timeline', 'john');
40
+
41
+ // Read
42
+ const page = await timeline.getActivities({ limit: 25 });
43
+
44
+ // Write
45
+ await timeline.addActivity({ type: 'post', text: 'Hello world' });
46
+
47
+ // Realtime — returns an unsubscribe function
48
+ const off = timeline.on('activity.created', (event) => {
49
+ console.log('new activity', event.data);
50
+ });
51
+ ```
52
+
53
+ ## Authentication
54
+
55
+ The SDK is client-only: every request is sent with `Authorization: Bearer <user JWT>` once `connectUser()` or `setToken()` has been called. Per-request override via options: `{ auth: 'auto' | 'user' | 'none' }` (`'server'` throws — call server endpoints from your backend).
56
+
57
+ ```ts
58
+ await fastrelay.getActivity('activity_id', { auth: 'user', idempotencyKey: 'key' });
59
+ ```
60
+
61
+ ## Activities, reactions, comments
62
+
63
+ ```ts
64
+ const activity = await fastrelay.addActivity({ feeds: ['user:john'], type: 'post', text: 'hi' });
65
+ await fastrelay.addReaction(activity.id, 'like');
66
+ const comment = await fastrelay.addComment(activity.id, { text: 'Nice!' });
67
+ await fastrelay.addCommentReaction(comment.id, 'like');
68
+ const comments = await fastrelay.listComments(activity.id, { sort: 'top', limit: 10 });
69
+ ```
70
+
71
+ Also available: bookmarks (`addBookmark`/`removeBookmark`/`listBookmarks`), pins (`feed.pinActivity`), polls (`createPoll`, `vote`), follows (`feed.follow`, `feed.listFollowers`), feed members, moderation (`createFlag`, `createMute`), feedback (`submitFeedback`), file upload (`uploadFile`).
72
+
73
+ ## Realtime
74
+
75
+ ```ts
76
+ const realtime = fastrelay.realtime!;
77
+ realtime.onStateChange((state) => console.log(state)); // connecting/connected/reconnecting/disconnected
78
+ realtime.onError((error) => console.warn(error.code, error.message));
79
+ realtime.onVideoStatus((event) => console.log(event.type, event.videoId));
80
+
81
+ const off = realtime.subscribeToFeed('timeline:john', (event) => { ... });
82
+ off(); // unsubscribes (frames are batched over the socket)
83
+ ```
84
+
85
+ Reconnects use exponential backoff with jitter; close code `4003` triggers a token refresh via `tokenProvider`, `4002`/`4029` are fatal. Duplicate `eventId`s are dropped.
86
+
87
+ ## Polling fallback
88
+
89
+ ```ts
90
+ import { FeedPollingService } from '@fastrelay/js-sdk';
91
+
92
+ const polling = new FeedPollingService(fastrelay);
93
+ const stop = polling.pollFeed('timeline', 'john', {
94
+ limit: 25,
95
+ onPage: (page) => render(page.data),
96
+ });
97
+ // polling.pause() / polling.resume() / polling.dispose()
98
+ ```
99
+
100
+ ## Video upload
101
+
102
+ ```ts
103
+ import { uploadVideoBytes } from '@fastrelay/js-sdk';
104
+
105
+ const result = await uploadVideoBytes(fastrelay, {
106
+ data: fileBytes, // Uint8Array | ArrayBuffer
107
+ filename: 'clip.mp4',
108
+ mimeType: 'video/mp4',
109
+ onProgress: ({ fraction }) => console.log(Math.round(fraction * 100), '%'),
110
+ });
111
+ // Wait for realtime `video.ready`, or poll fastrelay.getVideo(result.videoId)
112
+ ```
113
+
114
+ ## Error handling
115
+
116
+ ```ts
117
+ import { FastrelayApiError } from '@fastrelay/js-sdk';
118
+
119
+ try {
120
+ await fastrelay.getActivity('missing');
121
+ } catch (error) {
122
+ if (error instanceof FastrelayApiError) {
123
+ console.log(error.status, error.code, error.message, error.rateLimit);
124
+ }
125
+ }
126
+ ```
127
+
128
+ ## Development
129
+
130
+ ```bash
131
+ npm install
132
+ npm run build # tsc -> dist/
133
+ npm test # node --test (Node 23+ runs TS directly)
134
+ ```
@@ -0,0 +1,159 @@
1
+ import { FastrelayFeed } from './feed.ts';
2
+ import { FastrelayRealtime, type FastrelayRealtimeSocketFactory, type FastrelayTokenProvider } from './realtime.ts';
3
+ import type { CursorPage, FastrelayActivity, FastrelayBookmark, FastrelayComment, FastrelayCommentReaction, FastrelayFeedActivityPin, FastrelayFeedback, FastrelayFile, FastrelayModerationFlag, FastrelayPoll, FastrelayReaction, FastrelayUser, FastrelayUserMute, FastrelayVideo, FastrelayVideoUploadUrl, FeedActivityQuery, NotificationPage } from './types.ts';
4
+ import { type FeedTarget, type QueryMap } from './utils.ts';
5
+ export type FastrelayAuthMode = 'auto' | 'user' | 'server' | 'none';
6
+ export interface FastrelayRequestOptions {
7
+ auth?: FastrelayAuthMode;
8
+ idempotencyKey?: string;
9
+ headers?: Record<string, string>;
10
+ }
11
+ export interface FastrelayClientOptions {
12
+ apiKey: string;
13
+ baseUrl?: string;
14
+ token?: string;
15
+ user?: Record<string, unknown>;
16
+ fetch?: typeof fetch;
17
+ socketFactory?: FastrelayRealtimeSocketFactory;
18
+ }
19
+ export interface ConnectUserOptions {
20
+ upsertUser?: boolean;
21
+ realtime?: boolean;
22
+ tokenProvider?: FastrelayTokenProvider;
23
+ }
24
+ type ListQuery = {
25
+ limit?: number;
26
+ cursor?: string;
27
+ };
28
+ export declare class FastrelayClient {
29
+ apiKey: string;
30
+ baseUrl: string;
31
+ token?: string;
32
+ user?: Record<string, unknown>;
33
+ realtime?: FastrelayRealtime;
34
+ private readonly fetchImpl;
35
+ private readonly socketFactory?;
36
+ constructor(options: FastrelayClientOptions);
37
+ feed(group: string, id: string): FastrelayFeed;
38
+ connectUser(user: Record<string, unknown>, token: string, options?: ConnectUserOptions): Promise<this>;
39
+ disconnectUser(): this;
40
+ setToken(token: string): this;
41
+ setBaseUrl(baseUrl: string): this;
42
+ close(): void;
43
+ getCapabilities(query?: {
44
+ feed?: string;
45
+ }, options?: FastrelayRequestOptions): Promise<any>;
46
+ getUser(id: string, options?: FastrelayRequestOptions): Promise<FastrelayUser>;
47
+ updateUser(id: string, request: Record<string, unknown>, options?: FastrelayRequestOptions): Promise<FastrelayUser>;
48
+ getOrCreateFeed(group: string, id: string, request?: Record<string, unknown>, options?: FastrelayRequestOptions): Promise<any>;
49
+ getFeedActivities(group: string, id: string, query?: FeedActivityQuery, options?: FastrelayRequestOptions): Promise<CursorPage<FastrelayActivity>>;
50
+ getNotificationFeedActivities(group: string, id: string, query?: FeedActivityQuery, options?: FastrelayRequestOptions): Promise<NotificationPage<FastrelayActivity>>;
51
+ deleteFeed(group: string, id: string, options?: FastrelayRequestOptions): Promise<any>;
52
+ setFeedVisibility(group: string, id: string, level: string, options?: FastrelayRequestOptions): Promise<any>;
53
+ updateFeedSettings(group: string, id: string, request: Record<string, unknown>, options?: FastrelayRequestOptions): Promise<any>;
54
+ addFeedMember(group: string, id: string, request: {
55
+ userId: string;
56
+ role?: string;
57
+ }, options?: FastrelayRequestOptions): Promise<any>;
58
+ removeFeedMember(group: string, id: string, userId: string, options?: FastrelayRequestOptions): Promise<any>;
59
+ listFeedMembers(group: string, id: string, query?: ListQuery, options?: FastrelayRequestOptions): Promise<any>;
60
+ followFeed(group: string, id: string, request: {
61
+ target: FeedTarget;
62
+ activityCopyLimit?: number;
63
+ }, options?: FastrelayRequestOptions): Promise<any>;
64
+ batchFollowFeed(group: string, id: string, request: {
65
+ targets: FeedTarget[];
66
+ activityCopyLimit?: number;
67
+ }, options?: FastrelayRequestOptions): Promise<any>;
68
+ unfollowFeed(group: string, id: string, target: FeedTarget, { keepHistory }?: {
69
+ keepHistory?: boolean;
70
+ }, options?: FastrelayRequestOptions): Promise<any>;
71
+ listFollowers(group: string, id: string, query?: ListQuery, options?: FastrelayRequestOptions): Promise<any>;
72
+ listFollowing(group: string, id: string, query?: ListQuery, options?: FastrelayRequestOptions): Promise<any>;
73
+ listFollowRequests(group: string, id: string, query?: {
74
+ status?: string;
75
+ }, options?: FastrelayRequestOptions): Promise<any>;
76
+ approveFollowRequest(group: string, id: string, requestId: string, options?: FastrelayRequestOptions): Promise<any>;
77
+ rejectFollowRequest(group: string, id: string, requestId: string, options?: FastrelayRequestOptions): Promise<any>;
78
+ addActivity(request: Record<string, unknown>, options?: FastrelayRequestOptions): Promise<FastrelayActivity>;
79
+ getActivity(id: string, options?: FastrelayRequestOptions): Promise<FastrelayActivity>;
80
+ updateActivity(id: string, request: Record<string, unknown>, options?: FastrelayRequestOptions): Promise<FastrelayActivity>;
81
+ deleteActivity(id: string, options?: FastrelayRequestOptions): Promise<any>;
82
+ batchGetActivities(requestOrIds: string[] | Record<string, unknown>, options?: FastrelayRequestOptions): Promise<any>;
83
+ addReaction(activityId: string, type: string, options?: FastrelayRequestOptions): Promise<FastrelayReaction>;
84
+ removeReaction(activityId: string, reactionId: string, options?: FastrelayRequestOptions): Promise<void>;
85
+ listReactions(activityId: string, query?: {
86
+ type?: string;
87
+ } & ListQuery, options?: FastrelayRequestOptions): Promise<CursorPage<FastrelayReaction>>;
88
+ addComment(activityId: string, request: {
89
+ text: string;
90
+ parentId?: string;
91
+ mentionedUsers?: string[];
92
+ }, options?: FastrelayRequestOptions): Promise<FastrelayComment>;
93
+ updateComment(commentId: string, request: {
94
+ text: string;
95
+ }, options?: FastrelayRequestOptions): Promise<FastrelayComment>;
96
+ deleteComment(commentId: string, options?: FastrelayRequestOptions): Promise<void>;
97
+ listComments(activityId: string, query?: {
98
+ sort?: string;
99
+ } & ListQuery, options?: FastrelayRequestOptions): Promise<CursorPage<FastrelayComment>>;
100
+ listReplies(commentId: string, query?: ListQuery, options?: FastrelayRequestOptions): Promise<CursorPage<FastrelayComment>>;
101
+ addCommentReaction(commentId: string, type: string, options?: FastrelayRequestOptions): Promise<FastrelayCommentReaction>;
102
+ removeCommentReaction(commentId: string, reactionId: string, options?: FastrelayRequestOptions): Promise<void>;
103
+ addBookmark(activityId: string, options?: FastrelayRequestOptions): Promise<FastrelayBookmark>;
104
+ removeBookmark(activityId: string, options?: FastrelayRequestOptions): Promise<void>;
105
+ listBookmarks(query?: ListQuery, options?: FastrelayRequestOptions): Promise<CursorPage<FastrelayBookmark>>;
106
+ pinActivity(group: string, id: string, activityId: string, options?: FastrelayRequestOptions): Promise<FastrelayFeedActivityPin>;
107
+ unpinActivity(group: string, id: string, activityId: string, options?: FastrelayRequestOptions): Promise<void>;
108
+ createPoll(activityId: string, request: {
109
+ question: string;
110
+ options: Array<{
111
+ text: string;
112
+ } & Record<string, string>>;
113
+ maxVotesPerUser?: number;
114
+ anonymous?: boolean;
115
+ expiresAt?: string | Date;
116
+ }, options?: FastrelayRequestOptions): Promise<FastrelayPoll>;
117
+ getPollForActivity(activityId: string, options?: FastrelayRequestOptions): Promise<FastrelayPoll>;
118
+ getPoll(pollId: string, options?: FastrelayRequestOptions): Promise<FastrelayPoll>;
119
+ vote(pollId: string, optionId: string, options?: FastrelayRequestOptions): Promise<FastrelayPoll>;
120
+ removeVote(pollId: string, options?: FastrelayRequestOptions): Promise<void>;
121
+ uploadFile(data: Blob | ArrayBuffer | Uint8Array, filename: string, { type }?: {
122
+ type?: string;
123
+ }, options?: FastrelayRequestOptions): Promise<FastrelayFile>;
124
+ deleteFile(fileId: string, options?: FastrelayRequestOptions): Promise<void>;
125
+ createVideoUploadUrl(request: {
126
+ filename: string;
127
+ sizeBytes: number;
128
+ mimeType: string;
129
+ }, options?: FastrelayRequestOptions): Promise<FastrelayVideoUploadUrl>;
130
+ getVideo(videoId: string, options?: FastrelayRequestOptions): Promise<FastrelayVideo>;
131
+ deleteVideo(videoId: string, options?: FastrelayRequestOptions): Promise<void>;
132
+ submitFeedback(activityId: string, type: 'show_more' | 'show_less', options?: FastrelayRequestOptions): Promise<FastrelayFeedback>;
133
+ createFlag(targetType: string, targetId: string, request: {
134
+ reason: string;
135
+ description?: string;
136
+ }, options?: FastrelayRequestOptions): Promise<FastrelayModerationFlag>;
137
+ deleteFlag(flagId: string, options?: FastrelayRequestOptions): Promise<void>;
138
+ createMute(userId: string, { type, expiresAt }?: {
139
+ type?: string;
140
+ expiresAt?: string | Date;
141
+ }, options?: FastrelayRequestOptions): Promise<FastrelayUserMute>;
142
+ removeMute(userId: string, { type }?: {
143
+ type?: string;
144
+ }, options?: FastrelayRequestOptions): Promise<void>;
145
+ listMutes(query?: {
146
+ type?: string;
147
+ } & ListQuery, options?: FastrelayRequestOptions): Promise<CursorPage<FastrelayUserMute>>;
148
+ getMutedUsers(query?: {
149
+ type?: string;
150
+ } & ListQuery, options?: FastrelayRequestOptions): Promise<CursorPage<FastrelayUserMute>>;
151
+ request(method: string, path: string, { query, body, form, options, }?: {
152
+ query?: QueryMap;
153
+ body?: unknown;
154
+ form?: FormData;
155
+ options?: FastrelayRequestOptions;
156
+ }): Promise<any>;
157
+ private buildAuthorization;
158
+ }
159
+ export {};