@pmate/chat-sdk 0.12.0-next.7 → 0.12.0-next.71

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 (105) hide show
  1. package/README.md +170 -81
  2. package/lib/advanced.d.ts +10 -0
  3. package/lib/advanced.js +10 -0
  4. package/lib/chat.d.ts +5 -0
  5. package/lib/chat.js +11 -0
  6. package/lib/chatContract.d.ts +148 -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 +121 -0
  11. package/lib/chatTypes.js +1 -0
  12. package/lib/commands.d.ts +20 -0
  13. package/lib/commands.js +12 -0
  14. package/lib/components/ChatInputDesktop.js +5 -4
  15. package/lib/components/ChatInputMobile.js +5 -4
  16. package/lib/components/CommandMenu.d.ts +15 -0
  17. package/lib/components/CommandMenu.js +10 -0
  18. package/lib/credentials.d.ts +26 -0
  19. package/lib/credentials.js +110 -0
  20. package/lib/fileMetadata.d.ts +9 -0
  21. package/lib/fileMetadata.js +18 -0
  22. package/lib/framework.d.ts +11 -0
  23. package/lib/framework.js +12 -0
  24. package/lib/history.d.ts +4 -0
  25. package/lib/history.js +8 -0
  26. package/lib/hooks/useChatSnapshot.d.ts +4 -0
  27. package/lib/hooks/useChatSnapshot.js +9 -0
  28. package/lib/hooks/useComposerSuggestions.d.ts +50 -0
  29. package/lib/hooks/useComposerSuggestions.js +70 -0
  30. package/lib/index.d.ts +5 -0
  31. package/lib/index.js +5 -0
  32. package/lib/internal/chatEngine.d.ts +131 -0
  33. package/lib/internal/chatEngine.js +280 -0
  34. package/lib/internal/chatRegistry.d.ts +4 -0
  35. package/lib/internal/chatRegistry.js +4 -0
  36. package/lib/internal/createPublicChat.d.ts +4 -0
  37. package/lib/internal/createPublicChat.js +52 -0
  38. package/lib/messageView.d.ts +6 -0
  39. package/lib/messageView.js +103 -0
  40. package/lib/runtime/batchedReadIndex.d.ts +16 -3
  41. package/lib/runtime/batchedReadIndex.js +50 -4
  42. package/lib/runtime/cachedMessage.d.ts +12 -0
  43. package/lib/runtime/cachedMessage.js +18 -0
  44. package/lib/runtime/chatClient.d.ts +2 -0
  45. package/lib/runtime/chatClient.js +8 -3
  46. package/lib/runtime/chatError.d.ts +3 -0
  47. package/lib/runtime/chatError.js +15 -0
  48. package/lib/runtime/chatSession.d.ts +127 -13
  49. package/lib/runtime/chatSession.js +727 -40
  50. package/lib/runtime/connectivity.d.ts +4 -0
  51. package/lib/runtime/connectivity.js +8 -0
  52. package/lib/runtime/conversationDirectory.d.ts +37 -0
  53. package/lib/runtime/conversationDirectory.js +116 -0
  54. package/lib/runtime/conversationRead.d.ts +13 -0
  55. package/lib/runtime/conversationRead.js +34 -0
  56. package/lib/runtime/conversationReading.d.ts +28 -0
  57. package/lib/runtime/conversationReading.js +47 -0
  58. package/lib/runtime/createChat.d.ts +11 -7
  59. package/lib/runtime/createChat.js +46 -23
  60. package/lib/runtime/durableSender.d.ts +97 -14
  61. package/lib/runtime/durableSender.js +786 -71
  62. package/lib/runtime/groupClient.d.ts +76 -0
  63. package/lib/runtime/groupClient.js +325 -0
  64. package/lib/runtime/index.d.ts +7 -0
  65. package/lib/runtime/index.js +5 -0
  66. package/lib/runtime/messageClient.d.ts +35 -8
  67. package/lib/runtime/messageClient.js +364 -66
  68. package/lib/runtime/messageControls.d.ts +19 -0
  69. package/lib/runtime/messageControls.js +44 -0
  70. package/lib/runtime/messageLaunch.d.ts +44 -0
  71. package/lib/runtime/messageLaunch.js +137 -0
  72. package/lib/runtime/messageQuotes.d.ts +3 -0
  73. package/lib/runtime/messageQuotes.js +17 -0
  74. package/lib/runtime/messageReaders.d.ts +12 -1
  75. package/lib/runtime/messageReaders.js +67 -3
  76. package/lib/runtime/messageStore.d.ts +40 -2
  77. package/lib/runtime/messageStore.js +227 -11
  78. package/lib/runtime/messageTimeline.d.ts +50 -0
  79. package/lib/runtime/messageTimeline.js +120 -0
  80. package/lib/runtime/outboxSchema.d.ts +5 -0
  81. package/lib/runtime/outboxSchema.js +59 -0
  82. package/lib/runtime/outgoingContent.d.ts +89 -0
  83. package/lib/runtime/outgoingContent.js +113 -0
  84. package/lib/runtime/readBatchJournal.d.ts +35 -0
  85. package/lib/runtime/readBatchJournal.js +210 -0
  86. package/lib/runtime/readCounts.d.ts +15 -1
  87. package/lib/runtime/readCounts.js +97 -9
  88. package/lib/runtime/readIndex.d.ts +68 -7
  89. package/lib/runtime/readIndex.js +460 -22
  90. package/lib/runtime/readSchema.d.ts +2 -0
  91. package/lib/runtime/readSchema.js +58 -0
  92. package/lib/runtime/readSync.d.ts +1 -0
  93. package/lib/runtime/readSync.js +26 -9
  94. package/lib/runtime/readingClient.d.ts +19 -0
  95. package/lib/runtime/readingClient.js +131 -19
  96. package/lib/runtime/readingContent.d.ts +5 -0
  97. package/lib/runtime/readingContent.js +23 -0
  98. package/lib/runtime/responseCache.d.ts +59 -0
  99. package/lib/runtime/responseCache.js +232 -0
  100. package/lib/runtime/socialReadClient.d.ts +4 -2
  101. package/lib/runtime/socialReadClient.js +12 -8
  102. package/lib/runtime/voiceUpload.d.ts +2 -0
  103. package/lib/runtime/voiceUpload.js +27 -0
  104. package/lib/types/chatInput.d.ts +3 -0
  105. package/package.json +27 -2
package/README.md CHANGED
@@ -39,7 +39,7 @@ A dropped socket (including a network change or a suspended laptop) previously r
39
39
 
40
40
  `client.watchConnection(handler)` immediately reports the current state and returns an unsubscribe function. States are `connecting`, `reconnecting`, `authenticating`, `connected`, `authentication_required`, and `closed`. Only `connected` means the Hub acknowledged authentication. A rejected authentication stops retries; supply a fresh login session using a new client. `close()` cancels retries, heartbeat timers and browser wake listeners.
41
41
 
42
- Messages sent without an acknowledgement are rejected on disconnect and are **not replayed**: delivery is uncertain, so automatic retries could duplicate them. Callers should preserve drafts, display a reconnecting state, and refresh message history after recovery. No durable offline outbox is provided. An initial `connect()` wait times out after 30 seconds, but recovery continues until explicitly closed; retain the client and observe its connection state.
42
+ The low-level `ChatClient.sendChat` rejects unacknowledged sends on disconnect and does not provide a durable outbox. Browser applications should use `createChat().sendMessage` below, which persists the exact wire in IndexedDB and owns delivery state and retries. An initial `connect()` wait times out after 30 seconds, but recovery continues until explicitly closed; retain the client and observe its connection state.
43
43
 
44
44
  ## Versioned Markdown and chart content
45
45
 
@@ -128,93 +128,182 @@ The handler leaves text-only pastes and events already handled by the host untou
128
128
 
129
129
  Only send on explicit submission: block send while `pending`, deliver each ready attachment's `url`, and remove it after delivery succeeds. Keep unsuccessful drafts to allow retry without reuploading. For `ChatInputDesktop`, use `hasPendingContent` and `sendDisabled` with host-owned drafts. Unmount or key the draft owner when switching accounts, Teams or conversations.
130
130
 
131
- ## Browser message and reading data
131
+ ## Browser messages
132
132
 
133
133
  ### Background
134
134
 
135
- Message hosts previously coordinated remote history, offline snapshots, optimistic
136
- sends and reading synchronization independently. The runtime now provides local
137
- IndexedDB storage and fixed-window scheduling primitives so those operations can
138
- share bounded I/O, durable confirmation and scoped subscriptions.
139
-
140
- The browser entry point is `createChat` from `@pmate/chat-sdk/runtime`:
135
+ App code should render messages and report actual user actions. IndexedDB,
136
+ trusted recent windows, synchronization, recipients, retries and read rules belong
137
+ to the SDK. The default API presents one semantic message list, including delivery,
138
+ recalls and reactions. It never presents a partial migration as a ready window.
141
139
 
142
140
  ```ts
143
- const chat = createChat({ appId, accountId, profileId, teamId, token, onError })
141
+ import {createChat} from "@pmate/chat-sdk/chat"
142
+ const chat = createChat({appId, accountId, profileId, teamId,
143
+ getToken: ({forceRefresh, signal}) => auth.getAccessToken({forceRefresh, signal}),
144
+ })
144
145
  await chat.start()
145
- const page = await chat.getMessages(threadId, { limit: 50 })
146
- // After an authoritative mutation (e.g. history assignment), await reconciliation.
147
- // Concurrent refreshes share transport; errors do not masquerade as fresh cache.
148
- const reconciled = await chat.getMessages(threadId, { refresh: true })
149
- // Explicit full-history queries reuse cached pages and internally rebase expired cursors.
150
- const history = await chat.getMessages(threadId, { all: true })
151
- const off = chat.subscribe(threadId, () => render(chat.getSnapshot(threadId)))
152
- const offSummary = chat.subscribe(() => renderSummary(chat.getSnapshot()))
146
+ const threadId = await chat.resolveThreadId({peerId}) // or {groupId}; no remote creation
147
+ const off = chat.subscribeThread(threadId, state => {
148
+ if (state.status === "ready") render(state.messages)
149
+ else if (state.status === "error") showRetry(state.error)
150
+ }) // listens first, opens local state, then owns background synchronization
151
+ await chat.sendMessage(threadId, {text: "Hello"})
152
+ await chat.loadEarlier(threadId)
153
+ off()
154
+ await chat.close()
155
+ ```
156
+
157
+ `getThread(id)` is the one-shot equivalent; it returns the same stable snapshot
158
+ as `subscribeThread`. Cache hits do not repeat history requests. `loadEarlier(id)`
159
+ extends the SDK window without limits or cursors. `refreshThread(id)` explicitly
160
+ reconciles authority; ordinary reconnect/wake synchronization is automatic.
161
+ For a cold route, keep the usable list visible until `getThread` resolves, or
162
+ until a subscription delivers `status: "ready"`; no intermediate message loader
163
+ is necessary. Subscription initialization has four bounded concurrent lanes.
164
+ Multiple consumers share work; disposing one does not stop another consumer.
165
+
166
+ Thread state is `pending`, `ready`, or `error`. A failed initial read is `error`;
167
+ a failed background refresh retains `ready` messages and includes `error`.
168
+ `ready` is retained as a derived compatibility field; use `status` in new code.
169
+ Connection state is independent. A snapshot also contains `hasEarlier`, `unread`,
170
+ `group`, `readCounts` and scoped target identity. Unknown unread stays undefined.
171
+ `subscribeChat` delivers known conversation summaries and aggregate reading state.
172
+ Subscriptions emit their current snapshot first, then meaningful changes only;
173
+ `fields` filters notifications. Callbacks cannot corrupt a shared cache read.
174
+ Opening or subscribing does not mark messages read.
175
+
176
+ Messages provide kind/text/image/voice/quote, reactions and reading/delivery state.
177
+ No raw wire or control revision is needed. The public models do not derive their
178
+ types from Session, storage or projection internals. Object references stay stable
179
+ when content has not changed. React `useChatSnapshot(chat, threadId)` manages the
180
+ external-store subscription; custom integrations use `getChatStore` from
181
+ `@pmate/chat-sdk/framework`, where synchronous memory snapshots and change-only
182
+ notifications are explicitly separated from ordinary asynchronous reads.
183
+
184
+ `getThread`, `loadEarlier`, `refreshThread`, `getGroup`, `refreshGroup`,
185
+ `resolveThreadId`, and `getVoiceAttachment` accept `{signal}`. Reader queries accept
186
+ `{afterProfileId, revision, signal}`. Aborting rejects that consumer with the signal
187
+ reason (normally AbortError); it does not abort shared work, publish a fake cache
188
+ failure, or undo a durable send. A late abandoned voice lease is released.
189
+ Complete-history search/export is an explicit, potentially large operation:
190
+ `loadThreadHistory(chat, id, {signal})` from `@pmate/chat-sdk/history`. It traverses
191
+ accessible history and rejects incomplete scans. It is not a flag on normal reads.
192
+
193
+ The credential provider is invoked on connection/reconnection and authenticated
194
+ HTTP reads; concurrent requests share acquisition. HTTP 401 gets one coalesced
195
+ forced refresh and at most one replay. HTTP 403 is never retried automatically.
196
+ Providers must preserve account/app/profile identity. A fixed `token` remains
197
+ supported as an alternative; select one source. An ambiguous WebSocket AUTH_FAILED
198
+ requires authentication rather than guessing that a permission failure expired.
199
+ Closing fences late providers and prevents further authenticated network work.
200
+
201
+ Sending accepts text/mentions/quotes, images, files, voice, recalls, reactions and
202
+ invitations through the same typed content union. The SDK resolves the target
203
+ and group roster, expands @all and assigns reaction revisions. The default
204
+ Promise resolves after durable acceptance and returns `{id,timestamp}`; outgoing
205
+ status is part of the same message list. `{waitFor:"delivered"}` waits for the
206
+ server acknowledgement. `retryMessage(id)` retries a failed exact wire.
207
+
208
+ ```ts
209
+ await chat.sendMessage(threadId, {file:{url,name,size,mimeType}}) // uploaded HTTPS metadata
210
+ await chat.sendMessage(threadId, {voice:{blob,durationMs}})
211
+ await chat.sendMessage(threadId, {reaction:{messageId,emojiId,active:true}})
212
+ await chat.sendMessage(threadId, {recall:{messageId}}, {waitFor:"delivered"})
153
213
  await chat.observeMessages(threadId, visibleIds, "visible")
154
- // Report only the actual audio `playing` event.
155
- await chat.observeMessages(threadId, [voiceId], "played")
156
- await chat.sendMessage(threadId, { to, body, recipients })
214
+ await chat.observeMessages(threadId, [voiceId], "played") // actual playing event
215
+ await chat.markRead(threadId) // ordinary text only; unheard voice stays unread
157
216
  const readers = await chat.getReaders(threadId, messageId)
158
- off(); offSummary()
159
- await chat.close()
160
217
  ```
161
218
 
162
- The facade owns all clients below. Message reads transparently restore IndexedDB
163
- windows and fetch missing ranges through the authenticated Hub. Its normalized
164
- messages retain the protocol body; product rendering remains with the host.
165
- `sendMessage` resolves after local persistence, while `outgoing` in the thread
166
- snapshot reports transport progress and failure. Retry uses the original message
167
- hash. The subscribed recent window remains 100 records regardless of individual
168
- query limits; history requests return their own pages and do not replace it.
169
- Global snapshots contain thread summaries and totals, not complete message arrays.
170
- Message pages report `complete`; unavailable older pages return cached records with
171
- `complete: false`. Authorization failures throw and never fall back to denied
172
- records. Thread snapshots expose `resetVersion`; hosts discard retained rows when
173
- it changes. Reset epochs are durable, so late cross-tab writes cannot restore a
174
- pre-reset payload.
175
- Voice visibility never acknowledges playback, including observations that arrive
176
- before metadata. Create a new instance and close the previous owner when the
177
- account or active Team changes.
178
-
179
- This facade is still under integration. Reader details currently require network
180
- access, global summaries do not yet replace the host's contact/thread metadata,
181
- and Merry's message-list/send migration and production validation are unfinished.
182
- The lower-level primitives remain available for custom transports:
183
-
184
- `MessageClient` reads a trusted recent window locally, coalesces concurrent misses,
185
- subscribes per conversation, and accepts incremental event batches. Its transport
186
- supplies authoritative `load`, `changes` and `older` pages. `nextBefore` tracks
187
- uncached older history independently of the latest synchronization checkpoint.
188
- A projection reset removes obsolete rows before installing a new window.
189
- `SocialReadClient.messagePage` implements the authenticated Hub page protocol;
190
- request cancellation is supported through its optional second signal argument.
191
-
192
- `ReadingClient` owns persisted visible/explicit read intents, windowed network
193
- synchronization and retry. A host reports actual visibility with `recordVisible`
194
- or explicit actions with `recordExplicit`; loading or subscribing never marks
195
- messages read. `getSnapshot` plus `subscribe` provide stable reading snapshots.
196
- `BatchedReadIndex` resolves queued writes only after their transaction commits.
197
-
198
- `IndexedDbRecordCache` is a version-aware control-event index. Call and await
199
- `open` before using it. An optional legacy reader runs during initialization only;
200
- legacy cleanup happens after durable migration. Normal reads use memory and writes
201
- are merged into bounded IndexedDB transactions. Cache errors must be surfaced by
202
- the host, and pending operations are not reported as saved before their promise
203
- resolves.
204
- Control-event batches are bounded by both 128 records and 1 MiB per transaction;
205
- the queue includes in-flight writes and is limited to 4096 records / 8 MiB.
206
- `transactionBytes` and `queueBytes` can lower these limits. Oversized operations
207
- fail before changing the memory snapshot. Repeating an in-flight write waits for
208
- that physical write, including its failure, rather than acknowledging it early.
209
-
210
- `DurableChatSender` saves the exact message created by `ChatClient.prepareChat`
211
- before transmitting it. `retry(hash)` uses `sendPrepared` with the original hash
212
- and payload. Failed sends require an explicit retry; interrupted sends can resume
213
- when the connection returns. Call `confirm(hashes)` only after authoritative
214
- messages have been durably merged. Confirmation tombstones prevent another tab's
215
- late transport failure from recreating an outgoing message.
216
-
217
- Custom primitive integrations must initialize them with the same authenticated
218
- scope, dispose them on identity changes, and wire their transport to an authorized
219
- server. Standard browser integrations should use the facade instead. The Merry migration and production validation
220
- are tracked in the unified message layer proposal.
219
+ Voice media is acquired on demand through `getVoiceAttachment(threadId,messageId)`.
220
+ Use its `url` and call `release()` when playback/transcription stops. Acquisition,
221
+ clicking play, a fulfilled play Promise and transcription do not count as playing.
222
+ The SDK persists observations locally, batches writes and synchronization, and
223
+ preserves offline intents. It does not promise all media binaries are offline.
224
+
225
+ ```ts
226
+ const group = await chat.getGroup(threadId)
227
+ const created = await chat.createGroup({title, members}) // optional groupId
228
+ await chat.updateGroup(created.threadId, {title: "New title"})
229
+ ```
230
+
231
+ Group reads restore IndexedDB metadata before background reconciliation. Mutations
232
+ wait for confirmed delivery and a causal server version. SDK-owned requests share
233
+ bounded queues and time windows; Apps do not poll. `watchReaders(threadId,ids,receiver)`
234
+ selects receipt counts for the current viewport; clear with `watchReaders(undefined,[])`.
235
+
236
+ `ChatError` exposes code, retryable, optional HTTP status and cause. Categories are
237
+ unavailable, authentication-required, access-denied, storage-unavailable,
238
+ invalid-input, conflict, closed and busy. Token expiry requires authentication;
239
+ a 403 withdraws the scoped session and its caches. Account/profile/team changes
240
+ require a new SDK instance. Token renewal alone uses the provider.
241
+
242
+ ### Advanced integration and migration
243
+
244
+ The default client has no `advanced`, `getSnapshot`, `getMessages`, `getConversation`
245
+ or generic `subscribe`. Use resolveThreadId, getThread, subscribeThread,
246
+ loadEarlier and refreshThread. Public readers no longer accept a separate fourth
247
+ signal argument. Use `{signal}` together with the query instead.
248
+
249
+ Host-owned calls/presence transports use `createChatWithTransport` from
250
+ `@pmate/chat-sdk/advanced`; it returns the same public client. `getChatRuntime(chat)`
251
+ provides explicit one-time migration/diagnostic access. `handleChatReach` and
252
+ `revokeChatAccess` are advanced integration helpers. A deprecated
253
+ `getLegacyChat(chat)` adapter is available only from this entry during migration.
254
+ The raw factory is named `createChatSession` in `/runtime`; its old `createChat`
255
+ name is deprecated. Raw storage/transport clients remain advanced capabilities.
256
+ They must share identity and respect scope authority. Apps do not maintain a second
257
+ message cache, outbox, recipient roster or control revision generator.
258
+
259
+ ### Reading storage recovery
260
+
261
+ Large reading snapshots use a persisted journal. The full plan becomes durable
262
+ before canonical rows change; bounded transactions apply it, and the sync cursor
263
+ commits last. A reader recovers an active plan before returning a snapshot. Failed
264
+ staging leaves the previous snapshot authoritative; failure after activation keeps
265
+ the old cursor until recovery completes. Pending reads retain their operation IDs.
266
+
267
+ Writes use at most 128 records per transaction, including journal/lease records,
268
+ and conservatively budget one MiB per batch. Message order is stored per row so a
269
+ source no longer needs a growing array of every message ID. Ordinary observations
270
+ continue to update only positions and pending operations. Locks serialize each
271
+ owner across documents; a fenced IndexedDB lease supports contexts without Web Locks.
272
+
273
+ The private reading database format is now 60. Existing stores are renamed by
274
+ IndexedDB metadata operations, preserving keys and records. Older SDKs cannot
275
+ write through the former namespace and need a page reload to use the new format.
276
+ The message-body cache is separate and unchanged. The public reading state remains
277
+ schema version 1; applications do not select storage formats or recovery steps.
278
+
279
+
280
+ Pending voice snapshots contain `voiceAttachment` metadata, without loading audio bytes or creating Blob URLs at startup. For playback or transcription, acquire media through `await chat.getVoiceAttachment(threadId, messageId)`, use the returned `url`, and call `release()` when the consumer stops. The same method returns a remote voice reference for delivered messages. Attachment reads are charged before allocation, and close/revocation releases local URLs. A pending voice remains unread until the player actually starts; acquiring media does not mark it read.
281
+
282
+ The outbox format upgrade renames the old canonical store without copying binary data in the schema transaction, then atomically separates inline payloads in bounded, resumable transactions. An already-open older application must reload; unsupported senders reject the new format. Original wire/hash, failed status and retry semantics are retained.
283
+
284
+
285
+ SDK group metadata uses a 200ms merge window, four concurrent requests, a bounded
286
+ 128-request queue, five-second timeout and at most five read-after-write attempts.
287
+ The persistent group cache is capped at 64 groups / 1MiB, with 128KiB records,
288
+ 32-record write batches, conditional writes and bounded pruning. Conversation
289
+ identities are stored separately without rosters; repeated registrations share
290
+ one write, concurrent lookups use bulk reads, and a concurrent memory-hit burst
291
+ shares one fresh authority read without retaining authorization across bursts. Scope withdrawal retires both.
292
+
293
+
294
+ ## Slash command suggestions
295
+
296
+ ### Background
297
+
298
+ Host applications know which commands are available in a conversation. The shared desktop/mobile inputs now display that catalog while typing a leading `/`, without coupling the SDK to a particular bot or executing commands from the picker.
299
+
300
+ ```tsx
301
+ <ChatInputDesktop commands={{ commands: [
302
+ { name: "review", description: "Review code", usage: "PR URL or branch" },
303
+ { name: "qa", description: "Run independent tests", usage: "PR URL" },
304
+ ] }} mentions={groupMembers} onSend={sendMessage} />
305
+ ```
306
+
307
+ The same prop is supported by `ChatInputMobile`. Names omit the leading slash. Arrow keys navigate, Enter/Tab inserts a command and a space, Escape closes; touch selection edits the draft only. Command arguments and existing member mentions are preserved. IME composition, URLs and slashes in ordinary body text do not trigger command execution. Omit `commands` for unsupported conversations.
308
+
309
+ Host-owned textareas can use `useComposerSuggestions({ value, onChange, mentions, commands, disabled })`, spread `inputProps`, render `MentionMenu` with `menuProps` and `CommandMenu` with `commandMenuProps`, and send `getMentions()` with the untrimmed value.
@@ -0,0 +1,10 @@
1
+ import type { Chat } from "./chatContract.js";
2
+ import type { CreateChatOptions } from "./runtime/createChat.js";
3
+ /** Host-owned transport integration. The default factory never accepts raw transports. */
4
+ export declare function createChatWithTransport(options: CreateChatOptions): Chat;
5
+ /** Explicit migration/diagnostic access, outside the ordinary client. */
6
+ export declare function getChatRuntime(chat: Chat): import("./runtime/chatSession.js").ChatSession<import("./runtime/cachedMessage.js").CachedChatMessage>;
7
+ export declare function handleChatReach(chat: Chat, event: Parameters<ReturnType<typeof getChatRuntime>["handleReach"]>[0]): Promise<"handled" | undefined>;
8
+ export declare function revokeChatAccess(chat: Chat): Promise<void>;
9
+ /** @deprecated Transitional next.68 adapter. New App code must use the public client. */
10
+ export declare function getLegacyChat(chat: Chat): import("./internal/chatEngine.js").ChatEngine;
@@ -0,0 +1,10 @@
1
+ import { createPublicChat } from "./internal/createPublicChat.js";
2
+ import { chatEngine } from "./internal/chatRegistry.js";
3
+ /** Host-owned transport integration. The default factory never accepts raw transports. */
4
+ export function createChatWithTransport(options) { return createPublicChat(options); }
5
+ /** Explicit migration/diagnostic access, outside the ordinary client. */
6
+ export function getChatRuntime(chat) { return chatEngine(chat).advanced; }
7
+ export function handleChatReach(chat, event) { return chatEngine(chat).handleReach(event); }
8
+ export function revokeChatAccess(chat) { return chatEngine(chat).revokeAccess(); }
9
+ /** @deprecated Transitional next.68 adapter. New App code must use the public client. */
10
+ export function getLegacyChat(chat) { return chatEngine(chat); }
package/lib/chat.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { Chat, CreateChatOptions } from "./chatContract.js";
2
+ export type * from "./chatContract.js";
3
+ export type * from "./chatTypes.js";
4
+ export { ChatError, type ChatErrorCode } from "./chatError.js";
5
+ export declare function createChat(options: CreateChatOptions): Chat;
package/lib/chat.js ADDED
@@ -0,0 +1,11 @@
1
+ import { createPublicChat } from "./internal/createPublicChat.js";
2
+ import { ChatError } from "./chatError.js";
3
+ export { ChatError } from "./chatError.js";
4
+ export function createChat(options) {
5
+ const allowed = ["appId", "accountId", "profileId", "teamId", "threadId", "endpoint", "uploadEndpoint", "clientInstanceId", "fetch", "onError", "token", "getToken"];
6
+ if (Object.keys(options).some(key => !allowed.includes(key)))
7
+ throw new ChatError("invalid-input", "Unsupported chat option; transport integration belongs to /advanced");
8
+ if (options.token !== undefined && options.getToken !== undefined)
9
+ throw new ChatError("invalid-input", "Select one credential source");
10
+ return createPublicChat({ ...options, onError: error => options.onError?.(error) });
11
+ }
@@ -0,0 +1,148 @@
1
+ import type { ChatFile } from "./fileMetadata.js";
2
+ import type { ChatError } from "./chatError.js";
3
+ import type { ChatMention } from "./mentions/model.js";
4
+ import type { TokenProvider } from "./credentials.js";
5
+ import type { ChatGroup, ChatSnapshot, ChatThreadSnapshot, ReadOptions } from "./chatTypes.js";
6
+ export type { TokenProvider, TokenRequest } from "./credentials.js";
7
+ export type ConversationTarget = {
8
+ peerId: string;
9
+ groupId?: never;
10
+ } | {
11
+ groupId: string;
12
+ peerId?: never;
13
+ };
14
+ export type CreateChatOptions = {
15
+ appId: string;
16
+ accountId: string;
17
+ profileId: string;
18
+ teamId: string;
19
+ threadId?: string;
20
+ endpoint?: string;
21
+ uploadEndpoint?: string;
22
+ clientInstanceId?: string;
23
+ fetch?: typeof fetch;
24
+ onError?: (error: ChatError) => void;
25
+ } & ({
26
+ token: string;
27
+ getToken?: never;
28
+ } | {
29
+ getToken: TokenProvider;
30
+ token?: never;
31
+ });
32
+ export type SendOptions = {
33
+ waitFor?: "accepted" | "delivered";
34
+ };
35
+ export type SentMessage = Readonly<{
36
+ id: string;
37
+ timestamp: number;
38
+ }>;
39
+ export type ReadersQuery = {
40
+ afterProfileId?: string;
41
+ revision?: number;
42
+ };
43
+ export type ReadersPage = Readonly<{
44
+ appId: string;
45
+ threadId: string;
46
+ messageId: string;
47
+ revision: number;
48
+ total: number;
49
+ profileIds: string[];
50
+ nextAfter: string | null;
51
+ resetRequired: boolean;
52
+ }>;
53
+ export type GroupChanges = {
54
+ expectedRevision?: string;
55
+ addMembers?: readonly string[];
56
+ removeMembers?: readonly string[];
57
+ title?: string;
58
+ avatar?: string;
59
+ };
60
+ export type VoiceAttachment = Readonly<{
61
+ url: string;
62
+ release: () => void;
63
+ }>;
64
+ export type SubscribeOptions<T> = {
65
+ fields?: readonly (keyof T)[];
66
+ };
67
+ type ContentChoices = {
68
+ text: {
69
+ text: string;
70
+ mentions?: ChatMention[];
71
+ quote?: {
72
+ messageId?: string;
73
+ senderName: string;
74
+ content: string;
75
+ };
76
+ };
77
+ file: {
78
+ file: ChatFile;
79
+ };
80
+ image: {
81
+ image: {
82
+ url: string;
83
+ name?: string;
84
+ };
85
+ };
86
+ voice: {
87
+ voice: ({
88
+ blob: Blob;
89
+ url?: never;
90
+ } | {
91
+ url: string;
92
+ blob?: never;
93
+ }) & {
94
+ durationMs: number;
95
+ transcript?: string;
96
+ };
97
+ };
98
+ recall: {
99
+ recall: {
100
+ messageId: string;
101
+ };
102
+ };
103
+ reaction: {
104
+ reaction: {
105
+ messageId: string;
106
+ emojiId: string;
107
+ active: boolean;
108
+ };
109
+ };
110
+ invitation: {
111
+ invitation: {
112
+ message?: string;
113
+ };
114
+ };
115
+ };
116
+ export type OutgoingContent = {
117
+ [K in keyof ContentChoices]: ContentChoices[K] & {
118
+ [P in Exclude<keyof ContentChoices, K>]?: never;
119
+ };
120
+ }[keyof ContentChoices];
121
+ /** Default public client. Protocol and synchronous store internals are separate entry points. */
122
+ export interface Chat {
123
+ start(): Promise<void>;
124
+ close(): Promise<void>;
125
+ resolveThreadId(target: ConversationTarget, options?: ReadOptions): Promise<string>;
126
+ getThread(threadId: string, options?: ReadOptions): Promise<ChatThreadSnapshot>;
127
+ loadEarlier(threadId: string, options?: ReadOptions): Promise<ChatThreadSnapshot>;
128
+ refreshThread(threadId: string, options?: ReadOptions): Promise<ChatThreadSnapshot>;
129
+ subscribeThread(threadId: string, listener: (snapshot: ChatThreadSnapshot) => void, options?: SubscribeOptions<ChatThreadSnapshot>): () => void;
130
+ subscribeChat(listener: (snapshot: ChatSnapshot) => void, options?: SubscribeOptions<ChatSnapshot>): () => void;
131
+ sendMessage(threadId: string, content: OutgoingContent, options?: SendOptions): Promise<SentMessage>;
132
+ retryMessage(messageId: string): Promise<SentMessage>;
133
+ observeMessages(threadId: string, ids: string[], event: "visible" | "played"): Promise<void>;
134
+ markRead(threadId: string): Promise<void>;
135
+ getReaders(threadId: string, messageId: string, options?: ReadersQuery & ReadOptions): Promise<ReadersPage>;
136
+ watchReaders(threadId: string | undefined, ids: string[], receiver?: string): Promise<void>;
137
+ getVoiceAttachment(threadId: string, messageId: string, options?: ReadOptions): Promise<VoiceAttachment>;
138
+ getGroup(threadId: string, options?: ReadOptions): Promise<ChatGroup>;
139
+ refreshGroup(threadId: string, options?: ReadOptions): Promise<ChatGroup>;
140
+ createGroup(input: {
141
+ groupId?: string;
142
+ title: string;
143
+ members: readonly string[];
144
+ }): Promise<ChatGroup & {
145
+ threadId: string;
146
+ }>;
147
+ updateGroup(threadId: string, changes: GroupChanges): Promise<ChatGroup>;
148
+ }
@@ -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,121 @@
1
+ import type { ChatFile } from "./fileMetadata.js";
2
+ export type { ChatFile } from "./fileMetadata.js";
3
+ import type { ChatError } from "./chatError.js";
4
+ import type { ChatMention } from "./mentions/model.js";
5
+ /** Stable product model. No storage, transport, Session or projection types. */
6
+ export type ChatMessage = Readonly<{
7
+ id: string;
8
+ threadId: string;
9
+ timestamp: number;
10
+ from?: string;
11
+ mine: boolean;
12
+ status: "sending" | "sent" | "failed" | "read";
13
+ recalled: boolean;
14
+ unread?: boolean;
15
+ attentionEligible?: boolean;
16
+ reading?: Readonly<{
17
+ seq?: number;
18
+ read: boolean;
19
+ readPolicy: "visible" | "explicit";
20
+ }>;
21
+ reactions: readonly Readonly<{
22
+ emojiId: string;
23
+ userIds: string[];
24
+ mine: boolean;
25
+ }>[];
26
+ kind: "text" | "image" | "voice" | "emoji" | "quote" | "invitation" | "system" | "attachment";
27
+ text: string;
28
+ senderName?: string;
29
+ senderAvatar?: string;
30
+ timeLabel?: string;
31
+ file?: ChatFile;
32
+ image?: Readonly<{
33
+ url: string;
34
+ name?: string;
35
+ }>;
36
+ voice?: Readonly<{
37
+ url: string;
38
+ durationMs: number;
39
+ transcript?: string;
40
+ }>;
41
+ quote?: Readonly<{
42
+ messageId?: string;
43
+ senderName: string;
44
+ content: string;
45
+ recalled?: boolean;
46
+ }>;
47
+ attachment?: Readonly<{
48
+ name?: string;
49
+ size?: string;
50
+ }>;
51
+ richContent?: unknown;
52
+ mentions?: readonly ChatMention[];
53
+ recipientCount?: number;
54
+ system?: Readonly<{
55
+ type: "group-created" | "group-updated" | "contact-updated";
56
+ title?: string;
57
+ }>;
58
+ }>;
59
+ export type ChatGroup = Readonly<{
60
+ groupId: string;
61
+ teamId: string;
62
+ ownerId: string;
63
+ title: string;
64
+ avatar?: string;
65
+ memberIds: readonly string[];
66
+ revision: string;
67
+ }>;
68
+ export type ConversationInfo = Readonly<{
69
+ kind: "dm" | "group";
70
+ targetId: string;
71
+ }>;
72
+ export type UnreadState = Readonly<{
73
+ unreadMessageCount: number;
74
+ unreadMentionCount: number;
75
+ badgeCount: number;
76
+ }>;
77
+ export type ReadCounts = Readonly<{
78
+ appId: string;
79
+ threadId: string;
80
+ revision: number;
81
+ counts: Record<string, number>;
82
+ }>;
83
+ type ThreadFields = Readonly<{
84
+ threadId: string;
85
+ messages: readonly ChatMessage[];
86
+ hasEarlier: boolean;
87
+ conversation?: ConversationInfo;
88
+ unread: UnreadState | undefined;
89
+ group: ChatGroup | undefined;
90
+ readCounts: ReadCounts | undefined;
91
+ }>;
92
+ /** A refresh failure retains ready data; only an initial failure has status error. */
93
+ export type ChatThreadSnapshot = ThreadFields & (Readonly<{
94
+ status: "pending";
95
+ ready: false;
96
+ error?: undefined;
97
+ }> | Readonly<{
98
+ status: "ready";
99
+ ready: true;
100
+ error?: ChatError;
101
+ }> | Readonly<{
102
+ status: "error";
103
+ ready: false;
104
+ error: ChatError;
105
+ }>);
106
+ export type ChatConnectionState = "connecting" | "reconnecting" | "authenticating" | "connected" | "authentication_required" | "closed";
107
+ export type ChatSnapshot = Readonly<{
108
+ threads: readonly {
109
+ threadId: string;
110
+ lastMessage?: ChatMessage;
111
+ group: ChatGroup | undefined;
112
+ unread: UnreadState | undefined;
113
+ outgoingCount: number;
114
+ }[];
115
+ unreadMessageCount: number | undefined;
116
+ badgeCount: number | undefined;
117
+ connection: ChatConnectionState;
118
+ }>;
119
+ export type ReadOptions = {
120
+ signal?: AbortSignal;
121
+ };
@@ -0,0 +1 @@
1
+ export {};