import { useSyncExternalStore, createContext, useContext, useCallback, useState, useEffect, type Context } from 'react' import { DocHandle } from './doc-handle.js' import { Repo } from './repo.js' import type { DocumentId } from './document-id.js' import type { Doc } from '@automerge/automerge' export const RepoContext: Context = createContext(null) export function useRepo(): Repo { const repo = useContext(RepoContext) if (!repo) throw new Error('Repo was not found on RepoContext.') return repo } interface UseDocHandleOptions { suspense?: boolean } export function useDocHandle(id: DocumentId | undefined, options?: UseDocHandleOptions): DocHandle | undefined { if (options?.suspense) throw new Error('useDocHandle with suspense is not yet implemented in automerge-atproto') const repo = useRepo() const [handle, setHandle] = useState | undefined>(undefined) useEffect(() => { if (!id) { setHandle(undefined) return } let cancelled = false setHandle(undefined) repo.find(id).then((h) => { if (!cancelled) setHandle(h) }).catch((error) => { if (!cancelled) { console.error(`useDocHandle: failed to find document ${id}:`, error) setHandle(undefined) } }) return () => { cancelled = true } }, [repo, id]) return handle } export type UseDocumentReturn = [Doc, (changeFn: (doc: T) => void, options?: { message?: string, time?: number }) => void] const NOOP_CHANGE = () => {} export function useDocument(id: DocumentId | undefined): UseDocumentReturn | [undefined, () => void] { const handle = useDocHandle(id) const subscribe = useCallback( (callback: () => void) => { if (!handle) return () => {} handle.on('change', callback) return () => handle.off('change', callback) }, [handle], ) const getSnapshot = useCallback(() => { if (!handle?.isReady()) return undefined return handle.doc() }, [handle]) const doc = useSyncExternalStore(subscribe, getSnapshot) const change = useCallback( (changeFn: (doc: T) => void, options?: { message?: string, time?: number }) => { handle?.change(changeFn, options) }, [handle], ) if (!doc) return [undefined, NOOP_CHANGE] return [doc, change] } export function useDocHandles(_ids: readonly DocumentId[]): DocHandle[] { throw new Error('useDocHandles is not implemented in automerge-atproto') } export function useDocuments(): { ids: DocumentId[]; create: () => DocumentId } { throw new Error('useDocuments is not implemented in automerge-atproto') } export interface UseLocalAwarenessProps { handle?: DocHandle userId: string initialState: unknown heartbeatTime?: number } export function useLocalAwareness(_props: UseLocalAwarenessProps): unknown[] { throw new Error('useLocalAwareness is not implemented in automerge-atproto. Presence/awareness is separate from durable sync.') } export type PeerStates = Record export type Heartbeats = Record export interface UseRemoteAwarenessProps { handle?: DocHandle localUserId?: string offlineTimeout?: number getTime?: () => number } export function useRemoteAwareness(_props: UseRemoteAwarenessProps): [PeerStates, Heartbeats] { throw new Error('useRemoteAwareness is not implemented in automerge-atproto. Presence/awareness is separate from durable sync.') } export interface UsePresenceConfig { handle: DocHandle initialState?: State heartbeatMs?: number peerTtlMs?: number } export interface UsePresenceResult { peerStates: Record localState: State | undefined update: (channel: Channel, value: State[Channel]) => void start: (config?: Partial>) => void stop: () => void } export function usePresence(_config: UsePresenceConfig): UsePresenceResult { throw new Error('usePresence is not implemented in automerge-atproto. Presence/awareness is separate from durable sync.') }