RecordRoom, mount the providers (already wired in the scaffold), and call the hooks.
status: 'loading' | 'ready' | 'error' and error?: string alongside its records. Gate skeleton states on status.
For an end-to-end walkthrough including UI patterns, see the messaging guide.
useChannels()
create and remove are async - update and archive dispatch optimistically and return void.
- Create
- Update
- Archive
- Delete
create({ name, type }) requires both fields. Passing only { name } returns a channel with type: undefined and silently breaks downstream queries.'public' (any signed-in user), 'private' (members only), 'dm' (two-user direct message).useMessages(channelId, options?)
options.parentMessageId to scope the query to a single reply thread.
- Send
- Edit
- Soft delete
- Hard delete
Posts a new message to the channel. Returns the new
messageId (or undefined if there’s no signed-in user / no channelId). Identity (author) is derived from the verified JWT - you don’t pass an authorId.useReactions(channelId)
getReactionsForMessage returns one row per distinct emoji on the message, already aggregated with count, the full userIds list, and a currentUserReacted flag. toggle adds the caller’s reaction or removes it if it already exists. Fire-and-forget - toggle returns void.
useChannelMembers(channelId)
isMember is a derived boolean property (not a function) that reflects whether the current signed-in user is in this channel. join and leave are fire-and-forget - they return void.
useReadReceipts()
markAsRead records the current timestamp as the user’s last-read marker for the given channel (no messageId argument - the timestamp is what matters). getUnreadCount takes the channel’s messages array (typically from useMessages) and counts how many landed after the stored timestamp.
useConversation(options?)
For DM/conversation Durable Objects (scope conv:<id>) backed by conv_messages / conv_reactions / conv_members collections. Mount inside a <RecordScope roomId="conv:..." schemas={CONVERSATION_SCHEMAS}>.
void and dispatch through the underlying record store. Pass onMessageSent to useConversation() if you need a hook into successful sends (e.g., to scroll to the bottom).
Different from useMessages / useReactions / useChannelMembers - those target the channel-style collections within your app’s main RecordRoom. useConversation targets a dedicated DM Durable Object on a conv:<id> scope.
Record types
recordId, data, createdBy, createdAt, updatedAt) wraps every record.
Helper functions
| Helper | Signature |
|---|---|
formatMessageTime(dateStr) | Returns '3:42 PM' |
formatFullTimestamp(dateStr) | Returns 'Today at 3:42 PM' |
shouldGroupMessages(current, previous, options?) | True if consecutive messages from the same author within a window |
getThreadCounts(messages) | Map of parent messageId → reply count |
groupReactionsForMessage(reactions, messageId, currentUserId) | Aggregates reactions by emoji |
parseMessageMetadata(raw) | Safe JSON parse of metadata field |
Conversation helpers
| Helper | Signature |
|---|---|
getConversationDisplayName(conv) | Resolves a display string from a conversation record |
getConversationParticipantIds(conv) | Returns the array of participant userIds |
isDMConversation(type) | True if type === 'dm' |
Directory conversations
For cross-app conversations (the platform’s shareddir:<appHandle> DO), use the directory hooks instead of useChannels / useMessages. These hooks support discoverable conversations, communities, and posts spanning multiple DeepSpace apps. See the worker schemas reference for DIRECTORY_SCHEMAS.
See also
- Messaging guide - UI patterns and worked examples.
- Permissions - how
'private'/'dm'channel types map to RBAC. - Worker schemas reference -
CHANNELS_SCHEMA,MESSAGES_SCHEMA, etc.