@pmate/chat-sdk 0.12.0-next.3 → 0.12.0-next.31
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.
- package/README.md +41 -1
- package/lib/runtime/batchedReadIndex.d.ts +11 -2
- package/lib/runtime/batchedReadIndex.js +21 -1
- package/lib/runtime/chatClient.d.ts +1 -0
- package/lib/runtime/chatClient.js +1 -1
- package/lib/runtime/chatSession.d.ts +78 -8
- package/lib/runtime/chatSession.js +488 -39
- package/lib/runtime/connectivity.d.ts +4 -0
- package/lib/runtime/connectivity.js +8 -0
- package/lib/runtime/conversationRead.d.ts +13 -0
- package/lib/runtime/conversationRead.js +34 -0
- package/lib/runtime/conversationReading.d.ts +28 -0
- package/lib/runtime/conversationReading.js +43 -0
- package/lib/runtime/createChat.d.ts +2 -0
- package/lib/runtime/createChat.js +18 -5
- package/lib/runtime/durableSender.d.ts +28 -4
- package/lib/runtime/durableSender.js +159 -35
- package/lib/runtime/index.d.ts +3 -0
- package/lib/runtime/index.js +3 -0
- package/lib/runtime/messageClient.d.ts +31 -6
- package/lib/runtime/messageClient.js +405 -70
- package/lib/runtime/messageReaders.d.ts +12 -1
- package/lib/runtime/messageReaders.js +67 -3
- package/lib/runtime/messageStore.d.ts +37 -2
- package/lib/runtime/messageStore.js +150 -10
- package/lib/runtime/messageTimeline.d.ts +47 -0
- package/lib/runtime/messageTimeline.js +113 -0
- package/lib/runtime/readCounts.d.ts +15 -1
- package/lib/runtime/readCounts.js +96 -8
- package/lib/runtime/readIndex.d.ts +32 -3
- package/lib/runtime/readIndex.js +203 -8
- package/lib/runtime/readSync.js +12 -4
- package/lib/runtime/readingClient.d.ts +18 -0
- package/lib/runtime/readingClient.js +120 -18
- package/lib/runtime/readingContent.d.ts +2 -0
- package/lib/runtime/readingContent.js +9 -0
- package/lib/runtime/recordCache.d.ts +9 -0
- package/lib/runtime/recordCache.js +62 -5
- package/lib/runtime/responseCache.d.ts +44 -0
- package/lib/runtime/responseCache.js +173 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -142,7 +142,14 @@ The browser entry point is `createChat` from `@pmate/chat-sdk/runtime`:
|
|
|
142
142
|
```ts
|
|
143
143
|
const chat = createChat({ appId, accountId, profileId, teamId, token, onError })
|
|
144
144
|
await chat.start()
|
|
145
|
-
const page = await chat.getMessages(threadId
|
|
145
|
+
const page = await chat.getMessages(threadId)
|
|
146
|
+
// SDK owns the page size and history boundary; hosts need not construct a cursor.
|
|
147
|
+
const earlier = await chat.getMessages(threadId, { earlier: true })
|
|
148
|
+
// After an authoritative mutation (e.g. history assignment), await reconciliation.
|
|
149
|
+
// Concurrent refreshes share transport; errors do not masquerade as fresh cache.
|
|
150
|
+
const reconciled = await chat.getMessages(threadId, { refresh: true })
|
|
151
|
+
// Explicit full-history queries reuse cached pages and internally rebase expired cursors.
|
|
152
|
+
const history = await chat.getMessages(threadId, { all: true })
|
|
146
153
|
const off = chat.subscribe(threadId, () => render(chat.getSnapshot(threadId)))
|
|
147
154
|
const offSummary = chat.subscribe(() => renderSummary(chat.getSnapshot()))
|
|
148
155
|
await chat.observeMessages(threadId, visibleIds, "visible")
|
|
@@ -151,9 +158,17 @@ await chat.observeMessages(threadId, [voiceId], "played")
|
|
|
151
158
|
await chat.sendMessage(threadId, { to, body, recipients })
|
|
152
159
|
const readers = await chat.getReaders(threadId, messageId)
|
|
153
160
|
off(); offSummary()
|
|
161
|
+
// Ordinary navigation/shutdown preserves offline data.
|
|
154
162
|
await chat.close()
|
|
155
163
|
```
|
|
156
164
|
|
|
165
|
+
```ts
|
|
166
|
+
// When team access is withdrawn, use this instead of ordinary close(). It hides
|
|
167
|
+
// snapshots immediately, retires durable caches/outbox and closes this session.
|
|
168
|
+
// Other teams' reading operations remain intact. Create a new session to recover.
|
|
169
|
+
await chat.revokeAccess()
|
|
170
|
+
```
|
|
171
|
+
|
|
157
172
|
The facade owns all clients below. Message reads transparently restore IndexedDB
|
|
158
173
|
windows and fetch missing ranges through the authenticated Hub. Its normalized
|
|
159
174
|
messages retain the protocol body; product rendering remains with the host.
|
|
@@ -162,6 +177,11 @@ snapshot reports transport progress and failure. Retry uses the original message
|
|
|
162
177
|
hash. The subscribed recent window remains 100 records regardless of individual
|
|
163
178
|
query limits; history requests return their own pages and do not replace it.
|
|
164
179
|
Global snapshots contain thread summaries and totals, not complete message arrays.
|
|
180
|
+
Message pages report `complete`; unavailable older pages return cached records with
|
|
181
|
+
`complete: false`. Authorization failures throw and never fall back to denied
|
|
182
|
+
records. Thread snapshots expose `resetVersion`; hosts discard retained rows when
|
|
183
|
+
it changes. Reset epochs are durable, so late cross-tab writes cannot restore a
|
|
184
|
+
pre-reset payload.
|
|
165
185
|
Voice visibility never acknowledges playback, including observations that arrive
|
|
166
186
|
before metadata. Create a new instance and close the previous owner when the
|
|
167
187
|
account or active Team changes.
|
|
@@ -191,6 +211,11 @@ legacy cleanup happens after durable migration. Normal reads use memory and writ
|
|
|
191
211
|
are merged into bounded IndexedDB transactions. Cache errors must be surfaced by
|
|
192
212
|
the host, and pending operations are not reported as saved before their promise
|
|
193
213
|
resolves.
|
|
214
|
+
Control-event batches are bounded by both 128 records and 1 MiB per transaction;
|
|
215
|
+
the queue includes in-flight writes and is limited to 4096 records / 8 MiB.
|
|
216
|
+
`transactionBytes` and `queueBytes` can lower these limits. Oversized operations
|
|
217
|
+
fail before changing the memory snapshot. Repeating an in-flight write waits for
|
|
218
|
+
that physical write, including its failure, rather than acknowledging it early.
|
|
194
219
|
|
|
195
220
|
`DurableChatSender` saves the exact message created by `ChatClient.prepareChat`
|
|
196
221
|
before transmitting it. `retry(hash)` uses `sendPrepared` with the original hash
|
|
@@ -203,3 +228,18 @@ Custom primitive integrations must initialize them with the same authenticated
|
|
|
203
228
|
scope, dispose them on identity changes, and wire their transport to an authorized
|
|
204
229
|
server. Standard browser integrations should use the facade instead. The Merry migration and production validation
|
|
205
230
|
are tracked in the unified message layer proposal.
|
|
231
|
+
|
|
232
|
+
When the browser reports offline, scheduled message restoration and reading
|
|
233
|
+
refreshes wait for the normal reconnect/wake path. Cached reads remain available;
|
|
234
|
+
unknown receipt counts stay unknown and an uncached reader detail page rejects
|
|
235
|
+
without attempting transport. A network failure arriving after the browser went
|
|
236
|
+
offline does not turn background synchronization into an operation error.
|
|
237
|
+
Authorization failures still revoke the corresponding cached data.
|
|
238
|
+
|
|
239
|
+
Reading entry points check the message scope even before `initialize()` finishes.
|
|
240
|
+
Concurrent observations share the permission lookup and batch their local writes.
|
|
241
|
+
Visibility/playback operations reject another known team's conversation; a
|
|
242
|
+
permission change in another team does not discard this team's observations.
|
|
243
|
+
Ordinary shutdown waits for accepted local reading writes, while access withdrawal
|
|
244
|
+
rejects late results. Retired asynchronous reading entries reject through promises;
|
|
245
|
+
refresh/subscription teardown remains safe.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type ReadIndexPersistence, type ReadIndexState } from "./readIndex.js";
|
|
1
|
+
import { type ReadIndexPersistence, type ReadIndexState, type ReadIndexTransform } from "./readIndex.js";
|
|
2
2
|
type Change = {
|
|
3
|
-
transform:
|
|
3
|
+
transform: ReadIndexTransform;
|
|
4
4
|
resolve: (state: ReadIndexState) => void;
|
|
5
5
|
reject: (error: unknown) => void;
|
|
6
6
|
};
|
|
@@ -15,6 +15,15 @@ export declare class BatchedReadIndex implements ReadIndexPersistence {
|
|
|
15
15
|
private closed;
|
|
16
16
|
constructor(storage: ReadIndexPersistence, windowMs?: number, batchSize?: number, queueLimit?: number);
|
|
17
17
|
load(appId: string, profileId: string): Promise<ReadIndexState | undefined>;
|
|
18
|
+
loadVersioned(appId: string, profileId: string): Promise<{
|
|
19
|
+
state: ReadIndexState | undefined;
|
|
20
|
+
versions: import("./readIndex.js").ReadIndexVersions;
|
|
21
|
+
} | {
|
|
22
|
+
state: ReadIndexState | undefined;
|
|
23
|
+
versions: {};
|
|
24
|
+
}>;
|
|
25
|
+
loadVersions(appId: string, profileId: string): Promise<{}>;
|
|
26
|
+
invalidateTeam(appId: string, profileId: string, teamId: string, version: number, knownThreadIds?: readonly string[]): Promise<ReadIndexState>;
|
|
18
27
|
update(appId: string, profileId: string, transform: Change["transform"]): Promise<ReadIndexState>;
|
|
19
28
|
private drain;
|
|
20
29
|
flush(appId: string, profileId: string): Promise<void>;
|
|
@@ -22,6 +22,26 @@ export class BatchedReadIndex {
|
|
|
22
22
|
throw new Error("READ_STORAGE_CLOSED");
|
|
23
23
|
return this.storage.load(appId, profileId);
|
|
24
24
|
}
|
|
25
|
+
async loadVersioned(appId, profileId) {
|
|
26
|
+
await this.flush(appId, profileId);
|
|
27
|
+
if (this.closed)
|
|
28
|
+
throw new Error("READ_STORAGE_CLOSED");
|
|
29
|
+
return this.storage.loadVersioned ? this.storage.loadVersioned(appId, profileId) : { state: await this.storage.load(appId, profileId), versions: {} };
|
|
30
|
+
}
|
|
31
|
+
async loadVersions(appId, profileId) {
|
|
32
|
+
if (this.closed)
|
|
33
|
+
throw new Error("READ_STORAGE_CLOSED");
|
|
34
|
+
return this.storage.loadVersions ? this.storage.loadVersions(appId, profileId) : {};
|
|
35
|
+
}
|
|
36
|
+
async invalidateTeam(appId, profileId, teamId, version, knownThreadIds) {
|
|
37
|
+
if (this.closed)
|
|
38
|
+
throw new Error("READ_STORAGE_CLOSED");
|
|
39
|
+
if (!this.storage.invalidateTeam)
|
|
40
|
+
throw new Error("READ_SCOPE_INVALIDATION_UNAVAILABLE");
|
|
41
|
+
// Do not flush a blocked old HTTP transform first. The durable epoch is the
|
|
42
|
+
// barrier; any queued transform sees it inside its eventual transaction.
|
|
43
|
+
return this.storage.invalidateTeam(appId, profileId, teamId, version, knownThreadIds);
|
|
44
|
+
}
|
|
25
45
|
update(appId, profileId, transform) {
|
|
26
46
|
if (this.closed)
|
|
27
47
|
return Promise.reject(new Error("READ_STORAGE_CLOSED"));
|
|
@@ -48,7 +68,7 @@ export class BatchedReadIndex {
|
|
|
48
68
|
return;
|
|
49
69
|
const run = (async () => {
|
|
50
70
|
try {
|
|
51
|
-
const next = await this.storage.update(queue.appId, queue.profileId, state => batch.reduce((current, change) => change.transform(current), state));
|
|
71
|
+
const next = await this.storage.update(queue.appId, queue.profileId, (state, versions) => batch.reduce((current, change) => change.transform(current, versions), state));
|
|
52
72
|
batch.forEach(change => change.resolve(next));
|
|
53
73
|
}
|
|
54
74
|
catch (error) {
|
|
@@ -62,6 +62,7 @@ export declare class ChatClient {
|
|
|
62
62
|
profileId: string | undefined;
|
|
63
63
|
accountId: string | undefined;
|
|
64
64
|
teamId: string | undefined;
|
|
65
|
+
clientInstanceId: string;
|
|
65
66
|
};
|
|
66
67
|
watchConnection(handler: (state: ChatConnectionState) => void): () => void;
|
|
67
68
|
private setConnection;
|
|
@@ -123,7 +123,7 @@ export class ChatClient {
|
|
|
123
123
|
/** Non-secret identity used to validate a shared browser transport. */
|
|
124
124
|
identityScope() {
|
|
125
125
|
const identity = this.session?.identity ?? this.session?.session?.identity;
|
|
126
|
-
return { appId: this.options.appId, profileId: this.profileIdValue ?? this.options.profileId, accountId: identity?.accountId, teamId: this.options.teamId };
|
|
126
|
+
return { appId: this.options.appId, profileId: this.profileIdValue ?? this.options.profileId, accountId: identity?.accountId, teamId: this.options.teamId, clientInstanceId: this.instanceId };
|
|
127
127
|
}
|
|
128
128
|
watchConnection(handler) {
|
|
129
129
|
this.connectionHandlers.add(handler);
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { type TimelineItem } from "./messageTimeline.js";
|
|
2
|
+
import { type ReadingCandidate, type MessageReadingState } from "./conversationReading.js";
|
|
3
|
+
import type { ConversationReadMessage } from "./conversationRead.js";
|
|
4
|
+
import { type ReadCountsResult } from "./readCounts.js";
|
|
5
|
+
import type { OnlineReachEvent } from "./reach.js";
|
|
1
6
|
import { type MessageReadersQuery } from "./messageReaders.js";
|
|
2
|
-
import { type MessageClientOptions } from "./messageClient.js";
|
|
7
|
+
import { type MessageClientOptions, type MessageSnapshot } from "./messageClient.js";
|
|
3
8
|
import { DurableChatSender } from "./durableSender.js";
|
|
4
|
-
import { type ReadingClientOptions } from "./readingClient.js";
|
|
9
|
+
import { ReadingClient, type ReadingClientOptions } from "./readingClient.js";
|
|
5
10
|
import { ChatClient, type ChatClientOptions, type ChatConnectionState } from "./chatClient.js";
|
|
6
11
|
import { unreadInConversation } from "./readIndex.js";
|
|
7
12
|
import type { StoredMessage } from "./messageStore.js";
|
|
@@ -10,25 +15,33 @@ export type ChatSessionOptions<T extends StoredMessage> = {
|
|
|
10
15
|
messages: MessageClientOptions<T>;
|
|
11
16
|
connection: ChatClientOptions;
|
|
12
17
|
sharedConnection?: ChatClient;
|
|
18
|
+
activeThreadId?: string;
|
|
13
19
|
reading: ReadingClientOptions;
|
|
14
20
|
decode: (message: ChatRuntimeMessage) => T | undefined;
|
|
21
|
+
readingCandidate?: (message: T) => ReadingCandidate | undefined;
|
|
15
22
|
onError: (error: unknown) => void;
|
|
16
23
|
};
|
|
17
24
|
export type ChatThreadSnapshot<T> = {
|
|
18
25
|
messages: readonly T[];
|
|
26
|
+
timeline: readonly TimelineItem<T>[];
|
|
19
27
|
outgoing: ReturnType<DurableChatSender["snapshot"]>;
|
|
20
28
|
hasEarlier: boolean;
|
|
21
|
-
|
|
29
|
+
resetVersion: number;
|
|
30
|
+
complete: boolean;
|
|
31
|
+
readCounts?: ReadCountsResult;
|
|
32
|
+
unread: ReturnType<typeof unreadInConversation> | undefined;
|
|
33
|
+
messageReading: Readonly<Record<string, MessageReadingState>>;
|
|
22
34
|
};
|
|
23
35
|
export type ChatSummarySnapshot<T> = {
|
|
24
36
|
threads: readonly {
|
|
25
37
|
threadId: string;
|
|
26
38
|
lastMessage?: T;
|
|
39
|
+
lastItem?: TimelineItem<T>;
|
|
27
40
|
unread: ChatThreadSnapshot<T>["unread"];
|
|
28
41
|
outgoingCount: number;
|
|
29
42
|
}[];
|
|
30
|
-
unreadMessageCount: number;
|
|
31
|
-
badgeCount: number;
|
|
43
|
+
unreadMessageCount: number | undefined;
|
|
44
|
+
badgeCount: number | undefined;
|
|
32
45
|
connection: ChatConnectionState;
|
|
33
46
|
};
|
|
34
47
|
/** Owns all browser message clients. Hosts subscribe to stable thread snapshots. */
|
|
@@ -38,18 +51,57 @@ export declare class ChatSession<T extends StoredMessage> {
|
|
|
38
51
|
private connection;
|
|
39
52
|
private reading;
|
|
40
53
|
private readers;
|
|
54
|
+
private counts?;
|
|
55
|
+
private countsReceiver?;
|
|
56
|
+
private countsThread?;
|
|
57
|
+
private countSnapshots;
|
|
58
|
+
private countClosings;
|
|
59
|
+
private countTasks;
|
|
41
60
|
private sender;
|
|
42
61
|
private listeners;
|
|
43
62
|
private snapshots;
|
|
63
|
+
private timelineDecoded;
|
|
64
|
+
private decodeTimeline;
|
|
65
|
+
private historyEdges;
|
|
66
|
+
private earlierReads;
|
|
44
67
|
private off;
|
|
45
68
|
private globalListeners;
|
|
46
69
|
private globalSnapshot?;
|
|
70
|
+
private closedThread;
|
|
71
|
+
private closedSummary;
|
|
47
72
|
private connectionState;
|
|
73
|
+
private catchUp;
|
|
74
|
+
private readingAllowed;
|
|
75
|
+
private readingListeners;
|
|
76
|
+
private acceptedReading;
|
|
77
|
+
private readingAccess?;
|
|
78
|
+
private readingStarted;
|
|
79
|
+
private readingStarting?;
|
|
80
|
+
private withdrawn;
|
|
81
|
+
private revoking?;
|
|
48
82
|
private closed;
|
|
49
83
|
private starting?;
|
|
84
|
+
private initializing?;
|
|
50
85
|
private closing?;
|
|
51
86
|
constructor(options: ChatSessionOptions<T>);
|
|
52
87
|
start(): Promise<void>;
|
|
88
|
+
/** Initializes local state and background reading sync without opening a
|
|
89
|
+
* second transport when the host already owns calls/presence. */
|
|
90
|
+
initialize(): Promise<void>;
|
|
91
|
+
private startReading;
|
|
92
|
+
/** One-time migration only: never establishes trusted history coverage. */
|
|
93
|
+
importLegacyMessages(threadId: string, messages: T[]): Promise<void>;
|
|
94
|
+
getReadingSnapshot(): import("./readIndex.js").ReadIndexState | undefined;
|
|
95
|
+
subscribeReading(listener: () => void): () => void;
|
|
96
|
+
private setReadingAllowed;
|
|
97
|
+
private readingAuthority;
|
|
98
|
+
private authorizeReading;
|
|
99
|
+
private readingOperation;
|
|
100
|
+
refreshReading(): void;
|
|
101
|
+
synchronizeReading(cursor?: string, profileId?: string): Promise<import("./readIndex.js").ReadIndexState>;
|
|
102
|
+
/** Explicit text-history acknowledgement; unheard audio stays unread. */
|
|
103
|
+
markRead(threadId: string, messages?: readonly ConversationReadMessage[]): Promise<import("./readIndex.js").ReadIndexState>;
|
|
104
|
+
updateReading(transform: Parameters<ReadingClient["update"]>[0]): Promise<import("./readIndex.js").ReadIndexState>;
|
|
53
105
|
private assertOpen;
|
|
54
106
|
getMessages(threadId: string, options?: {
|
|
55
107
|
limit?: number;
|
|
@@ -57,7 +109,13 @@ export declare class ChatSession<T extends StoredMessage> {
|
|
|
57
109
|
timestamp: number;
|
|
58
110
|
id: string;
|
|
59
111
|
};
|
|
60
|
-
|
|
112
|
+
earlier?: boolean;
|
|
113
|
+
all?: boolean;
|
|
114
|
+
refresh?: boolean;
|
|
115
|
+
}): Promise<MessageSnapshot<T>>;
|
|
116
|
+
private rememberHistoryEdge;
|
|
117
|
+
private readEarlier;
|
|
118
|
+
private readAll;
|
|
61
119
|
sendMessage(threadId: string, input: {
|
|
62
120
|
to: string;
|
|
63
121
|
body: ChatBody;
|
|
@@ -65,14 +123,26 @@ export declare class ChatSession<T extends StoredMessage> {
|
|
|
65
123
|
}): Promise<ChatRuntimeMessage>;
|
|
66
124
|
getReaders(threadId: string, messageId: string, page?: Omit<MessageReadersQuery, "threadId" | "messageId">, signal?: AbortSignal): Promise<import("./messageReaders.js").MessageReadersPage>;
|
|
67
125
|
retryMessage(messageId: string): Promise<ChatRuntimeMessage>;
|
|
68
|
-
observeMessages(threadId: string, ids: string[], event: "visible" | "played"):
|
|
126
|
+
observeMessages(threadId: string, ids: string[], event: "visible" | "played"): ReturnType<ReadingClient["recordVisible"]>;
|
|
127
|
+
observeMessages(threadId: string | undefined, ids: string[], event: "receipts", clientInstanceId?: string): Promise<void>;
|
|
128
|
+
/** Bridge transports can delegate social events without maintaining clients. */
|
|
129
|
+
handleReach(event: OnlineReachEvent): Promise<"handled" | undefined>;
|
|
130
|
+
private wakeCounts;
|
|
131
|
+
private publishCountSnapshot;
|
|
132
|
+
private publishCounts;
|
|
133
|
+
private watchCounts;
|
|
69
134
|
getSnapshot(): ChatSummarySnapshot<T>;
|
|
70
135
|
getSnapshot(threadId: string): ChatThreadSnapshot<T>;
|
|
136
|
+
private buildThreadSnapshot;
|
|
71
137
|
subscribe(listener: () => void): () => void;
|
|
72
|
-
subscribe(threadId: string, listener: () => void): () => void;
|
|
138
|
+
subscribe(threadId: string, listener: () => void, fields?: readonly (keyof ChatThreadSnapshot<T>)[]): () => void;
|
|
73
139
|
private publishAll;
|
|
74
140
|
private buildGlobal;
|
|
75
141
|
private publishGlobal;
|
|
76
142
|
private publish;
|
|
143
|
+
/** Permanently retire this identity/team session. UI withdrawal is immediate;
|
|
144
|
+
* completion means all durable stores have crossed the same authority epoch. */
|
|
145
|
+
revokeAccess(): Promise<void>;
|
|
77
146
|
close(): Promise<void>;
|
|
147
|
+
private dispose;
|
|
78
148
|
}
|