@pmate/chat-sdk 0.11.4 → 0.11.6
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/lib/mentions/model.d.ts
CHANGED
package/lib/mentions/model.js
CHANGED
|
@@ -12,7 +12,7 @@ export function filterMentionCandidates(candidates, query) {
|
|
|
12
12
|
if (seen.has(candidate.id))
|
|
13
13
|
return false;
|
|
14
14
|
seen.add(candidate.id);
|
|
15
|
-
return candidate.name.toLocaleLowerCase().includes(query.toLocaleLowerCase());
|
|
15
|
+
return [candidate.name, ...(candidate.aliases ?? [])].some(name => name.toLocaleLowerCase().includes(query.toLocaleLowerCase()));
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
/** Rebase untouched mentions; editing any part of a mention removes its identity. */
|
|
@@ -82,14 +82,12 @@ export interface ReadIndexPersistence {
|
|
|
82
82
|
/** Transform and persist atomically; UI publishes only the committed result. */
|
|
83
83
|
update(appId: string, profileId: string, transform: (current: ReadIndexState) => ReadIndexState): Promise<ReadIndexState>;
|
|
84
84
|
}
|
|
85
|
-
/**
|
|
85
|
+
/** Dexie preserves the legacy database/store and outbound [appId, profileId] keys. */
|
|
86
86
|
export declare class IndexedDbReadIndex implements ReadIndexPersistence {
|
|
87
|
-
private
|
|
88
|
-
private
|
|
89
|
-
private database?;
|
|
87
|
+
private database;
|
|
88
|
+
private profiles;
|
|
90
89
|
private closed;
|
|
91
90
|
constructor(factory?: IDBFactory, name?: string);
|
|
92
|
-
private getDatabase;
|
|
93
91
|
private transaction;
|
|
94
92
|
load(appId: string, profileId: string): Promise<ReadIndexState | undefined>;
|
|
95
93
|
update(appId: string, profileId: string, transform: (current: ReadIndexState) => ReadIndexState): Promise<ReadIndexState>;
|
package/lib/runtime/readIndex.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Dexie } from "dexie";
|
|
1
2
|
import { isValidChatTeamId } from "./thread.js";
|
|
2
3
|
export class ReadSyncGap extends Error {
|
|
3
4
|
cursor;
|
|
@@ -162,107 +163,57 @@ export function unreadInThread(state, threadId) {
|
|
|
162
163
|
const unreadMentionCount = messages.filter(message => message.mentions.includes(state.profileId)).length;
|
|
163
164
|
return { unreadMessageCount, unreadMentionCount, badgeCount: thread.kind === "group" ? unreadMentionCount : unreadMessageCount };
|
|
164
165
|
}
|
|
165
|
-
/**
|
|
166
|
+
/** Dexie preserves the legacy database/store and outbound [appId, profileId] keys. */
|
|
166
167
|
export class IndexedDbReadIndex {
|
|
167
|
-
factory;
|
|
168
|
-
name;
|
|
169
168
|
database;
|
|
169
|
+
profiles;
|
|
170
170
|
closed = false;
|
|
171
171
|
constructor(factory = globalThis.indexedDB, name = "pmate-social-read-index") {
|
|
172
|
-
this.factory = factory;
|
|
173
|
-
this.name = name;
|
|
174
172
|
if (!factory)
|
|
175
173
|
throw new Error("READ_STORAGE_UNAVAILABLE");
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return this.database;
|
|
182
|
-
let abandoned = false;
|
|
183
|
-
const pending = new Promise((resolve, reject) => {
|
|
184
|
-
const request = this.factory.open(this.name);
|
|
185
|
-
request.onupgradeneeded = () => { if (!request.result.objectStoreNames.contains("profiles"))
|
|
186
|
-
request.result.createObjectStore("profiles"); };
|
|
187
|
-
request.onsuccess = () => {
|
|
188
|
-
const db = request.result;
|
|
189
|
-
if (abandoned || this.closed) {
|
|
190
|
-
db.close();
|
|
191
|
-
reject(new Error("READ_STORAGE_CLOSED"));
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
const invalidate = () => { if (this.database === pending)
|
|
195
|
-
this.database = undefined; };
|
|
196
|
-
db.onversionchange = () => { db.close(); invalidate(); };
|
|
197
|
-
db.onclose = invalidate;
|
|
198
|
-
resolve(db);
|
|
199
|
-
};
|
|
200
|
-
request.onerror = () => { abandoned = true; reject(request.error); };
|
|
201
|
-
request.onblocked = () => { abandoned = true; reject(new Error("READ_STORAGE_BLOCKED")); };
|
|
202
|
-
});
|
|
203
|
-
this.database = pending;
|
|
204
|
-
void pending.catch(() => { if (this.database === pending)
|
|
205
|
-
this.database = undefined; });
|
|
206
|
-
return pending;
|
|
174
|
+
this.database = new Dexie(name, { indexedDB: factory, IDBKeyRange: globalThis.IDBKeyRange });
|
|
175
|
+
// The empty schema retains the legacy out-of-line primary key and records.
|
|
176
|
+
this.database.version(1).stores({ profiles: "" });
|
|
177
|
+
this.profiles = this.database.table("profiles");
|
|
178
|
+
this.database.on("versionchange", () => { this.database.close({ disableAutoOpen: false }); return false; });
|
|
207
179
|
}
|
|
208
180
|
async transaction(mode, execute) {
|
|
209
181
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
210
|
-
const pending = this.getDatabase(), db = await pending;
|
|
211
182
|
if (this.closed)
|
|
212
183
|
throw new Error("READ_STORAGE_CLOSED");
|
|
213
|
-
let
|
|
184
|
+
let started = false;
|
|
214
185
|
try {
|
|
215
|
-
|
|
186
|
+
return await this.database.transaction(mode, this.profiles, async () => {
|
|
187
|
+
if (this.closed)
|
|
188
|
+
throw new Error("READ_STORAGE_CLOSED");
|
|
189
|
+
started = true;
|
|
190
|
+
return execute();
|
|
191
|
+
});
|
|
216
192
|
}
|
|
217
193
|
catch (error) {
|
|
218
|
-
if (
|
|
194
|
+
if (this.closed)
|
|
195
|
+
throw new Error("READ_STORAGE_CLOSED");
|
|
196
|
+
// Retry only failed transaction creation, never a transform or write.
|
|
197
|
+
if (attempt || started || this.closed || error?.name !== "InvalidStateError")
|
|
219
198
|
throw error;
|
|
220
|
-
|
|
221
|
-
db.close();
|
|
222
|
-
if (this.database === pending)
|
|
223
|
-
this.database = undefined;
|
|
224
|
-
continue;
|
|
199
|
+
this.database.close({ disableAutoOpen: false });
|
|
225
200
|
}
|
|
226
|
-
// Start requests synchronously after creation, including on WebKit.
|
|
227
|
-
return execute(transaction);
|
|
228
201
|
}
|
|
229
202
|
throw new Error("READ_STORAGE_UNAVAILABLE");
|
|
230
203
|
}
|
|
231
204
|
async load(appId, profileId) {
|
|
232
|
-
return this.transaction("
|
|
233
|
-
const request = transaction.objectStore("profiles").get([appId, profileId]);
|
|
234
|
-
transaction.oncomplete = () => resolve(request.result);
|
|
235
|
-
transaction.onerror = () => reject(transaction.error);
|
|
236
|
-
transaction.onabort = () => reject(transaction.error ?? new Error("READ_STORAGE_ABORTED"));
|
|
237
|
-
}));
|
|
205
|
+
return this.transaction("r", () => this.profiles.get([appId, profileId]));
|
|
238
206
|
}
|
|
239
207
|
async update(appId, profileId, transform) {
|
|
240
|
-
return this.transaction("
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (next.appId !== appId || next.profileId !== profileId || next.schemaVersion !== 1)
|
|
248
|
-
throw new Error("READ_IDENTITY_MISMATCH");
|
|
249
|
-
store.put(next, [appId, profileId]);
|
|
250
|
-
}
|
|
251
|
-
catch (error) {
|
|
252
|
-
failure = error;
|
|
253
|
-
transaction.abort();
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
transaction.oncomplete = () => resolve(next);
|
|
257
|
-
transaction.onerror = () => reject(failure ?? transaction.error);
|
|
258
|
-
transaction.onabort = () => reject(failure ?? transaction.error ?? new Error("READ_STORAGE_ABORTED"));
|
|
259
|
-
}));
|
|
208
|
+
return this.transaction("rw", async () => {
|
|
209
|
+
const next = transform(await this.profiles.get([appId, profileId]) ?? createReadIndex(appId, profileId));
|
|
210
|
+
if (next.appId !== appId || next.profileId !== profileId || next.schemaVersion !== 1)
|
|
211
|
+
throw new Error("READ_IDENTITY_MISMATCH");
|
|
212
|
+
await this.profiles.put(next, [appId, profileId]);
|
|
213
|
+
return next;
|
|
214
|
+
});
|
|
260
215
|
}
|
|
261
|
-
async close() { this.closed = true;
|
|
262
|
-
const db = await pending.catch(error => { if (error?.message !== "READ_STORAGE_CLOSED")
|
|
263
|
-
throw error; return undefined; });
|
|
264
|
-
db?.close();
|
|
265
|
-
} }
|
|
216
|
+
async close() { this.closed = true; this.database.close(); }
|
|
266
217
|
}
|
|
267
218
|
export function sourcesInConversation(state, conversationId) {
|
|
268
219
|
return state.threads.filter(thread => (thread.conversationId ?? thread.id) === conversationId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pmate/chat-sdk",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.6",
|
|
4
4
|
"description": "Cross-platform PMate chat input components, voice input primitives, and ASR helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@emoji-mart/react": "^1.1.1",
|
|
34
34
|
"@msgpack/msgpack": "^3.1.2",
|
|
35
35
|
"@noble/hashes": "^2.4.0",
|
|
36
|
+
"dexie": "^4.0.4",
|
|
36
37
|
"jotai": "^2.16.1"
|
|
37
38
|
},
|
|
38
39
|
"peerDependencies": {
|