@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
TypeScript
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