@pmate/chat-sdk 0.12.0-next.67 → 0.12.0-next.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +73 -65
  2. package/lib/advanced.d.ts +10 -0
  3. package/lib/advanced.js +10 -0
  4. package/lib/chat.d.ts +4 -130
  5. package/lib/chat.js +9 -217
  6. package/lib/chatContract.d.ts +144 -0
  7. package/lib/chatContract.js +1 -0
  8. package/lib/chatError.d.ts +11 -0
  9. package/lib/chatError.js +13 -0
  10. package/lib/chatTypes.d.ts +119 -0
  11. package/lib/chatTypes.js +1 -0
  12. package/lib/credentials.d.ts +26 -0
  13. package/lib/credentials.js +109 -0
  14. package/lib/framework.d.ts +11 -0
  15. package/lib/framework.js +12 -0
  16. package/lib/history.d.ts +4 -0
  17. package/lib/history.js +8 -0
  18. package/lib/hooks/useChatSnapshot.js +4 -3
  19. package/lib/index.d.ts +1 -1
  20. package/lib/index.js +1 -1
  21. package/lib/internal/chatEngine.d.ts +131 -0
  22. package/lib/internal/chatEngine.js +280 -0
  23. package/lib/internal/chatRegistry.d.ts +4 -0
  24. package/lib/internal/chatRegistry.js +4 -0
  25. package/lib/internal/createPublicChat.d.ts +4 -0
  26. package/lib/internal/createPublicChat.js +52 -0
  27. package/lib/messageView.d.ts +2 -33
  28. package/lib/messageView.js +1 -1
  29. package/lib/runtime/chatClient.d.ts +1 -0
  30. package/lib/runtime/chatClient.js +7 -2
  31. package/lib/runtime/chatError.d.ts +2 -11
  32. package/lib/runtime/chatError.js +3 -14
  33. package/lib/runtime/chatSession.d.ts +1 -0
  34. package/lib/runtime/chatSession.js +14 -1
  35. package/lib/runtime/createChat.d.ts +6 -2
  36. package/lib/runtime/createChat.js +17 -7
  37. package/lib/runtime/messageClient.d.ts +5 -0
  38. package/lib/runtime/messageClient.js +32 -0
  39. package/lib/runtime/messageStore.d.ts +2 -0
  40. package/lib/runtime/messageStore.js +12 -0
  41. package/lib/runtime/readCounts.js +1 -1
  42. package/lib/runtime/readSync.js +1 -1
  43. package/lib/runtime/socialReadClient.d.ts +1 -1
  44. package/lib/runtime/socialReadClient.js +4 -4
  45. package/package.json +16 -1
@@ -0,0 +1,144 @@
1
+ import type { ChatError } from "./chatError.js";
2
+ import type { ChatMention } from "./mentions/model.js";
3
+ import type { TokenProvider } from "./credentials.js";
4
+ import type { ChatGroup, ChatSnapshot, ChatThreadSnapshot, ReadOptions } from "./chatTypes.js";
5
+ export type { TokenProvider, TokenRequest } from "./credentials.js";
6
+ export type ConversationTarget = {
7
+ peerId: string;
8
+ groupId?: never;
9
+ } | {
10
+ groupId: string;
11
+ peerId?: never;
12
+ };
13
+ export type CreateChatOptions = {
14
+ appId: string;
15
+ accountId: string;
16
+ profileId: string;
17
+ teamId: string;
18
+ threadId?: string;
19
+ endpoint?: string;
20
+ uploadEndpoint?: string;
21
+ clientInstanceId?: string;
22
+ fetch?: typeof fetch;
23
+ onError?: (error: ChatError) => void;
24
+ } & ({
25
+ token: string;
26
+ getToken?: never;
27
+ } | {
28
+ getToken: TokenProvider;
29
+ token?: never;
30
+ });
31
+ export type SendOptions = {
32
+ waitFor?: "accepted" | "delivered";
33
+ };
34
+ export type SentMessage = Readonly<{
35
+ id: string;
36
+ timestamp: number;
37
+ }>;
38
+ export type ReadersQuery = {
39
+ afterProfileId?: string;
40
+ revision?: number;
41
+ };
42
+ export type ReadersPage = Readonly<{
43
+ appId: string;
44
+ threadId: string;
45
+ messageId: string;
46
+ revision: number;
47
+ total: number;
48
+ profileIds: string[];
49
+ nextAfter: string | null;
50
+ resetRequired: boolean;
51
+ }>;
52
+ export type GroupChanges = {
53
+ expectedRevision?: string;
54
+ addMembers?: readonly string[];
55
+ removeMembers?: readonly string[];
56
+ title?: string;
57
+ avatar?: string;
58
+ };
59
+ export type VoiceAttachment = Readonly<{
60
+ url: string;
61
+ release: () => void;
62
+ }>;
63
+ export type SubscribeOptions<T> = {
64
+ fields?: readonly (keyof T)[];
65
+ };
66
+ type ContentChoices = {
67
+ text: {
68
+ text: string;
69
+ mentions?: ChatMention[];
70
+ quote?: {
71
+ messageId?: string;
72
+ senderName: string;
73
+ content: string;
74
+ };
75
+ };
76
+ image: {
77
+ image: {
78
+ url: string;
79
+ name?: string;
80
+ };
81
+ };
82
+ voice: {
83
+ voice: ({
84
+ blob: Blob;
85
+ url?: never;
86
+ } | {
87
+ url: string;
88
+ blob?: never;
89
+ }) & {
90
+ durationMs: number;
91
+ transcript?: string;
92
+ };
93
+ };
94
+ recall: {
95
+ recall: {
96
+ messageId: string;
97
+ };
98
+ };
99
+ reaction: {
100
+ reaction: {
101
+ messageId: string;
102
+ emojiId: string;
103
+ active: boolean;
104
+ };
105
+ };
106
+ invitation: {
107
+ invitation: {
108
+ message?: string;
109
+ };
110
+ };
111
+ };
112
+ export type OutgoingContent = {
113
+ [K in keyof ContentChoices]: ContentChoices[K] & {
114
+ [P in Exclude<keyof ContentChoices, K>]?: never;
115
+ };
116
+ }[keyof ContentChoices];
117
+ /** Default public client. Protocol and synchronous store internals are separate entry points. */
118
+ export interface Chat {
119
+ start(): Promise<void>;
120
+ close(): Promise<void>;
121
+ resolveThreadId(target: ConversationTarget, options?: ReadOptions): Promise<string>;
122
+ getThread(threadId: string, options?: ReadOptions): Promise<ChatThreadSnapshot>;
123
+ loadEarlier(threadId: string, options?: ReadOptions): Promise<ChatThreadSnapshot>;
124
+ refreshThread(threadId: string, options?: ReadOptions): Promise<ChatThreadSnapshot>;
125
+ subscribeThread(threadId: string, listener: (snapshot: ChatThreadSnapshot) => void, options?: SubscribeOptions<ChatThreadSnapshot>): () => void;
126
+ subscribeChat(listener: (snapshot: ChatSnapshot) => void, options?: SubscribeOptions<ChatSnapshot>): () => void;
127
+ sendMessage(threadId: string, content: OutgoingContent, options?: SendOptions): Promise<SentMessage>;
128
+ retryMessage(messageId: string): Promise<SentMessage>;
129
+ observeMessages(threadId: string, ids: string[], event: "visible" | "played"): Promise<void>;
130
+ markRead(threadId: string): Promise<void>;
131
+ getReaders(threadId: string, messageId: string, options?: ReadersQuery & ReadOptions): Promise<ReadersPage>;
132
+ watchReaders(threadId: string | undefined, ids: string[], receiver?: string): Promise<void>;
133
+ getVoiceAttachment(threadId: string, messageId: string, options?: ReadOptions): Promise<VoiceAttachment>;
134
+ getGroup(threadId: string, options?: ReadOptions): Promise<ChatGroup>;
135
+ refreshGroup(threadId: string, options?: ReadOptions): Promise<ChatGroup>;
136
+ createGroup(input: {
137
+ groupId?: string;
138
+ title: string;
139
+ members: readonly string[];
140
+ }): Promise<ChatGroup & {
141
+ threadId: string;
142
+ }>;
143
+ updateGroup(threadId: string, changes: GroupChanges): Promise<ChatGroup>;
144
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ export type ChatErrorCode = "unavailable" | "access-denied" | "authentication-required" | "storage-unavailable" | "invalid-input" | "conflict" | "closed" | "busy";
2
+ /** Stable recovery categories. The original failure remains available as cause. */
3
+ export declare class ChatError extends Error {
4
+ readonly code: ChatErrorCode;
5
+ readonly retryable: boolean;
6
+ readonly status?: number;
7
+ constructor(code: ChatErrorCode, message: string, options?: {
8
+ cause?: unknown;
9
+ status?: number;
10
+ });
11
+ }
@@ -0,0 +1,13 @@
1
+ /** Stable recovery categories. The original failure remains available as cause. */
2
+ export class ChatError extends Error {
3
+ code;
4
+ retryable;
5
+ status;
6
+ constructor(code, message, options = {}) {
7
+ super(message, { cause: options.cause });
8
+ this.code = code;
9
+ this.name = "ChatError";
10
+ this.status = options.status;
11
+ this.retryable = ["unavailable", "storage-unavailable", "busy"].includes(code);
12
+ }
13
+ }
@@ -0,0 +1,119 @@
1
+ import type { ChatError } from "./chatError.js";
2
+ import type { ChatMention } from "./mentions/model.js";
3
+ /** Stable product model. No storage, transport, Session or projection types. */
4
+ export type ChatMessage = Readonly<{
5
+ id: string;
6
+ threadId: string;
7
+ timestamp: number;
8
+ from?: string;
9
+ mine: boolean;
10
+ status: "sending" | "sent" | "failed" | "read";
11
+ recalled: boolean;
12
+ unread?: boolean;
13
+ attentionEligible?: boolean;
14
+ reading?: Readonly<{
15
+ seq?: number;
16
+ read: boolean;
17
+ readPolicy: "visible" | "explicit";
18
+ }>;
19
+ reactions: readonly Readonly<{
20
+ emojiId: string;
21
+ userIds: string[];
22
+ mine: boolean;
23
+ }>[];
24
+ kind: "text" | "image" | "voice" | "emoji" | "quote" | "invitation" | "system" | "attachment";
25
+ text: string;
26
+ senderName?: string;
27
+ senderAvatar?: string;
28
+ timeLabel?: string;
29
+ image?: Readonly<{
30
+ url: string;
31
+ name?: string;
32
+ }>;
33
+ voice?: Readonly<{
34
+ url: string;
35
+ durationMs: number;
36
+ transcript?: string;
37
+ }>;
38
+ quote?: Readonly<{
39
+ messageId?: string;
40
+ senderName: string;
41
+ content: string;
42
+ recalled?: boolean;
43
+ }>;
44
+ attachment?: Readonly<{
45
+ name?: string;
46
+ size?: string;
47
+ }>;
48
+ richContent?: unknown;
49
+ mentions?: readonly ChatMention[];
50
+ recipientCount?: number;
51
+ system?: Readonly<{
52
+ type: "group-created" | "group-updated" | "contact-updated";
53
+ title?: string;
54
+ }>;
55
+ }>;
56
+ export type ChatGroup = Readonly<{
57
+ groupId: string;
58
+ teamId: string;
59
+ ownerId: string;
60
+ title: string;
61
+ avatar?: string;
62
+ memberIds: readonly string[];
63
+ revision: string;
64
+ }>;
65
+ export type ConversationInfo = Readonly<{
66
+ kind: "dm" | "group";
67
+ targetId: string;
68
+ }>;
69
+ export type UnreadState = Readonly<{
70
+ unreadMessageCount: number;
71
+ unreadMentionCount: number;
72
+ badgeCount: number;
73
+ }>;
74
+ export type ReadCounts = Readonly<{
75
+ appId: string;
76
+ threadId: string;
77
+ revision: number;
78
+ counts: Record<string, number>;
79
+ }>;
80
+ type ThreadFields = Readonly<{
81
+ threadId: string;
82
+ messages: readonly ChatMessage[];
83
+ hasEarlier: boolean;
84
+ conversation?: ConversationInfo;
85
+ unread: UnreadState | undefined;
86
+ group: ChatGroup | undefined;
87
+ readCounts: ReadCounts | undefined;
88
+ }>;
89
+ /** A refresh failure retains ready data; only an initial failure has status error. */
90
+ export type ChatThreadSnapshot = ThreadFields & (Readonly<{
91
+ status: "pending";
92
+ ready: false;
93
+ error?: undefined;
94
+ }> | Readonly<{
95
+ status: "ready";
96
+ ready: true;
97
+ error?: ChatError;
98
+ }> | Readonly<{
99
+ status: "error";
100
+ ready: false;
101
+ error: ChatError;
102
+ }>);
103
+ export type ChatConnectionState = "connecting" | "reconnecting" | "authenticating" | "connected" | "authentication_required" | "closed";
104
+ export type ChatSnapshot = Readonly<{
105
+ threads: readonly {
106
+ threadId: string;
107
+ lastMessage?: ChatMessage;
108
+ group: ChatGroup | undefined;
109
+ unread: UnreadState | undefined;
110
+ outgoingCount: number;
111
+ }[];
112
+ unreadMessageCount: number | undefined;
113
+ badgeCount: number | undefined;
114
+ connection: ChatConnectionState;
115
+ }>;
116
+ export type ReadOptions = {
117
+ signal?: AbortSignal;
118
+ };
119
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ export type TokenRequest = Readonly<{
2
+ forceRefresh: boolean;
3
+ signal: AbortSignal;
4
+ }>;
5
+ export type TokenProvider = (request: TokenRequest) => string | Promise<string>;
6
+ /** Cancel the caller's wait, not the underlying shared operation. */
7
+ export declare function waitForCaller<T>(task: Promise<T>, signal?: AbortSignal, onAbandonedValue?: (value: T) => void): Promise<T>;
8
+ /** One scoped credential owner; stale 401s share one refresh rather than cascading. */
9
+ export declare class ChatCredentials {
10
+ private provider;
11
+ private origin;
12
+ private transport;
13
+ private lifetime;
14
+ private flight?;
15
+ private refreshing?;
16
+ private latest?;
17
+ private recovery?;
18
+ private recoveredFrom;
19
+ private version;
20
+ constructor(provider: TokenProvider, origin: string, transport?: typeof fetch);
21
+ private acquire;
22
+ private recover;
23
+ getToken: () => Promise<string>;
24
+ fetch: typeof fetch;
25
+ close(): void;
26
+ }
@@ -0,0 +1,109 @@
1
+ import { ChatError } from "./chatError.js";
2
+ /** Cancel the caller's wait, not the underlying shared operation. */
3
+ export function waitForCaller(task, signal, onAbandonedValue) {
4
+ if (!signal)
5
+ return task;
6
+ if (signal.aborted) {
7
+ void task.then(value => onAbandonedValue?.(value), () => { });
8
+ return Promise.reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
9
+ }
10
+ return new Promise((resolve, reject) => {
11
+ let settled = false;
12
+ const abort = () => { if (settled)
13
+ return; settled = true; signal.removeEventListener("abort", abort); reject(signal.reason ?? new DOMException("Aborted", "AbortError")); };
14
+ signal.addEventListener("abort", abort, { once: true });
15
+ task.then(value => { signal.removeEventListener("abort", abort); if (settled) {
16
+ onAbandonedValue?.(value);
17
+ return;
18
+ } settled = true; resolve(value); }, error => { signal.removeEventListener("abort", abort); if (!settled) {
19
+ settled = true;
20
+ reject(error);
21
+ } });
22
+ });
23
+ }
24
+ /** One scoped credential owner; stale 401s share one refresh rather than cascading. */
25
+ export class ChatCredentials {
26
+ provider;
27
+ origin;
28
+ transport;
29
+ lifetime = new AbortController();
30
+ flight;
31
+ refreshing;
32
+ latest;
33
+ recovery;
34
+ recoveredFrom = -1;
35
+ version = 0;
36
+ constructor(provider, origin, transport = fetch) {
37
+ this.provider = provider;
38
+ this.origin = origin;
39
+ this.transport = transport;
40
+ }
41
+ acquire(forceRefresh = false) {
42
+ if (this.lifetime.signal.aborted)
43
+ return Promise.reject(new ChatError("closed", "Chat credentials closed"));
44
+ if (this.refreshing)
45
+ return this.refreshing;
46
+ if (!forceRefresh && this.flight)
47
+ return this.flight;
48
+ const version = forceRefresh ? ++this.version : this.version;
49
+ const signal = AbortSignal.any([this.lifetime.signal, AbortSignal.timeout(15000)]);
50
+ const task = waitForCaller(Promise.resolve().then(() => this.provider({ forceRefresh, signal })), signal).then(token => {
51
+ if (this.lifetime.signal.aborted)
52
+ throw new ChatError("closed", "Chat credentials closed");
53
+ if (typeof token !== "string" || !token.trim())
54
+ throw new ChatError("authentication-required", "A valid access token is required");
55
+ if (version < this.version && this.latest)
56
+ return this.latest;
57
+ const value = { token, version };
58
+ this.latest = value;
59
+ return value;
60
+ }).catch(error => {
61
+ if (error?.status === 401)
62
+ throw new ChatError("authentication-required", "Credentials require a fresh login", { cause: error, status: 401 });
63
+ if (error?.status === 403)
64
+ throw new ChatError("access-denied", "Credential access denied", { cause: error, status: 403 });
65
+ throw error;
66
+ }).finally(() => { if (this.flight === task)
67
+ this.flight = undefined; if (this.refreshing === task)
68
+ this.refreshing = undefined; });
69
+ if (forceRefresh)
70
+ this.refreshing = task;
71
+ else
72
+ this.flight = task;
73
+ return task;
74
+ }
75
+ recover(used) {
76
+ if (this.latest && this.latest.version > used.version)
77
+ return Promise.resolve(this.latest);
78
+ if (this.recovery && used.version <= this.recoveredFrom)
79
+ return this.recovery;
80
+ this.recoveredFrom = used.version;
81
+ this.recovery = this.acquire(true);
82
+ return this.recovery;
83
+ }
84
+ getToken = async () => { return (await this.acquire()).token; };
85
+ fetch = async (input, init) => {
86
+ const url = new URL(input instanceof Request ? input.url : String(input)), headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined));
87
+ // Never attach an app credential to uploads, other origins or unauthenticated requests.
88
+ if (url.origin !== this.origin || !headers.has("authorization"))
89
+ return this.transport(input, init);
90
+ const callerSignal = init?.signal ?? (input instanceof Request ? input.signal : undefined);
91
+ const signal = AbortSignal.any([this.lifetime.signal, ...(callerSignal ? [callerSignal] : [])]);
92
+ signal?.throwIfAborted();
93
+ const original = input instanceof Request ? input.clone() : input;
94
+ const send = async (token) => {
95
+ signal?.throwIfAborted();
96
+ headers.set("authorization", `Bearer ${token}`);
97
+ return this.transport(original instanceof Request ? original.clone() : original, { ...init, headers, signal });
98
+ };
99
+ const used = await waitForCaller(this.acquire(), signal ?? undefined);
100
+ const response = await send(used.token);
101
+ if (response.status !== 401)
102
+ return response;
103
+ // Denial (403) is terminal. Only an unauthenticated response gets one replay.
104
+ void response.body?.cancel().catch(() => { });
105
+ const next = await waitForCaller(this.recover(used), signal);
106
+ return send(next.token);
107
+ };
108
+ close() { this.lifetime.abort(new ChatError("closed", "Chat credentials closed")); }
109
+ }
@@ -0,0 +1,11 @@
1
+ import type { Chat } from "./chatContract.js";
2
+ import type { ChatSnapshot, ChatThreadSnapshot } from "./chatTypes.js";
3
+ export interface ChatStore {
4
+ subscribeThread(threadId: string, listener: () => void, fields?: readonly (keyof ChatThreadSnapshot)[]): () => void;
5
+ getSnapshot(): ChatSnapshot;
6
+ getSnapshot(threadId: string): ChatThreadSnapshot;
7
+ subscribeChanges(listener: () => void): () => void;
8
+ subscribeChanges(threadId: string, listener: () => void, fields?: readonly (keyof ChatThreadSnapshot)[]): () => void;
9
+ }
10
+ /** Synchronous memory reads are for external-store integrations, not cache selection. */
11
+ export declare function getChatStore(chat: Chat): ChatStore;
@@ -0,0 +1,12 @@
1
+ import { chatEngine } from "./internal/chatRegistry.js";
2
+ const stores = new WeakMap();
3
+ /** Synchronous memory reads are for external-store integrations, not cache selection. */
4
+ export function getChatStore(chat) {
5
+ let store = stores.get(chat);
6
+ if (!store) {
7
+ const engine = chatEngine(chat);
8
+ store = { subscribeThread: (id, listener, fields) => engine.subscribeThread(id, listener, { fields, emitCurrent: false }), getSnapshot: ((id) => id === undefined ? engine.getSnapshot() : engine.getSnapshot(id)), subscribeChanges: ((target, callback, fields) => typeof target === "function" ? engine.subscribe(target, { emitCurrent: false }) : engine.subscribe(target, callback, { fields, emitCurrent: false })) };
9
+ stores.set(chat, store);
10
+ }
11
+ return store;
12
+ }
@@ -0,0 +1,4 @@
1
+ import type { Chat } from "./chatContract.js";
2
+ import type { ChatThreadSnapshot, ReadOptions } from "./chatTypes.js";
3
+ /** Explicit complete-history operation for search/export; may scan many pages. */
4
+ export declare function loadThreadHistory(chat: Chat, threadId: string, { signal }?: ReadOptions): Promise<ChatThreadSnapshot>;
package/lib/history.js ADDED
@@ -0,0 +1,8 @@
1
+ import { chatEngine } from "./internal/chatRegistry.js";
2
+ import { waitForCaller } from "./credentials.js";
3
+ /** Explicit complete-history operation for search/export; may scan many pages. */
4
+ export function loadThreadHistory(chat, threadId, { signal } = {}) {
5
+ if (signal?.aborted)
6
+ return Promise.reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
7
+ return waitForCaller(chatEngine(chat).getMessages(threadId, { all: true }), signal);
8
+ }
@@ -1,8 +1,9 @@
1
1
  import { useCallback, useSyncExternalStore } from "react";
2
+ import { getChatStore } from "../framework.js";
2
3
  const emptySummary = Object.freeze({ threads: [], unreadMessageCount: undefined, badgeCount: undefined, connection: "closed" });
3
- const emptyThread = Object.freeze({ threadId: "", messages: [], ready: false, hasEarlier: false, unread: undefined, group: undefined, readCounts: undefined });
4
+ const emptyThread = Object.freeze({ threadId: "", messages: [], status: "pending", ready: false, hasEarlier: false, unread: undefined, group: undefined, readCounts: undefined });
4
5
  export function useChatSnapshot(chat, threadId) {
5
- const subscribe = useCallback((notify) => !chat ? () => { } : threadId === undefined ? chat.subscribe(notify, { emitCurrent: false }) : chat.subscribe(threadId, notify, { emitCurrent: false }), [chat, threadId]);
6
- const snapshot = useCallback(() => threadId === undefined ? chat?.getSnapshot() ?? emptySummary : chat?.getSnapshot(threadId) ?? emptyThread, [chat, threadId]);
6
+ const subscribe = useCallback((notify) => !chat ? () => { } : threadId === undefined ? getChatStore(chat).subscribeChanges(notify) : getChatStore(chat).subscribeThread(threadId, notify), [chat, threadId]);
7
+ const snapshot = useCallback(() => threadId === undefined ? (chat ? getChatStore(chat).getSnapshot() : undefined) ?? emptySummary : (chat ? getChatStore(chat).getSnapshot(threadId) : undefined) ?? emptyThread, [chat, threadId]);
7
8
  return useSyncExternalStore(subscribe, snapshot, () => threadId === undefined ? emptySummary : emptyThread);
8
9
  }
package/lib/index.d.ts CHANGED
@@ -41,5 +41,5 @@ export * from "./runtime/socialReadClient";
41
41
  export * from "./ux/imagePaste";
42
42
  export * from "./hooks/useImageAttachments";
43
43
  export * from "./components/ImageAttachmentTray";
44
- export { createChat, Chat, ChatError, type ChatThreadSnapshot, type ChatMessage as MessageView, type ChatErrorCode, type ConversationTarget, type OutgoingContent, type CreateChatOptions, type ChatSnapshot, type SentMessage, type MessageQuery, type ReadersQuery, type GroupChanges } from "./chat.js";
44
+ export { createChat, type Chat, ChatError, type ChatThreadSnapshot, type ChatMessage as MessageView, type ChatErrorCode, type ConversationTarget, type OutgoingContent, type CreateChatOptions, type ChatSnapshot, type SentMessage, type ReadOptions, type ReadersQuery, type GroupChanges } from "./chat.js";
45
45
  export { useChatSnapshot } from "./hooks/useChatSnapshot.js";
package/lib/index.js CHANGED
@@ -41,5 +41,5 @@ export * from "./runtime/socialReadClient";
41
41
  export * from "./ux/imagePaste";
42
42
  export * from "./hooks/useImageAttachments";
43
43
  export * from "./components/ImageAttachmentTray";
44
- export { createChat, Chat, ChatError } from "./chat.js";
44
+ export { createChat, ChatError } from "./chat.js";
45
45
  export { useChatSnapshot } from "./hooks/useChatSnapshot.js";
@@ -0,0 +1,131 @@
1
+ export type { ChatMessage } from "../chatTypes.js";
2
+ import { type CreateChatOptions } from "../runtime/createChat.js";
3
+ import type { ChatSession } from "../runtime/chatSession.js";
4
+ import type { CachedChatMessage } from "../runtime/cachedMessage.js";
5
+ import type { ConversationTarget } from "../runtime/conversationDirectory.js";
6
+ import type { OutgoingContent as RuntimeContent } from "../runtime/outgoingContent.js";
7
+ export { ChatError } from "../chatError.js";
8
+ export type { ChatErrorCode } from "../chatError.js";
9
+ export type { CreateChatOptions, ConversationTarget };
10
+ export type OutgoingContent = Exclude<RuntimeContent, {
11
+ groupCreate: unknown;
12
+ } | {
13
+ groupUpdate: unknown;
14
+ }>;
15
+ import type { ChatThreadSnapshot, ChatSnapshot, ReadOptions } from "../chatTypes.js";
16
+ export type { ChatGroup, ConversationInfo, ChatThreadSnapshot, ChatSnapshot, ReadOptions } from "../chatTypes.js";
17
+ export type MessageQuery = {
18
+ earlier?: boolean;
19
+ all?: boolean;
20
+ refresh?: boolean;
21
+ };
22
+ export type ReadersQuery = {
23
+ afterProfileId?: string;
24
+ revision?: number;
25
+ };
26
+ export type GroupChanges = {
27
+ expectedRevision?: string;
28
+ addMembers?: readonly string[];
29
+ removeMembers?: readonly string[];
30
+ title?: string;
31
+ avatar?: string;
32
+ };
33
+ export type SendOptions = {
34
+ waitFor?: "accepted" | "delivered";
35
+ };
36
+ export type SentMessage = Readonly<{
37
+ id: string;
38
+ timestamp: number;
39
+ }>;
40
+ type SubscribeOptions<T> = {
41
+ fields?: readonly (keyof T)[];
42
+ emitCurrent?: boolean;
43
+ };
44
+ /** App-facing API. Cache coverage, raw protocol and synchronization stay in advanced. */
45
+ export declare class ChatEngine {
46
+ readonly advanced: ChatSession<CachedChatMessage>;
47
+ private options;
48
+ private groups;
49
+ private group;
50
+ private present;
51
+ private redactedQuotes;
52
+ private messageLists;
53
+ private messages;
54
+ private views;
55
+ private summary?;
56
+ private targets;
57
+ private errors;
58
+ private listeners;
59
+ private closed;
60
+ constructor(advanced: ChatSession<CachedChatMessage>, options: CreateChatOptions);
61
+ private operation;
62
+ private deny;
63
+ private threadOperation;
64
+ start(): Promise<void>;
65
+ /** Resolve a scoped ID locally, registering its target when needed. No remote group is created. */
66
+ resolveThreadId(target: ConversationTarget): Promise<string>;
67
+ /** A trusted recent window. Source selection and synchronization belong to the SDK. */
68
+ getThread(threadId: string, options?: ReadOptions): Promise<ChatThreadSnapshot>;
69
+ loadEarlier(threadId: string, options?: ReadOptions): Promise<ChatThreadSnapshot>;
70
+ refreshThread(threadId: string, options?: ReadOptions): Promise<ChatThreadSnapshot>;
71
+ private readForCaller;
72
+ private threadSubscribers;
73
+ private openQueue;
74
+ private opening;
75
+ private drainOpens;
76
+ /** Listen first, then open the local window; concurrent consumers share the initial read. */
77
+ subscribeThread(threadId: string, listener: (snapshot: ChatThreadSnapshot) => void, options?: SubscribeOptions<ChatThreadSnapshot>): () => void;
78
+ /** @deprecated Use resolveThreadId. Retained only for the migration window. */
79
+ getConversation(target: ConversationTarget): Promise<string>;
80
+ getSnapshot(): ChatSnapshot;
81
+ getSnapshot(threadId: string): ChatThreadSnapshot;
82
+ getMessages(threadId: string, query?: MessageQuery): Promise<ChatThreadSnapshot>;
83
+ sendMessage(threadId: string, content: OutgoingContent, options?: SendOptions): Promise<SentMessage>;
84
+ retryMessage(messageId: string): Promise<SentMessage>;
85
+ subscribe(listener: (snapshot: ChatSnapshot) => void, options?: SubscribeOptions<ChatSnapshot>): () => void;
86
+ subscribe(threadId: string, listener: (snapshot: ChatThreadSnapshot) => void, options?: SubscribeOptions<ChatThreadSnapshot>): () => void;
87
+ private sessionFields;
88
+ private notify;
89
+ observeMessages(threadId: string, ids: string[], event: "visible" | "played"): Promise<void>;
90
+ markRead(threadId: string): Promise<void>;
91
+ getReaders(threadId: string, messageId: string, page?: ReadersQuery, signal?: AbortSignal): Promise<import("../runtime/messageReaders.js").MessageReadersPage>;
92
+ watchReaders(threadId: string | undefined, ids: string[], receiver?: string): Promise<void>;
93
+ handleReach(event: Parameters<ChatSession<CachedChatMessage>["handleReach"]>[0]): Promise<"handled" | undefined>;
94
+ getVoiceAttachment(threadId: string, messageId: string): Promise<import("../runtime/durableSender.js").VoiceAttachmentLease>;
95
+ getGroup(threadId: string, options?: {
96
+ refresh?: boolean;
97
+ }): Promise<Readonly<{
98
+ groupId: string;
99
+ teamId: string;
100
+ ownerId: string;
101
+ title: string;
102
+ avatar?: string;
103
+ memberIds: readonly string[];
104
+ revision: string;
105
+ }>>;
106
+ createGroup(input: {
107
+ groupId?: string;
108
+ title: string;
109
+ members: readonly string[];
110
+ }): Promise<{
111
+ threadId: string;
112
+ groupId: string;
113
+ teamId: string;
114
+ ownerId: string;
115
+ title: string;
116
+ avatar?: string;
117
+ memberIds: readonly string[];
118
+ revision: string;
119
+ }>;
120
+ updateGroup(threadId: string, changes: GroupChanges): Promise<Readonly<{
121
+ groupId: string;
122
+ teamId: string;
123
+ ownerId: string;
124
+ title: string;
125
+ avatar?: string;
126
+ memberIds: readonly string[];
127
+ revision: string;
128
+ }>>;
129
+ revokeAccess(): Promise<void>;
130
+ close(): Promise<void>;
131
+ }