@pmate/chat-sdk 0.12.0-next.77 → 0.12.0-next.79
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.
|
@@ -304,12 +304,11 @@ export class ChatSession {
|
|
|
304
304
|
throw new Error("INVALID_MESSAGE_QUERY");
|
|
305
305
|
// Explicit authoritative changes (for example history assignment) can invalidate
|
|
306
306
|
// even a recently cached empty window. Keep this on the existing read surface.
|
|
307
|
-
|
|
308
|
-
await this.messages.read(threadId, options.limit ?? 100, true);
|
|
307
|
+
const refreshed = options.refresh ? await this.messages.read(threadId, options.limit ?? 100, true) : undefined;
|
|
309
308
|
if (options.all) {
|
|
310
309
|
return this.readAll(threadId);
|
|
311
310
|
}
|
|
312
|
-
const page = await (options.before ? this.messages.earlier(threadId, options.before, options.limit) : this.messages.read(threadId, options.limit));
|
|
311
|
+
const page = await (options.before ? this.messages.earlier(threadId, options.before, options.limit) : refreshed ?? this.messages.read(threadId, options.limit));
|
|
313
312
|
this.assertOpen();
|
|
314
313
|
await this.confirmMessages(page.messages);
|
|
315
314
|
if (this.initializing && !this.readingStarted && page.complete)
|
|
@@ -339,7 +338,12 @@ export class ChatSession {
|
|
|
339
338
|
if (running)
|
|
340
339
|
return running;
|
|
341
340
|
const task = (async () => {
|
|
342
|
-
const
|
|
341
|
+
const cached = this.messages.getSnapshot(threadId), known = this.historyEdges.get(threadId);
|
|
342
|
+
// The earlier-page path validates its own durable scope and coverage.
|
|
343
|
+
// A known boundary does not require rereading/reconfirming the newest
|
|
344
|
+
// message bodies before loading older messages.
|
|
345
|
+
const recent = cached?.complete && known?.hasEarlier && known.version === cached.resetVersion
|
|
346
|
+
? cached : await this.getMessages(threadId);
|
|
343
347
|
if (!recent.complete)
|
|
344
348
|
throw new Error("MESSAGE_WINDOW_INCOMPLETE");
|
|
345
349
|
const edge = this.historyEdges.get(threadId);
|
|
@@ -57,6 +57,7 @@ export declare class IndexedDbMessageStore<T extends StoredMessage> {
|
|
|
57
57
|
hasEarlier: boolean;
|
|
58
58
|
coverage: MessageCoverage | undefined;
|
|
59
59
|
}>;
|
|
60
|
+
private readRows;
|
|
60
61
|
/** Bounded identifier reads for SDK-owned view handoffs; no transport fallback. */
|
|
61
62
|
readSelected(thread: string, ids: readonly string[]): Promise<{
|
|
62
63
|
messages: T[];
|
|
@@ -42,12 +42,24 @@ export class IndexedDbMessageStore {
|
|
|
42
42
|
const access = await this.scopeAccess();
|
|
43
43
|
if (access.revoked)
|
|
44
44
|
return { messages: [], hasEarlier: false, coverage: { complete: false, revoked: true, resetVersion: 0, scopeVersion: access.version } };
|
|
45
|
-
const
|
|
46
|
-
const records = await range.reverse().limit(limit + 1).toArray();
|
|
45
|
+
const records = await this.readRows(this.rows, thread, limit, before);
|
|
47
46
|
return this.controlPage({ messages: records.slice(0, limit).reverse().map(row => row.value), hasEarlier: records.length > limit, coverage: this.scopedCoverage(await this.coverage.get([this.key, thread]), access) });
|
|
48
47
|
});
|
|
49
48
|
return this.finishPage(thread, page);
|
|
50
49
|
}
|
|
50
|
+
readRows(table, thread, limit, before) {
|
|
51
|
+
const lower = [this.key, thread, Dexie.minKey, Dexie.minKey], upper = [this.key, thread, before?.timestamp ?? Dexie.maxKey, before?.id ?? Dexie.maxKey];
|
|
52
|
+
const index = Dexie.currentTransaction.idbtrans.objectStore(table.name).index("[scope+thread+time+id]");
|
|
53
|
+
// Bounded batches avoid a browser round trip per history row. Older engines
|
|
54
|
+
// retain the same indexed cursor path and pagination boundaries.
|
|
55
|
+
if (typeof index.getAllRecords === "function")
|
|
56
|
+
return new Dexie.Promise((resolve, reject) => {
|
|
57
|
+
const request = index.getAllRecords({ query: IDBKeyRange.bound(lower, upper, false, Boolean(before)), count: limit + 1, direction: "prev" });
|
|
58
|
+
request.onsuccess = () => resolve(request.result.map(record => record.value));
|
|
59
|
+
request.onerror = () => reject(request.error);
|
|
60
|
+
});
|
|
61
|
+
return table.where("[scope+thread+time+id]").between(lower, upper, true, !before).reverse().limit(limit + 1).toArray();
|
|
62
|
+
}
|
|
51
63
|
/** Bounded identifier reads for SDK-owned view handoffs; no transport fallback. */
|
|
52
64
|
async readSelected(thread, ids) {
|
|
53
65
|
if (ids.length > 100 || ids.some(id => !id || id.length > 256))
|
|
@@ -118,7 +130,7 @@ export class IndexedDbMessageStore {
|
|
|
118
130
|
const coverage = access.revoked ? { complete: false, revoked: true, resetVersion: 0, scopeVersion: access.version } : this.scopedCoverage(await this.coverage.get([this.key, thread]), access);
|
|
119
131
|
if (coverage?.revoked)
|
|
120
132
|
return { messages: [], hasEarlier: false, coverage };
|
|
121
|
-
const read = (table) =>
|
|
133
|
+
const read = (table) => this.readRows(table, thread, limit, before);
|
|
122
134
|
const [canonical, legacy] = await Promise.all([read(this.rows), read(this.legacy)]);
|
|
123
135
|
const rows = [...new Map([...legacy, ...canonical].map(row => [row.id, row])).values()].sort((a, b) => a.time - b.time || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
124
136
|
return this.controlPage({ messages: rows.slice(-limit).map(row => row.value), hasEarlier: rows.length > limit, coverage });
|
|
@@ -197,7 +209,8 @@ export class IndexedDbMessageStore {
|
|
|
197
209
|
const thread = page.messages[0]?.threadId;
|
|
198
210
|
const targets = thread && references.length ? (await this.rows.bulkGet(references.map(id => [this.key, thread, id]))).flatMap(row => row ? [row.value] : []) : [];
|
|
199
211
|
const selected = [...page.messages, ...targets];
|
|
200
|
-
|
|
212
|
+
// Exact bounded index reads avoid serial cursor seeks across a message page.
|
|
213
|
+
const facts = (await Promise.all(selected.map(message => this.controls.where("[scope+thread+target]").equals([this.key, message.threadId, message.id]).toArray()))).flat();
|
|
201
214
|
const latest = new Map();
|
|
202
215
|
for (const event of [...facts, ...events]) {
|
|
203
216
|
const key = JSON.stringify(controlKey(event));
|
package/package.json
CHANGED