@fluxy-chat/sdk 0.1.0 → 0.2.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/README.md CHANGED
@@ -36,6 +36,8 @@ Use the returned `token` in the browser.
36
36
 
37
37
  ## Quick start (React)
38
38
 
39
+ ### Option A — explicit client
40
+
39
41
  ```tsx
40
42
  import { FluxyChatClient, useChat } from "@fluxy-chat/sdk";
41
43
 
@@ -46,11 +48,46 @@ const client = new FluxyChatClient({
46
48
  });
47
49
 
48
50
  function Room({ roomId }: { roomId: string }) {
49
- const { messages, sendMessage, connectionStatus } = useChat({ roomId, client });
50
- // render messages…
51
+ const { messages, sendMessage, connectionStatus, loadMore, hasMore, isLoadingMore } =
52
+ useChat({ roomId, client });
53
+ // render messages; call loadMore() when the user scrolls to the top
54
+ }
55
+ ```
56
+
57
+ ### Option B — `FluxyRealtimeProvider` (hosted Next.js or custom mint)
58
+
59
+ Wrap your app (or chat layout) once. The provider refreshes the member JWT before expiry and on auth errors.
60
+
61
+ ```tsx
62
+ import { FluxyRealtimeProvider, useChat } from "@fluxy-chat/sdk";
63
+
64
+ export function ChatLayout({ children }: { children: React.ReactNode }) {
65
+ return (
66
+ <FluxyRealtimeProvider
67
+ workerUrl={process.env.NEXT_PUBLIC_FLUXYCHAT_WORKER_URL!}
68
+ connectUrl="/api/fluxy/connect"
69
+ >
70
+ {children}
71
+ </FluxyRealtimeProvider>
72
+ );
73
+ }
74
+
75
+ function Room({ roomId }: { roomId: string }) {
76
+ const { messages, sendMessage, loadMore, hasMore } = useChat({ roomId });
77
+ // …
51
78
  }
52
79
  ```
53
80
 
81
+ For your own backend mint flow, pass `authTokenProvider={() => fetch("/api/chat-token").then(r => r.json())}` instead of `connectUrl`.
82
+
83
+ ### Pagination
84
+
85
+ `GET /api/messages` supports a `before` cursor (`createdAt` of the oldest visible message). The SDK sorts history chronologically and exposes:
86
+
87
+ - `hasMore` — another page may exist
88
+ - `isLoadingMore` — `loadMore()` in flight
89
+ - `loadMore()` — prepends older messages
90
+
54
91
  ## Self-host vs hosted cloud
55
92
 
56
93
  - **Self-host:** deploy `apps/worker` from the [monorepo](https://github.com/AlessandroFare/fluxychat), run D1 migrations, set secrets. See `apps/worker/.dev.vars.example`.
@@ -0,0 +1,14 @@
1
+ export interface AgentOutboundMessageInput {
2
+ userId: string;
3
+ content: string;
4
+ parentId?: number | null;
5
+ attachments?: unknown[];
6
+ }
7
+ export interface AgentOutboundValidationResult {
8
+ valid: boolean;
9
+ error?: string;
10
+ content?: string;
11
+ parentId?: number | null;
12
+ }
13
+ export declare function validateAgentOutboundMessage(input: AgentOutboundMessageInput): AgentOutboundValidationResult;
14
+ export declare function buildAgentOutboundWsPayload(input: AgentOutboundMessageInput): Record<string, unknown>;
@@ -0,0 +1,53 @@
1
+ import { FLUXY_MAX_MESSAGE_LENGTH } from "./room-rest";
2
+ export function validateAgentOutboundMessage(input) {
3
+ const userId = input.userId?.trim();
4
+ if (!userId) {
5
+ return { valid: false, error: "userId is required" };
6
+ }
7
+ if (userId.length > 128) {
8
+ return { valid: false, error: "userId exceeds maximum length" };
9
+ }
10
+ if (typeof input.content !== "string") {
11
+ return { valid: false, error: "content must be a string" };
12
+ }
13
+ const trimmed = input.content.trim();
14
+ if (!trimmed) {
15
+ return { valid: false, error: "content cannot be empty" };
16
+ }
17
+ if (trimmed.length > FLUXY_MAX_MESSAGE_LENGTH) {
18
+ return {
19
+ valid: false,
20
+ error: `content exceeds maximum length of ${FLUXY_MAX_MESSAGE_LENGTH} characters`,
21
+ };
22
+ }
23
+ let parentId = null;
24
+ if (input.parentId != null) {
25
+ if (!Number.isFinite(input.parentId) || input.parentId < 1) {
26
+ return { valid: false, error: "parentId must be a positive message id" };
27
+ }
28
+ parentId = Math.floor(input.parentId);
29
+ }
30
+ const attachments = input.attachments;
31
+ if (attachments != null) {
32
+ if (!Array.isArray(attachments)) {
33
+ return { valid: false, error: "attachments must be an array" };
34
+ }
35
+ if (attachments.length > 20) {
36
+ return { valid: false, error: "attachments cannot exceed 20 items" };
37
+ }
38
+ }
39
+ return { valid: true, content: trimmed, parentId };
40
+ }
41
+ export function buildAgentOutboundWsPayload(input) {
42
+ const validated = validateAgentOutboundMessage(input);
43
+ if (!validated.valid) {
44
+ throw new Error(validated.error ?? "Invalid agent outbound message");
45
+ }
46
+ return {
47
+ type: "message",
48
+ userId: input.userId.trim(),
49
+ content: validated.content,
50
+ parentId: validated.parentId ?? null,
51
+ attachments: Array.isArray(input.attachments) ? input.attachments : [],
52
+ };
53
+ }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,14 @@
1
1
  export { FluxyAuthError, FluxyConnectionError, FluxySendError, FluxyTimeoutError, FLUXY_WS_CLOSE_NORMAL, FLUXY_WS_CLOSE_POLICY, computeReconnectBackoffMs, mapWebSocketCloseToError, } from "./errors";
2
2
  export { FluxyChatRoomConnection, type FluxyRoomConnectionOptions, type FluxyRoomConnectionStatus, type FluxyWaitForOptions, } from "./room-connection";
3
3
  export { FluxyMessageStream, type FluxyMessageStreamOptions } from "./message-stream";
4
+ export { clampHistoryLimit, mergeMessagesChronological, sortMessagesChronological, MAX_HISTORY_LIMIT, type HistoryMessage, } from "./message-history";
5
+ export { decodeFluxyJwtPayload, jwtRefreshDelayMs, type DecodedFluxyJwt } from "./jwt-utils";
6
+ export { FLUXY_MAX_MESSAGE_LENGTH, normalizeRoomMember, normalizeRoomMembers, } from "./room-rest";
7
+ export { validateAgentOutboundMessage, buildAgentOutboundWsPayload, type AgentOutboundMessageInput, type AgentOutboundValidationResult, } from "./agent-outbound";
8
+ export { FluxyRealtimeProvider, type FluxyRealtimeProviderProps, type FluxyAuthTokenResult, } from "./realtime-provider";
9
+ export { useFluxyChat, useFluxyChatOptional, type FluxyRealtimeContextValue } from "./use-fluxy-chat";
10
+ export { useChat, type UseChatOptions } from "./use-chat";
11
+ export { useRooms } from "./use-rooms";
4
12
  import { FluxyChatRoomConnection, type FluxyRoomConnectionOptions } from "./room-connection";
5
13
  export interface FluxyChatMessage {
6
14
  id: number;
@@ -37,6 +45,16 @@ export interface FluxyChatRoom {
37
45
  name: string;
38
46
  created_at: string;
39
47
  }
48
+ export interface FluxyRoomMember {
49
+ userId: string;
50
+ role: string;
51
+ joined_at?: string;
52
+ }
53
+ export interface FetchMessagesOptions {
54
+ limit?: number;
55
+ /** ISO `createdAt` cursor — returns messages older than this timestamp. */
56
+ before?: string;
57
+ }
40
58
  export interface FluxyChatToolDefinition {
41
59
  type: "function";
42
60
  function: {
@@ -159,7 +177,10 @@ export declare class FluxyChatClient {
159
177
  */
160
178
  connectRoom(roomId: string, options?: FluxyRoomConnectionOptions): FluxyChatRoomConnection;
161
179
  connectSSE(roomId: string): EventSource | null;
162
- fetchMessages(roomId: string, limit?: number): Promise<FluxyChatMessage[]>;
180
+ fetchMessages(roomId: string, limitOrOptions?: number | FetchMessagesOptions): Promise<FluxyChatMessage[]>;
181
+ fetchRoomMembers(roomId: string): Promise<FluxyRoomMember[]>;
182
+ /** Alias for {@link fetchMessages} — chronological room history via REST. */
183
+ fetchRoomHistory(roomId: string, options?: FetchMessagesOptions): Promise<FluxyChatMessage[]>;
163
184
  listRooms(type?: string): Promise<FluxyChatRoom[]>;
164
185
  createMessage(roomId: string, content: string, replyTo?: number | null, attachments?: FluxyChatAttachment[]): Promise<FluxyChatMessage | null>;
165
186
  /**
@@ -241,53 +262,3 @@ export declare class FluxyChatClient {
241
262
  }): Promise<void>;
242
263
  deleteWebhook(webhookId: string): Promise<void>;
243
264
  }
244
- export interface UseChatOptions {
245
- roomId: string;
246
- client: FluxyChatClient;
247
- agentId?: string;
248
- }
249
- export declare function useChat({ roomId, client, agentId }: UseChatOptions): {
250
- messages: FluxyChatMessage[];
251
- online: number;
252
- typingUsers: Record<string, boolean>;
253
- seenBy: Record<number, string[]>;
254
- onlineUsers: string[];
255
- connected: boolean;
256
- connectionStatus: "connecting" | "connected" | "reconnecting" | "disconnected" | "polling" | "sse";
257
- reconnectAttempt: number;
258
- connectionError: Error | null;
259
- agentTyping: boolean;
260
- typingAgentId: string | null;
261
- reactions: Record<number, Record<string, number>>;
262
- sendMessage: (content: string, replyTo?: number | null, attachments?: FluxyChatAttachment[]) => void;
263
- setTyping: (isTyping: boolean) => void;
264
- editMessage: (messageId: number, content: string) => void;
265
- sendReaction: (messageId: number, emoji: string, op?: "add" | "remove") => void;
266
- sendReadReceipt: (messageId: number) => void;
267
- deleteMessage: (messageId: number) => void;
268
- invokeAgent: (content: string, options?: {
269
- agentId?: string;
270
- replyTo?: number | null;
271
- }) => Promise<{
272
- run: {
273
- id: string;
274
- status: string;
275
- latencyMs?: number;
276
- inputTokens?: number;
277
- outputTokens?: number;
278
- estimatedCost?: number;
279
- iterations?: number;
280
- toolCalls?: FluxyChatToolCall[];
281
- createdAt: string;
282
- };
283
- message: FluxyChatMessage;
284
- }>;
285
- };
286
- export declare function useRooms(client: FluxyChatClient): {
287
- rooms: (FluxyChatRoom & {
288
- unreadCount?: number;
289
- })[];
290
- loading: boolean;
291
- error: string | null;
292
- reload: () => Promise<void>;
293
- };