UNPKG

@liveblocks/react

Version:

A set of React hooks and providers to use Liveblocks declaratively. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.

1,248 lines (1,243 loc) 103 kB
import * as react from 'react'; import { ComponentType, Context, PropsWithChildren, ReactNode } from 'react'; import { BaseUserMeta, Client, JsonObject, LsonObject, LiveObject, User, Json, RoomSubscriptionSettings, Room, Status, BroadcastOptions, OthersEvent, LostConnectionEvent, History, BaseMetadata as BaseMetadata$1, ClientOptions, ThreadData as ThreadData$1, CommentData as CommentData$1 } from '@liveblocks/client'; import * as _liveblocks_core from '@liveblocks/core'; import { OpaqueClient, OpaqueRoom, AiKnowledgeSource, AiOpaqueToolDefinition, BaseMetadata, QueryMetadata, AsyncResult, DRI, AsyncSuccess, Resolve, CommentBody, CommentAttachment, PartialUnless, Patchable, InboxNotificationData, NotificationSettings, Relax, ToImmutable, PartialNotificationSettings, AsyncLoading, AsyncError, ThreadData, HistoryVersion, AiChat, Client as Client$1, LiveblocksError, SyncStatus, RoomEventMessage, CommentData, WithNavigation, AiChatMessage, MutableSignal, ThreadDataWithDeleteInfo, ThreadDeleteInfo, DerivedSignal, DefaultMap, MessageId, SubscriptionData, SubscriptionKey, CommentUserReaction, InboxNotificationDeleteInfo, SubscriptionDeleteInfo, RoomSubscriptionSettings as RoomSubscriptionSettings$1, ISignal, Permission, BaseUserMeta as BaseUserMeta$1, DistributiveOmit, DU, DM, TextEditorType, IYjsProvider, DP, DS, DE } from '@liveblocks/core'; import * as react_jsx_runtime from 'react/jsx-runtime'; /** * Raw access to the React context where the LiveblocksProvider stores the * current client. Exposed for advanced use cases only. * * @private This is a private/advanced API. Do not rely on it. */ declare const ClientContext: react.Context<OpaqueClient | null>; /** * @private This is an internal API. */ declare function useClientOrNull<U extends BaseUserMeta>(): Client<U> | null; /** * Obtains a reference to the current Liveblocks client. */ declare function useClient<U extends BaseUserMeta>(): Client<U>; /** * Raw access to the React context where the RoomProvider stores the current * room. Exposed for advanced use cases only. * * @private This is a private/advanced API. Do not rely on it. */ declare const RoomContext: react.Context<OpaqueRoom | null>; type RegisterAiKnowledgeProps = AiKnowledgeSource & { /** * An optional unique key for this knowledge source. If multiple components * register knowledge under the same key, the last one to mount takes * precedence. */ id?: string; }; type RegisterAiToolProps = { name: string; tool: AiOpaqueToolDefinition; /** * When provided, the tool will only be available for this chatId. If not * provided, this tool will globally be made available to any AiChat * instance. */ chatId?: string; /** * Whether this tool should be enabled. When set to `false`, the tool will * not be made available to the AI copilot for any new/future chat messages, * but will still allow existing tool invocations to be rendered that are * part of the historic chat record. Defaults to true. */ enabled?: boolean; }; type UiChatMessage = WithNavigation<AiChatMessage>; type UseSyncStatusOptions = { /** * When setting smooth, the hook will not update immediately as status * changes. This is because in typical applications, these states can change * quickly between synchronizing and synchronized. If you use this hook to * build a "Saving changes..." style UI, prefer setting `smooth: true`. */ smooth?: boolean; }; type UseSendAiMessageOptions = { /** * The id of the copilot to use to send the message. */ copilotId?: string; /** Stream the response as it is being generated. Defaults to true. */ stream?: boolean; /** The maximum timeout for the answer to be generated. */ timeout?: number; }; type ThreadsQuery<M extends BaseMetadata> = { /** * Whether to only return threads marked as resolved or unresolved. If not provided, * all threads will be returned. */ resolved?: boolean; /** * The metadata to filter the threads by. If provided, only threads with metadata that matches * the provided metadata will be returned. If not provided, all threads will be returned. */ metadata?: Partial<QueryMetadata<M>>; }; type UseUserThreadsOptions<M extends BaseMetadata> = { /** * The query (including metadata) to filter the threads by. If provided, only threads * that match the query will be returned. If not provided, all threads will be returned. */ query?: ThreadsQuery<M>; }; type UseThreadsOptions<M extends BaseMetadata> = { /** * The query (including metadata) to filter the threads by. If provided, only threads * that match the query will be returned. If not provided, all threads will be returned. */ query?: ThreadsQuery<M>; /** * Whether to scroll to a comment on load based on the URL hash. Defaults to `true`. * * @example * Given the URL `https://example.com/my-room#cm_xxx`, the `cm_xxx` comment will be * scrolled to on load if it exists in the page. */ scrollOnLoad?: boolean; }; type UserAsyncResult<T> = AsyncResult<T, "user">; type UserAsyncSuccess<T> = AsyncSuccess<T, "user">; type RoomInfoAsyncResult = AsyncResult<DRI, "info">; type RoomInfoAsyncSuccess = AsyncSuccess<DRI, "info">; type AttachmentUrlAsyncResult = AsyncResult<string, "url">; type AttachmentUrlAsyncSuccess = AsyncSuccess<string, "url">; type CreateThreadOptions<M extends BaseMetadata> = Resolve<{ body: CommentBody; attachments?: CommentAttachment[]; } & PartialUnless<M, { metadata: M; }>>; type EditThreadMetadataOptions<M extends BaseMetadata> = { threadId: string; metadata: Patchable<M>; }; type CreateCommentOptions = { threadId: string; body: CommentBody; attachments?: CommentAttachment[]; }; type EditCommentOptions = { threadId: string; commentId: string; body: CommentBody; attachments?: CommentAttachment[]; }; type DeleteCommentOptions = { threadId: string; commentId: string; }; type CommentReactionOptions = { threadId: string; commentId: string; emoji: string; }; type PaginationFields = { hasFetchedAll: boolean; isFetchingMore: boolean; fetchMore: () => void; fetchMoreError?: Error; }; type PagedAsyncSuccess<T, F extends string> = Resolve<AsyncSuccess<T, F> & PaginationFields>; type PagedAsyncResult<T, F extends string> = Relax<AsyncLoading<F> | AsyncError<F> | PagedAsyncSuccess<T, F>>; type ThreadsAsyncSuccess<M extends BaseMetadata> = PagedAsyncSuccess<ThreadData<M>[], "threads">; type ThreadsAsyncResult<M extends BaseMetadata> = PagedAsyncResult<ThreadData<M>[], "threads">; type InboxNotificationsAsyncSuccess = PagedAsyncSuccess<InboxNotificationData[], "inboxNotifications">; type InboxNotificationsAsyncResult = PagedAsyncResult<InboxNotificationData[], "inboxNotifications">; type UnreadInboxNotificationsCountAsyncSuccess = AsyncSuccess<number, "count">; type UnreadInboxNotificationsCountAsyncResult = AsyncResult<number, "count">; type NotificationSettingsAsyncResult = AsyncResult<NotificationSettings, "settings">; type RoomSubscriptionSettingsAsyncSuccess = AsyncSuccess<RoomSubscriptionSettings, "settings">; type RoomSubscriptionSettingsAsyncResult = AsyncResult<RoomSubscriptionSettings, "settings">; type HistoryVersionDataAsyncResult = AsyncResult<Uint8Array>; type HistoryVersionsAsyncSuccess = AsyncSuccess<HistoryVersion[], "versions">; type HistoryVersionsAsyncResult = AsyncResult<HistoryVersion[], "versions">; type AiChatsAsyncSuccess = PagedAsyncSuccess<AiChat[], "chats">; type AiChatsAsyncResult = PagedAsyncResult<AiChat[], "chats">; type AiChatAsyncSuccess = AsyncSuccess<AiChat, "chat">; type AiChatAsyncResult = AsyncResult<AiChat, "chat">; type AiChatMessagesAsyncSuccess = AsyncSuccess<readonly UiChatMessage[], "messages">; type AiChatMessagesAsyncResult = AsyncResult<readonly UiChatMessage[], "messages">; type RoomProviderProps<P extends JsonObject, S extends LsonObject> = Resolve<{ /** * The id of the room you want to connect to */ id: string; children: ReactNode; /** * Whether or not the room should connect to Liveblocks servers * when the RoomProvider is rendered. * * By default equals to `typeof window !== "undefined"`, * meaning the RoomProvider tries to connect to Liveblocks servers * only on the client side. */ autoConnect?: boolean; } & PartialUnless<P, { /** * The initial Presence to use and announce when you enter the Room. The * Presence is available on all users in the Room (me & others). */ initialPresence: P | ((roomId: string) => P); }> & PartialUnless<S, { /** * The initial Storage to use when entering a new Room. */ initialStorage: S | ((roomId: string) => S); }>>; /** * For any function type, returns a similar function type, but without the * first argument. */ type OmitFirstArg<F> = F extends (first: any, ...rest: infer A) => infer R ? (...args: A) => R : never; type MutationContext<P extends JsonObject, S extends LsonObject, U extends BaseUserMeta> = { storage: LiveObject<S>; self: User<P, U>; others: readonly User<P, U>[]; setMyPresence: (patch: Partial<P>, options?: { addToHistory: boolean; }) => void; }; type ThreadSubscription = Relax<{ status: "not-subscribed"; subscribe: () => void; unsubscribe: () => void; } | { status: "subscribed"; unreadSince: null; subscribe: () => void; unsubscribe: () => void; } | { status: "subscribed"; unreadSince: Date; subscribe: () => void; unsubscribe: () => void; }>; type SharedContextBundle<U extends BaseUserMeta> = { classic: { /** * Obtains a reference to the current Liveblocks client. */ useClient(): Client$1<U>; /** * Returns user info from a given user ID. * * @example * const { user, error, isLoading } = useUser("user-id"); */ useUser(userId: string): UserAsyncResult<U["info"]>; /** * Returns room info from a given room ID. * * @example * const { info, error, isLoading } = useRoomInfo("room-id"); */ useRoomInfo(roomId: string): RoomInfoAsyncResult; /** * Returns whether the hook is called within a RoomProvider context. * * @example * const isInsideRoom = useIsInsideRoom(); */ useIsInsideRoom(): boolean; /** * useErrorListener is a React hook that allows you to respond to any * Liveblocks error, for example room connection errors, errors * creating/editing/deleting threads, etc. * * @example * useErrorListener(err => { * console.error(err); * }) */ useErrorListener(callback: (err: LiveblocksError) => void): void; /** * Returns the current Liveblocks sync status, and triggers a re-render * whenever it changes. Can be used to render a "Saving..." indicator, or for * preventing that a browser tab can be closed until all changes have been * synchronized with the server. * * @example * const syncStatus = useSyncStatus(); // "synchronizing" | "synchronized" * const syncStatus = useSyncStatus({ smooth: true }); */ useSyncStatus(options?: UseSyncStatusOptions): SyncStatus; /** * Make knowledge about your application state available to any AI used in * a chat or a one-off request. * * For example: * * <RegisterAiKnowledge * description="The current mode of my application" * value="dark" /> * * <RegisterAiKnowledge * description="The current list of todos" * value={todos} /> * * By mounting this component, the AI will get access to this knwoledge. * By unmounting this component, the AI will no longer have access to it. * It can choose to use or ignore this knowledge in its responses. */ RegisterAiKnowledge: ComponentType<RegisterAiKnowledgeProps>; RegisterAiTool: ComponentType<RegisterAiToolProps>; }; suspense: { /** * Obtains a reference to the current Liveblocks client. */ useClient(): Client$1<U>; /** * Returns user info from a given user ID. * * @example * const { user } = useUser("user-id"); */ useUser(userId: string): UserAsyncSuccess<U["info"]>; /** * Returns room info from a given room ID. * * @example * const { info } = useRoomInfo("room-id"); */ useRoomInfo(roomId: string): RoomInfoAsyncSuccess; /** * Returns whether the hook is called within a RoomProvider context. * * @example * const isInsideRoom = useIsInsideRoom(); */ useIsInsideRoom(): boolean; /** * useErrorListener is a React hook that allows you to respond to any * Liveblocks error, for example room connection errors, errors * creating/editing/deleting threads, etc. * * @example * useErrorListener(err => { * console.error(err); * }) */ useErrorListener(callback: (err: LiveblocksError) => void): void; /** * Returns the current Liveblocks sync status, and triggers a re-render * whenever it changes. Can be used to render a "Saving..." indicator, or for * preventing that a browser tab can be closed until all changes have been * synchronized with the server. * * @example * const syncStatus = useSyncStatus(); // "synchronizing" | "synchronized" * const syncStatus = useSyncStatus({ smooth: true }); */ useSyncStatus(options?: UseSyncStatusOptions): SyncStatus; /** * Make knowledge about your application state available to any AI used in * a chat or a one-off request. * * For example: * * <RegisterAiKnowledge * description="The current mode of my application" * value="dark" /> * * <RegisterAiKnowledge * description="The current list of todos" * value={todos} /> * * By mounting this component, the AI will get access to this knwoledge. * By unmounting this component, the AI will no longer have access to it. * It can choose to use or ignore this knowledge in its responses. */ RegisterAiKnowledge: ComponentType<RegisterAiKnowledgeProps>; RegisterAiTool: ComponentType<RegisterAiToolProps>; }; }; /** * Properties that are the same in RoomContext and RoomContext["suspense"]. */ type RoomContextBundleCommon<P extends JsonObject, S extends LsonObject, U extends BaseUserMeta, E extends Json, M extends BaseMetadata> = { /** * You normally don't need to directly interact with the RoomContext, but * it can be necessary if you're building an advanced app where you need to * set up a context bridge between two React renderers. */ RoomContext: Context<Room<P, S, U, E, M> | null>; /** * Makes a Room available in the component hierarchy below. * Joins the room when the component is mounted, and automatically leaves * the room when the component is unmounted. */ RoomProvider(props: RoomProviderProps<P, S>): JSX.Element; /** * Returns the Room of the nearest RoomProvider above in the React component * tree. */ useRoom(options?: { allowOutsideRoom: false; }): Room<P, S, U, E, M>; useRoom(options: { allowOutsideRoom: boolean; }): Room<P, S, U, E, M> | null; /** * Returns the current connection status for the Room, and triggers * a re-render whenever it changes. Can be used to render a status badge. */ useStatus(): Status; /** * Returns a callback that lets you broadcast custom events to other users in the room * * @example * const broadcast = useBroadcastEvent(); * * broadcast({ type: "CUSTOM_EVENT", data: { x: 0, y: 0 } }); */ useBroadcastEvent(): (event: E, options?: BroadcastOptions) => void; /** * Get informed when users enter or leave the room, as an event. * * @example * useOthersListener({ type, user, others }) => { * if (type === 'enter') { * // `user` has joined the room * } else if (type === 'leave') { * // `user` has left the room * } * }) */ useOthersListener(callback: (event: OthersEvent<P, U>) => void): void; /** * Get informed when reconnecting to the Liveblocks servers is taking * longer than usual. This typically is a sign of a client that has lost * internet connectivity. * * This isn't problematic (because the Liveblocks client is still trying to * reconnect), but it's typically a good idea to inform users about it if * the connection takes too long to recover. * * @example * useLostConnectionListener(event => { * if (event === 'lost') { * toast.warn('Reconnecting to the Liveblocks servers is taking longer than usual...') * } else if (event === 'failed') { * toast.warn('Reconnecting to the Liveblocks servers failed.') * } else if (event === 'restored') { * toast.clear(); * } * }) */ useLostConnectionListener(callback: (event: LostConnectionEvent) => void): void; /** * useEventListener is a React hook that allows you to respond to events broadcast * by other users in the room. * * The `user` argument will indicate which `User` instance sent the message. * This will be equal to one of the others in the room, but it can be `null` * in case this event was broadcasted from the server. * * @example * useEventListener(({ event, user, connectionId }) => { * // ^^^^ Will be Client A * if (event.type === "CUSTOM_EVENT") { * // Do something * } * }); */ useEventListener(callback: (data: RoomEventMessage<P, U, E>) => void): void; /** * Returns the room.history */ useHistory(): History; /** * Returns a function that undoes the last operation executed by the current client. * It does not impact operations made by other clients. */ useUndo(): () => void; /** * Returns a function that redoes the last operation executed by the current client. * It does not impact operations made by other clients. */ useRedo(): () => void; /** * Returns whether there are any operations to undo. */ useCanUndo(): boolean; /** * Returns whether there are any operations to redo. */ useCanRedo(): boolean; /** * Returns the mutable (!) Storage root. This hook exists for * backward-compatible reasons. * * @example * const [root] = useStorageRoot(); */ useStorageRoot(): [root: LiveObject<S> | null]; /** * Returns the presence of the current user of the current room, and a function to update it. * It is different from the setState function returned by the useState hook from React. * You don't need to pass the full presence object to update it. * * @example * const [myPresence, updateMyPresence] = useMyPresence(); * updateMyPresence({ x: 0 }); * updateMyPresence({ y: 0 }); * * // At the next render, "myPresence" will be equal to "{ x: 0, y: 0 }" */ useMyPresence(): [ P, (patch: Partial<P>, options?: { addToHistory: boolean; }) => void ]; /** * useUpdateMyPresence is similar to useMyPresence but it only returns the function to update the current user presence. * If you don't use the current user presence in your component, but you need to update it (e.g. live cursor), it's better to use useUpdateMyPresence to avoid unnecessary renders. * * @example * const updateMyPresence = useUpdateMyPresence(); * updateMyPresence({ x: 0 }); * updateMyPresence({ y: 0 }); * * // At the next render, the presence of the current user will be equal to "{ x: 0, y: 0 }" */ useUpdateMyPresence(): (patch: Partial<P>, options?: { addToHistory: boolean; }) => void; /** * Create a callback function that lets you mutate Liveblocks state. * * The first argument that gets passed into your callback will be * a "mutation context", which exposes the following: * * - `storage` - The mutable Storage root. * You can mutate any Live structures with this, for example: * `storage.get('layers').get('layer1').set('fill', 'red')` * * - `setMyPresence` - Call this with a new (partial) Presence value. * * - `self` - A read-only version of the latest self, if you need it to * compute the next state. * * - `others` - A read-only version of the latest others list, if you * need it to compute the next state. * * useMutation is like React's useCallback, except that the first argument * that gets passed into your callback will be a "mutation context". * * If you want get access to the immutable root somewhere in your mutation, * you can use `storage.ToImmutable()`. * * @example * const fillLayers = useMutation( * ({ storage }, color: Color) => { * ... * }, * [], * ); * * fillLayers('red'); * * const deleteLayers = useMutation( * ({ storage }) => { * ... * }, * [], * ); * * deleteLayers(); */ useMutation<F extends (context: MutationContext<P, S, U>, ...args: any[]) => any>(callback: F, deps: readonly unknown[]): OmitFirstArg<F>; /** * Returns an array with information about all the users currently connected * in the room (except yourself). * * @example * const others = useOthers(); * * // Example to map all cursors in JSX * return ( * <> * {others.map((user) => { * if (user.presence.cursor == null) { * return null; * } * return <Cursor key={user.connectionId} cursor={user.presence.cursor} /> * })} * </> * ) */ useOthers(): readonly User<P, U>[]; /** * Extract arbitrary data based on all the users currently connected in the * room (except yourself). * * The selector function will get re-evaluated any time a user enters or * leaves the room, as well as whenever their presence data changes. * * The component that uses this hook will automatically re-render if your * selector function returns a different value from its previous run. * * By default `useOthers()` uses strict `===` to check for equality. Take * extra care when returning a computed object or list, for example when you * return the result of a .map() or .filter() call from the selector. In * those cases, you'll probably want to use a `shallow` comparison check. * * @example * const avatars = useOthers(users => users.map(u => u.info.avatar), shallow); * const cursors = useOthers(users => users.map(u => u.presence.cursor), shallow); * const someoneIsTyping = useOthers(users => users.some(u => u.presence.isTyping)); * */ useOthers<T>(selector: (others: readonly User<P, U>[]) => T, isEqual?: (prev: T, curr: T) => boolean): T; /** * Returns an array of connection IDs. This matches the values you'll get by * using the `useOthers()` hook. * * Roughly equivalent to: * useOthers((others) => others.map(other => other.connectionId), shallow) * * This is useful in particular to implement efficiently rendering components * for each user in the room, e.g. cursors. * * @example * const ids = useOthersConnectionIds(); * // [2, 4, 7] */ useOthersConnectionIds(): readonly number[]; /** * Related to useOthers(), but optimized for selecting only "subsets" of * others. This is useful for performance reasons in particular, because * selecting only a subset of users also means limiting the number of * re-renders that will be triggered. * * @example * const avatars = useOthersMapped(user => user.info.avatar); * // ^^^^^^^ * // { connectionId: number; data: string }[] * * The selector function you pass to useOthersMapped() is called an "item * selector", and operates on a single user at a time. If you provide an * (optional) "item comparison" function, it will be used to compare each * item pairwise. * * For example, to select multiple properties: * * @example * const avatarsAndCursors = useOthersMapped( * user => [u.info.avatar, u.presence.cursor], * shallow, // 👈 * ); */ useOthersMapped<T>(itemSelector: (other: User<P, U>) => T, itemIsEqual?: (prev: T, curr: T) => boolean): ReadonlyArray<readonly [connectionId: number, data: T]>; /** * Given a connection ID (as obtained by using `useOthersConnectionIds`), you * can call this selector deep down in your component stack to only have the * component re-render if properties for this particular user change. * * @example * // Returns only the selected values re-renders whenever that selection changes) * const { x, y } = useOther(2, user => user.presence.cursor); */ useOther<T>(connectionId: number, selector: (other: User<P, U>) => T, isEqual?: (prev: T, curr: T) => boolean): T; /** * Returns a function that creates a thread with an initial comment, and optionally some metadata. * * @example * const createThread = useCreateThread(); * createThread({ body: {}, metadata: {} }); */ useCreateThread(): (options: CreateThreadOptions<M>) => ThreadData<M>; /** * Returns a function that deletes a thread and its associated comments. * Only the thread creator can delete a thread, it will throw otherwise. * * @example * const deleteThread = useDeleteThread(); * deleteThread("th_xxx"); */ useDeleteThread(): (threadId: string) => void; /** * Returns a function that edits a thread's metadata. * To delete an existing metadata property, set its value to `null`. * * @example * const editThreadMetadata = useEditThreadMetadata(); * editThreadMetadata({ threadId: "th_xxx", metadata: {} }) */ useEditThreadMetadata(): (options: EditThreadMetadataOptions<M>) => void; /** * Returns a function that marks a thread as resolved. * * @example * const markThreadAsResolved = useMarkThreadAsResolved(); * markThreadAsResolved("th_xxx"); */ useMarkThreadAsResolved(): (threadId: string) => void; /** * Returns a function that marks a thread as unresolved. * * @example * const markThreadAsUnresolved = useMarkThreadAsUnresolved(); * markThreadAsUnresolved("th_xxx"); */ useMarkThreadAsUnresolved(): (threadId: string) => void; /** * Returns a function that subscribes the user to a thread. * * @example * const subscribeToThread = useSubscribeToThread(); * subscribeToThread("th_xxx"); */ useSubscribeToThread(): (threadId: string) => void; /** * Returns a function that unsubscribes the user from a thread. * * @example * const unsubscribeFromThread = useUnsubscribeFromThread(); * unsubscribeFromThread("th_xxx"); */ useUnsubscribeFromThread(): (threadId: string) => void; /** * Returns a function that adds a comment to a thread. * * @example * const createComment = useCreateComment(); * createComment({ threadId: "th_xxx", body: {} }); */ useCreateComment(): (options: CreateCommentOptions) => CommentData; /** * Returns a function that edits a comment's body. * * @example * const editComment = useEditComment() * editComment({ threadId: "th_xxx", commentId: "cm_xxx", body: {} }) */ useEditComment(): (options: EditCommentOptions) => void; /** * Returns a function that deletes a comment. * If it is the last non-deleted comment, the thread also gets deleted. * * @example * const deleteComment = useDeleteComment(); * deleteComment({ threadId: "th_xxx", commentId: "cm_xxx" }) */ useDeleteComment(): (options: DeleteCommentOptions) => void; /** * Returns a function that adds a reaction from a comment. * * @example * const addReaction = useAddReaction(); * addReaction({ threadId: "th_xxx", commentId: "cm_xxx", emoji: "👍" }) */ useAddReaction(): (options: CommentReactionOptions) => void; /** * Returns a function that removes a reaction on a comment. * * @example * const removeReaction = useRemoveReaction(); * removeReaction({ threadId: "th_xxx", commentId: "cm_xxx", emoji: "👍" }) */ useRemoveReaction(): (options: CommentReactionOptions) => void; /** * Returns a function that updates the user's subscription settings * for the current room. * * @example * const updateRoomSubscriptionSettings = useUpdateRoomSubscriptionSettings(); * updateRoomSubscriptionSettings({ threads: "all" }); */ useUpdateRoomSubscriptionSettings(): (settings: Partial<RoomSubscriptionSettings>) => void; /** * Returns a function that marks a thread as read. * * @example * const markThreadAsRead = useMarkThreadAsRead(); * markThreadAsRead("th_xxx"); */ useMarkThreadAsRead(): (threadId: string) => void; /** * Returns the subscription status of a thread, methods to update it, and when * the thread was last read. * * @example * const { status, subscribe, unsubscribe, unreadSince } = useThreadSubscription("th_xxx"); */ useThreadSubscription(threadId: string): ThreadSubscription; }; type RoomContextBundle<P extends JsonObject, S extends LsonObject, U extends BaseUserMeta, E extends Json, M extends BaseMetadata> = Resolve<RoomContextBundleCommon<P, S, U, E, M> & SharedContextBundle<U>["classic"] & { /** * Extract arbitrary data from the Liveblocks Storage state, using an * arbitrary selector function. * * The selector function will get re-evaluated any time something changes in * Storage. The value returned by your selector function will also be the * value returned by the hook. * * The `root` value that gets passed to your selector function is * a immutable/readonly version of your Liveblocks storage root. * * The component that uses this hook will automatically re-render if the * returned value changes. * * By default `useStorage()` uses strict `===` to check for equality. Take * extra care when returning a computed object or list, for example when you * return the result of a .map() or .filter() call from the selector. In * those cases, you'll probably want to use a `shallow` comparison check. */ useStorage<T>(selector: (root: ToImmutable<S>) => T, isEqual?: (prev: T | null, curr: T | null) => boolean): T | null; /** * Gets the current user once it is connected to the room. * * @example * const me = useSelf(); * if (me !== null) { * const { x, y } = me.presence.cursor; * } */ useSelf(): User<P, U> | null; /** * Extract arbitrary data based on the current user. * * The selector function will get re-evaluated any time your presence data * changes. * * The component that uses this hook will automatically re-render if your * selector function returns a different value from its previous run. * * By default `useSelf()` uses strict `===` to check for equality. Take extra * care when returning a computed object or list, for example when you return * the result of a .map() or .filter() call from the selector. In those * cases, you'll probably want to use a `shallow` comparison check. * * Will return `null` while Liveblocks isn't connected to a room yet. * * @example * const cursor = useSelf(me => me.presence.cursor); * if (cursor !== null) { * const { x, y } = cursor; * } * */ useSelf<T>(selector: (me: User<P, U>) => T, isEqual?: (prev: T, curr: T) => boolean): T | null; /** * Returns the threads within the current room. * * @example * const { threads, error, isLoading } = useThreads(); */ useThreads(options?: UseThreadsOptions<M>): ThreadsAsyncResult<M>; /** * Returns the user's subscription settings for the current room * and a function to update them. * * @example * const [{ settings }, updateSettings] = useRoomSubscriptionSettings(); */ useRoomSubscriptionSettings(): [ RoomSubscriptionSettingsAsyncResult, (settings: Partial<RoomSubscriptionSettings>) => void ]; /** * Returns a presigned URL for an attachment by its ID. * * @example * const { url, error, isLoading } = useAttachmentUrl("at_xxx"); */ useAttachmentUrl(attachmentId: string): AttachmentUrlAsyncResult; /** * (Private beta) Returns a history of versions of the current room. * * @example * const { versions, error, isLoading } = useHistoryVersions(); */ useHistoryVersions(): HistoryVersionsAsyncResult; /** * (Private beta) Returns the data of a specific version of the current room. * * @example * const { data, error, isLoading } = useHistoryVersionData(version.id); */ useHistoryVersionData(id: string): HistoryVersionDataAsyncResult; suspense: Resolve<RoomContextBundleCommon<P, S, U, E, M> & SharedContextBundle<U>["suspense"] & { /** * Extract arbitrary data from the Liveblocks Storage state, using an * arbitrary selector function. * * The selector function will get re-evaluated any time something changes in * Storage. The value returned by your selector function will also be the * value returned by the hook. * * The `root` value that gets passed to your selector function is * a immutable/readonly version of your Liveblocks storage root. * * The component that uses this hook will automatically re-render if the * returned value changes. * * By default `useStorage()` uses strict `===` to check for equality. Take * extra care when returning a computed object or list, for example when you * return the result of a .map() or .filter() call from the selector. In * those cases, you'll probably want to use a `shallow` comparison check. */ useStorage<T>(selector: (root: ToImmutable<S>) => T, isEqual?: (prev: T, curr: T) => boolean): T; /** * Gets the current user once it is connected to the room. * * @example * const me = useSelf(); * const { x, y } = me.presence.cursor; */ useSelf(): User<P, U>; /** * Extract arbitrary data based on the current user. * * The selector function will get re-evaluated any time your presence data * changes. * * The component that uses this hook will automatically re-render if your * selector function returns a different value from its previous run. * * By default `useSelf()` uses strict `===` to check for equality. Take extra * care when returning a computed object or list, for example when you return * the result of a .map() or .filter() call from the selector. In those * cases, you'll probably want to use a `shallow` comparison check. * * @example * const cursor = useSelf(me => me.presence.cursor); * const { x, y } = cursor; * */ useSelf<T>(selector: (me: User<P, U>) => T, isEqual?: (prev: T, curr: T) => boolean): T; /** * Returns the threads within the current room. * * @example * const { threads } = useThreads(); */ useThreads(options?: UseThreadsOptions<M>): ThreadsAsyncSuccess<M>; /** * (Private beta) Returns a history of versions of the current room. * * @example * const { versions } = useHistoryVersions(); */ useHistoryVersions(): HistoryVersionsAsyncSuccess; /** * Returns the user's subscription settings for the current room * and a function to update them. * * @example * const [{ settings }, updateSettings] = useRoomSubscriptionSettings(); */ useRoomSubscriptionSettings(): [ RoomSubscriptionSettingsAsyncSuccess, (settings: Partial<RoomSubscriptionSettings>) => void ]; /** * Returns a presigned URL for an attachment by its ID. * * @example * const { url } = useAttachmentUrl("at_xxx"); */ useAttachmentUrl(attachmentId: string): AttachmentUrlAsyncSuccess; }>; }>; /** * Properties that are the same in LiveblocksContext and LiveblocksContext["suspense"]. */ type LiveblocksContextBundleCommon<M extends BaseMetadata> = { /** * Makes Liveblocks features outside of rooms (e.g. Notifications) available * in the component hierarchy below. */ LiveblocksProvider(props: PropsWithChildren): JSX.Element; /** * Returns a function that marks an inbox notification as read for the current user. * * @example * const markInboxNotificationAsRead = useMarkInboxNotificationAsRead(); * markInboxNotificationAsRead("in_xxx"); */ useMarkInboxNotificationAsRead(): (inboxNotificationId: string) => void; /** * Returns a function that marks all of the current user's inbox notifications as read. * * @example * const markAllInboxNotificationsAsRead = useMarkAllInboxNotificationsAsRead(); * markAllInboxNotificationsAsRead(); */ useMarkAllInboxNotificationsAsRead(): () => void; /** * Returns a function that deletes an inbox notification for the current user. * * @example * const deleteInboxNotification = useDeleteInboxNotification(); * deleteInboxNotification("in_xxx"); */ useDeleteInboxNotification(): (inboxNotificationId: string) => void; /** * Returns a function that deletes all of the current user's inbox notifications. * * @example * const deleteAllInboxNotifications = useDeleteAllInboxNotifications(); * deleteAllInboxNotifications(); */ useDeleteAllInboxNotifications(): () => void; /** * Returns the thread associated with a `"thread"` inbox notification. * * It can **only** be called with IDs of `"thread"` inbox notifications, * so we recommend only using it when customizing the rendering or in other * situations where you can guarantee the kind of the notification. * * When `useInboxNotifications` returns `"thread"` inbox notifications, * it also receives the associated threads and caches them behind the scenes. * When you call `useInboxNotificationThread`, it simply returns the cached thread * for the inbox notification ID you passed to it, without any fetching or waterfalls. * * @example * const thread = useInboxNotificationThread("in_xxx"); */ useInboxNotificationThread(inboxNotificationId: string): ThreadData<M>; /** * Returns notification settings for the current user. * * @example * const [{ settings }, updateNotificationSettings] = useNotificationSettings() */ useNotificationSettings(): [ NotificationSettingsAsyncResult, (settings: PartialNotificationSettings) => void ]; /** * Returns a function that updates the user's notification * settings for a project. * * @example * const updateNotificationSettings = useUpdateNotificationSettings() */ useUpdateNotificationSettings(): (settings: PartialNotificationSettings) => void; /** * Returns the current Liveblocks sync status, and triggers a re-render * whenever it changes. Can be used to render a "Saving..." indicator, or for * preventing that a browser tab can be closed until all changes have been * synchronized with the server. * * @example * const syncStatus = useSyncStatus(); // "synchronizing" | "synchronized" * const syncStatus = useSyncStatus({ smooth: true }); */ useSyncStatus(options?: UseSyncStatusOptions): SyncStatus; /** * Returns a function that creates an AI chat. * * If you do not pass a title for the chat, it will be automatically computed * after the first AI response. * * @example * const createAiChat = useCreateAiChat(); * createAiChat({ id: "ai-chat-id", title: "My AI chat" }); */ useCreateAiChat(): (options: { id: string; title?: string; metadata?: Record<string, string | string[]>; }) => void; /** * Returns a function that deletes the AI chat with the specified id. * * @example * const deleteAiChat = useDeleteAiChat(); * deleteAiChat("ai-chat-id"); */ useDeleteAiChat(): (chatId: string) => void; /** * Returns a function to send a message in an AI chat. * * @example * const sendMessage = useSendAiMessage(chatId); * sendMessage("Hello, Liveblocks AI!"); */ useSendAiMessage(chatId: string, options?: UseSendAiMessageOptions): (message: string) => void; }; type LiveblocksContextBundle<U extends BaseUserMeta, M extends BaseMetadata> = Resolve<LiveblocksContextBundleCommon<M> & SharedContextBundle<U>["classic"] & { /** * Returns the inbox notifications for the current user. * * @example * const { inboxNotifications, error, isLoading } = useInboxNotifications(); */ useInboxNotifications(): InboxNotificationsAsyncResult; /** * Returns the number of unread inbox notifications for the current user. * * @example * const { count, error, isLoading } = useUnreadInboxNotificationsCount(); */ useUnreadInboxNotificationsCount(): UnreadInboxNotificationsCountAsyncResult; /** * @experimental * * This hook is experimental and could be removed or changed at any time! * Do not use unless explicitly recommended by the Liveblocks team. */ useUserThreads_experimental(options?: UseUserThreadsOptions<M>): ThreadsAsyncResult<M>; /** * (Private beta) Returns the chats for the current user. * * @example * const { chats, error, isLoading } = useAiChats(); */ useAiChats(): AiChatsAsyncResult; /** * (Private beta) Returns the messages in the given chat. * * @example * const { messages, error, isLoading } = useAiChatMessages("my-chat"); */ useAiChatMessages(chatId: string): AiChatMessagesAsyncResult; /** * (Private beta) Returns the information of the given chat. * * @example * const { chat, error, isLoading } = useAiChat("my-chat"); */ useAiChat(chatId: string): AiChatAsyncResult; suspense: Resolve<LiveblocksContextBundleCommon<M> & SharedContextBundle<U>["suspense"] & { /** * Returns the inbox notifications for the current user. * * @example * const { inboxNotifications } = useInboxNotifications(); */ useInboxNotifications(): InboxNotificationsAsyncSuccess; /** * Returns the number of unread inbox notifications for the current user. * * @example * const { count } = useUnreadInboxNotificationsCount(); */ useUnreadInboxNotificationsCount(): UnreadInboxNotificationsCountAsyncSuccess; /** * Returns notification settings for the current user. * * @example * const [{ settings }, updateNotificationSettings] = useNotificationSettings() */ useNotificationSettings(): [ NotificationSettingsAsyncResult, (settings: PartialNotificationSettings) => void ]; /** * @experimental * * This hook is experimental and could be removed or changed at any time! * Do not use unless explicitly recommended by the Liveblocks team. */ useUserThreads_experimental(options?: UseUserThreadsOptions<M>): ThreadsAsyncSuccess<M>; /** * (Private beta) Returns the chats for the current user. * * @example * const { chats } = useAiChats(); */ useAiChats(): AiChatsAsyncSuccess; /** * (Private beta) Returns the messages in the given chat. * * @example * const { messages } = useAiChatMessages("my-chat"); */ useAiChatMessages(chatId: string): AiChatMessagesAsyncSuccess; /** * (Private beta) Returns the information of the given chat. * * @example * const { chat, error, isLoading } = useAiChat("my-chat"); */ useAiChat(chatId: string): AiChatAsyncSuccess; }>; }>; type ReadonlyThreadDB<M extends BaseMetadata> = Omit<ThreadDB<M>, "upsert" | "delete" | "signal">; /** * This class implements a lightweight, in-memory, "database" for all Thread * instances. * * It exposes the following methods: * * - upsert: To add/update a thread * - upsertIfNewer: To add/update a thread. Only update an existing thread if * its newer * - delete: To mark existing threads as deleted * - get: To get any non-deleted thread * - getEvenIfDeleted: To get a thread which is possibly deleted * - findMany: To filter an ordered list of non-deleted threads * - clone: To clone the DB to mutate it further. This is used to mix in * optimistic updates without losing the original thread contents. * */ declare class ThreadDB<M extends BaseMetadata> { #private; readonly signal: MutableSignal<this>; constructor(); clone(): ThreadDB<M>; /** Returns an existing thread by ID. Will never return a deleted thread. */ get(threadId: string): ThreadData<M> | undefined; /** Returns the (possibly deleted) thread by ID. */ getEvenIfDeleted(threadId: string): ThreadDataWithDeleteInfo<M> | undefined; /** Adds or updates a thread in the DB. If the newly given thread is a deleted one, it will get deleted. */ upsert(thread: ThreadDataWithDeleteInfo<M>): void; /** Like .upsert(), except it won't update if a thread by this ID already exists. */ upsertIfNewer(thread: ThreadDataWithDeleteInfo<M>): void; applyDelta(newThreads: ThreadData<M>[], deletedThreads: ThreadDeleteInfo[]): void; /** * Marks a thread as deleted. It will no longer pop up in .findMany() * queries, but it can still be accessed via `.getEvenIfDeleted()`. */ delete(threadId: string, deletedAt: Date): void; /** * Returns all threads matching a given roomId and query. If roomId is not * specified, it will return all threads matching the query, across all * rooms. * * Returns the results in the requested order. Please note: * 'asc' means by createdAt ASC * 'desc' means by updatedAt DESC * * Will never return deleted threads in the result. */ findMany(roomId: string | undefined, query: ThreadsQuery<M> | undefined, direction: "asc" | "desc"): ThreadData<M>[]; } type OptimisticUpdate<M extends BaseMetadata> = CreateThreadOptimisticUpdate<M> | DeleteThreadOptimisticUpdate | EditThreadMetadataOptimisticUpdate<M> | MarkThreadAsResolvedOptimisticUpdate | MarkThreadAsUnresolvedOptimisticUpdate | SubscribeToThreadOptimisticUpdate | UnsubscribeFromThreadOptimisticUpdate | CreateCommentOptimisticUpdate | EditCommentOptimisticUpdate | DeleteCommentOptimisticUpdate | AddReactionOptimisticUpdate | RemoveReactionOptimisticUpdate | MarkInboxNotificationAsReadOptimisticUpdate | MarkAllInboxNotificationsAsReadOptimisticUpdate | DeleteInboxNotificationOptimisticUpdate | DeleteAllInboxNotificationsOptimisticUpdate | UpdateRoomSubscriptionSettingsOptimisticUpdate | UpdateNotificationSettingsOptimisticUpdate; type CreateThreadOptimisticUpdate<M extends BaseMetadata> = { type: "create-thread"; id: string; roomId: string; thread: ThreadData<M>; }; type DeleteThreadOptimisticUpdate = { type: "delete-thread"; id: string; roomId: string; threadId: string; deletedAt: Date; }; type EditThreadMetadataOptimisticUpdate<M extends BaseMetadata> = { type: "edit-thread-metadata"; id: string; threadId: string; metadata: Resolve<Patchable<M>>; updatedAt: Date; }; type MarkThreadAsResol