@pmate/chat-sdk 0.12.0-next.3 → 0.12.0-next.4
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.
|
@@ -71,6 +71,7 @@ export class ChatSession {
|
|
|
71
71
|
async getMessages(threadId, options = {}) {
|
|
72
72
|
this.assertOpen();
|
|
73
73
|
const page = await (options.before ? this.messages.earlier(threadId, options.before, options.limit) : this.messages.read(threadId, options.limit));
|
|
74
|
+
await this.sender.confirm(page.messages.map(message => message.id));
|
|
74
75
|
if (!options.before)
|
|
75
76
|
this.publish(threadId);
|
|
76
77
|
return page;
|
|
@@ -143,10 +143,13 @@ export class DurableChatSender {
|
|
|
143
143
|
/** Confirm only after the authoritative message has been durably merged.
|
|
144
144
|
* A tombstone prevents another tab's late failure from recreating the outbox. */
|
|
145
145
|
async confirm(hashes) {
|
|
146
|
+
hashes = [...new Set(hashes)];
|
|
147
|
+
if (!hashes.length)
|
|
148
|
+
return;
|
|
146
149
|
await this.ready;
|
|
147
150
|
await this.db.transaction("rw", this.outbox, async () => {
|
|
148
151
|
const previous = await this.outbox.bulkGet(hashes.map(hash => [this.scope, hash]));
|
|
149
|
-
const confirmed = previous.filter((record) => Boolean(record)).map(record => ({ ...record, status: "sent", confirmed: true }));
|
|
152
|
+
const confirmed = previous.filter((record) => Boolean(record && !record.confirmed)).map(record => ({ ...record, status: "sent", confirmed: true }));
|
|
150
153
|
if (confirmed.length)
|
|
151
154
|
await this.outbox.bulkPut(confirmed);
|
|
152
155
|
});
|
|
@@ -27,6 +27,7 @@ export type MessageClientOptions<T extends StoredMessage> = {
|
|
|
27
27
|
syncWindowMs?: number;
|
|
28
28
|
transactionBytes?: number;
|
|
29
29
|
queueBytes?: number;
|
|
30
|
+
freshnessMs?: number;
|
|
30
31
|
};
|
|
31
32
|
/** Local-first message facade; hosts never select a cache or network source. */
|
|
32
33
|
export declare class MessageClient<T extends StoredMessage> {
|
|
@@ -66,6 +66,9 @@ export class MessageClient {
|
|
|
66
66
|
throw new Error("MESSAGE_CLIENT_CLOSED");
|
|
67
67
|
const result = await this.fillWindow(thread, limit);
|
|
68
68
|
const snapshot = await this.reload(thread);
|
|
69
|
+
if (local.coverage?.complete && Date.now() - (local.coverage.syncedAt ?? 0) > (this.options.freshnessMs ?? 30_000)
|
|
70
|
+
&& (typeof navigator === "undefined" || navigator.onLine !== false))
|
|
71
|
+
this.invalidate(thread);
|
|
69
72
|
return limit === 100 ? snapshot : result;
|
|
70
73
|
})().finally(() => { if (this.reads.get(key) === task)
|
|
71
74
|
this.reads.delete(key); });
|
|
@@ -105,17 +108,20 @@ export class MessageClient {
|
|
|
105
108
|
if (this.closed || this.closing || this.revoked.has(thread))
|
|
106
109
|
return Promise.reject(new Error("MESSAGE_CLIENT_UNAVAILABLE"));
|
|
107
110
|
const queue = this.writes.get(thread) ?? [];
|
|
111
|
+
if (messages.length > 1000 || this.pendingRecords + messages.length > 4096)
|
|
112
|
+
return Promise.reject(new Error("MESSAGE_STORAGE_BACKPRESSURE"));
|
|
108
113
|
let bytes = 0;
|
|
109
114
|
try {
|
|
110
|
-
for (const message of messages)
|
|
115
|
+
for (const message of messages) {
|
|
111
116
|
bytes += this.messageBytes(message);
|
|
117
|
+
if (this.pendingBytes + bytes > this.queueBytes)
|
|
118
|
+
throw new Error("MESSAGE_STORAGE_BACKPRESSURE");
|
|
119
|
+
}
|
|
112
120
|
messages = structuredClone(messages);
|
|
113
121
|
}
|
|
114
122
|
catch (error) {
|
|
115
123
|
return Promise.reject(error);
|
|
116
124
|
}
|
|
117
|
-
if (messages.length > 1000 || this.pendingRecords + messages.length > 4096 || this.pendingBytes + bytes > this.queueBytes)
|
|
118
|
-
return Promise.reject(new Error("MESSAGE_STORAGE_BACKPRESSURE"));
|
|
119
125
|
this.pendingBytes += bytes;
|
|
120
126
|
this.pendingRecords += messages.length;
|
|
121
127
|
const promise = new Promise((resolve, reject) => queue.push({ messages, bytes, resolve, reject }));
|
|
@@ -123,8 +129,9 @@ export class MessageClient {
|
|
|
123
129
|
this.writeTasks.schedule(thread, () => this.flushWrites(thread));
|
|
124
130
|
return promise;
|
|
125
131
|
}
|
|
126
|
-
async persist(thread, messages, checkpoint, complete, nextBefore) {
|
|
132
|
+
async persist(thread, messages, checkpoint, complete, nextBefore, syncedAt) {
|
|
127
133
|
let changed = 0;
|
|
134
|
+
let navigationChanged = false;
|
|
128
135
|
// Coverage advances only after all bounded batches have durably committed.
|
|
129
136
|
let batch = [], bytes = 0;
|
|
130
137
|
const commit = async () => {
|
|
@@ -148,9 +155,10 @@ export class MessageClient {
|
|
|
148
155
|
const old = (await this.storage.read(thread, 1)).coverage;
|
|
149
156
|
if (this.closed || this.revoked.has(thread))
|
|
150
157
|
throw new Error("MESSAGE_CLIENT_UNAVAILABLE");
|
|
151
|
-
|
|
158
|
+
navigationChanged = nextBefore !== undefined && nextBefore !== old?.nextBefore;
|
|
159
|
+
await this.storage.commit(thread, [], { syncedAt: syncedAt ?? old?.syncedAt, complete: complete ?? old?.complete ?? false, checkpoint: checkpoint ?? old?.checkpoint, nextBefore: nextBefore === undefined ? old?.nextBefore : nextBefore });
|
|
152
160
|
}
|
|
153
|
-
if (changed) {
|
|
161
|
+
if (changed || navigationChanged) {
|
|
154
162
|
await this.reload(thread);
|
|
155
163
|
this.channel?.postMessage(thread);
|
|
156
164
|
}
|
|
@@ -213,7 +221,7 @@ export class MessageClient {
|
|
|
213
221
|
throw new Error("MESSAGE_ACCESS_REVOKED");
|
|
214
222
|
if (page.resetRequired)
|
|
215
223
|
throw new Error("MESSAGE_SYNC_RESET_REQUIRED");
|
|
216
|
-
await this.persist(thread, page.messages, page.checkpoint, true, after ? undefined : page.nextBefore);
|
|
224
|
+
await this.persist(thread, page.messages, page.checkpoint, true, after ? undefined : page.nextBefore, Date.now());
|
|
217
225
|
if (!page.hasMoreChanges)
|
|
218
226
|
break;
|
|
219
227
|
if (!page.checkpoint || page.checkpoint === after || !this.options.transport.changes)
|
package/package.json
CHANGED