rspace-online/modules/rchats/schemas.ts

145 lines
3.3 KiB
TypeScript

/**
* rChats Automerge document schemas.
*
* Granularity: one directory doc per space + one doc per channel.
* DocId format: {space}:chats:channels (directory)
* {space}:chats:channel:{channelId} (messages)
* {space}:chats:dm:{sortedDid1+Did2} (DM channels)
*/
import type { DocSchema } from '../../shared/local-first/document';
// ── Transclusion: structured ref to any rSpace object ──
export interface Transclusion {
module: string; // "rtasks" | "rcal" | "rdocs" | etc.
docId: string;
objectId?: string;
display: 'inline' | 'card' | 'link';
snapshot?: { title: string; summary?: string; capturedAt: number };
}
// ── Thread metadata ──
export interface ThreadMeta {
participantDids: string[];
lastActivity: number;
replyCount: number;
}
// ── Document types ──
export interface ChannelInfo {
id: string;
name: string;
description: string;
isPrivate: boolean;
isDm: boolean;
createdBy: string | null;
createdAt: number;
updatedAt: number;
visibility?: import('../../shared/membrane').ObjectVisibility;
}
export interface ChatsDirectoryDoc {
meta: {
module: string;
collection: string;
version: number;
spaceSlug: string;
createdAt: number;
};
channels: Record<string, ChannelInfo>;
}
export interface Member {
userId: string;
displayName: string;
joinedAt: number;
}
export interface ChatMessage {
id: string;
channelId: string;
authorId: string;
authorName: string;
content: string;
replyTo: string | null;
threadId?: string; // points to root message ID if this is a thread reply
reactions: Record<string, string[]>; // emoji → DID[]
transclusions: Transclusion[];
editedAt: number | null;
createdAt: number;
visibility?: import('../../shared/membrane').ObjectVisibility;
}
export interface ChatChannelDoc {
meta: {
module: string;
collection: string;
version: number;
spaceSlug: string;
createdAt: number;
};
channelId: string;
messages: Record<string, ChatMessage>;
members: Record<string, Member>;
threads: Record<string, ThreadMeta>; // rootMessageId → thread metadata
pins: string[]; // pinned message IDs
isDm: boolean;
}
// ── Schema registration ──
export const chatsDirectorySchema: DocSchema<ChatsDirectoryDoc> = {
module: 'chats',
collection: 'channels',
version: 2,
init: (): ChatsDirectoryDoc => ({
meta: {
module: 'chats',
collection: 'channels',
version: 2,
spaceSlug: '',
createdAt: Date.now(),
},
channels: {},
}),
};
export const chatChannelSchema: DocSchema<ChatChannelDoc> = {
module: 'chats',
collection: 'channel',
version: 2,
init: (): ChatChannelDoc => ({
meta: {
module: 'chats',
collection: 'channel',
version: 2,
spaceSlug: '',
createdAt: Date.now(),
},
channelId: '',
messages: {},
members: {},
threads: {},
pins: [],
isDm: false,
}),
};
// ── Helpers ──
export function chatsDirectoryDocId(space: string) {
return `${space}:chats:channels` as const;
}
export function chatChannelDocId(space: string, channelId: string) {
return `${space}:chats:channel:${channelId}` as const;
}
export function dmChannelDocId(space: string, did1: string, did2: string): string {
const sorted = [did1, did2].sort();
return `${space}:chats:dm:${sorted[0]}+${sorted[1]}`;
}