@pmate/chat-sdk 0.11.1 → 0.11.3
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.
|
@@ -84,8 +84,13 @@ export interface ReadIndexPersistence {
|
|
|
84
84
|
}
|
|
85
85
|
/** IndexedDB transactions serialize tabs, and retain offline outbox across process restarts. */
|
|
86
86
|
export declare class IndexedDbReadIndex implements ReadIndexPersistence {
|
|
87
|
-
private
|
|
87
|
+
private factory;
|
|
88
|
+
private name;
|
|
89
|
+
private database?;
|
|
90
|
+
private closed;
|
|
88
91
|
constructor(factory?: IDBFactory, name?: string);
|
|
92
|
+
private getDatabase;
|
|
93
|
+
private transaction;
|
|
89
94
|
load(appId: string, profileId: string): Promise<ReadIndexState | undefined>;
|
|
90
95
|
update(appId: string, profileId: string, transform: (current: ReadIndexState) => ReadIndexState): Promise<ReadIndexState>;
|
|
91
96
|
close(): Promise<void>;
|
package/lib/runtime/readIndex.js
CHANGED
|
@@ -164,32 +164,80 @@ export function unreadInThread(state, threadId) {
|
|
|
164
164
|
}
|
|
165
165
|
/** IndexedDB transactions serialize tabs, and retain offline outbox across process restarts. */
|
|
166
166
|
export class IndexedDbReadIndex {
|
|
167
|
+
factory;
|
|
168
|
+
name;
|
|
167
169
|
database;
|
|
170
|
+
closed = false;
|
|
168
171
|
constructor(factory = globalThis.indexedDB, name = "pmate-social-read-index") {
|
|
172
|
+
this.factory = factory;
|
|
173
|
+
this.name = name;
|
|
169
174
|
if (!factory)
|
|
170
175
|
throw new Error("READ_STORAGE_UNAVAILABLE");
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
176
|
+
}
|
|
177
|
+
getDatabase() {
|
|
178
|
+
if (this.closed)
|
|
179
|
+
return Promise.reject(new Error("READ_STORAGE_CLOSED"));
|
|
180
|
+
if (this.database)
|
|
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")); };
|
|
177
202
|
});
|
|
203
|
+
this.database = pending;
|
|
204
|
+
void pending.catch(() => { if (this.database === pending)
|
|
205
|
+
this.database = undefined; });
|
|
206
|
+
return pending;
|
|
207
|
+
}
|
|
208
|
+
async transaction(mode, execute) {
|
|
209
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
210
|
+
const pending = this.getDatabase(), db = await pending;
|
|
211
|
+
if (this.closed)
|
|
212
|
+
throw new Error("READ_STORAGE_CLOSED");
|
|
213
|
+
let transaction;
|
|
214
|
+
try {
|
|
215
|
+
transaction = db.transaction("profiles", mode);
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
if (attempt || error?.name !== "InvalidStateError")
|
|
219
|
+
throw error;
|
|
220
|
+
// Retry only transaction creation: no transform or write has run yet.
|
|
221
|
+
db.close();
|
|
222
|
+
if (this.database === pending)
|
|
223
|
+
this.database = undefined;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
// Start requests synchronously after creation, including on WebKit.
|
|
227
|
+
return execute(transaction);
|
|
228
|
+
}
|
|
229
|
+
throw new Error("READ_STORAGE_UNAVAILABLE");
|
|
178
230
|
}
|
|
179
231
|
async load(appId, profileId) {
|
|
180
|
-
|
|
181
|
-
return new Promise((resolve, reject) => {
|
|
182
|
-
const transaction = database.transaction("profiles", "readonly");
|
|
232
|
+
return this.transaction("readonly", transaction => new Promise((resolve, reject) => {
|
|
183
233
|
const request = transaction.objectStore("profiles").get([appId, profileId]);
|
|
184
234
|
transaction.oncomplete = () => resolve(request.result);
|
|
185
235
|
transaction.onerror = () => reject(transaction.error);
|
|
186
236
|
transaction.onabort = () => reject(transaction.error ?? new Error("READ_STORAGE_ABORTED"));
|
|
187
|
-
});
|
|
237
|
+
}));
|
|
188
238
|
}
|
|
189
239
|
async update(appId, profileId, transform) {
|
|
190
|
-
|
|
191
|
-
return new Promise((resolve, reject) => {
|
|
192
|
-
const transaction = database.transaction("profiles", "readwrite");
|
|
240
|
+
return this.transaction("readwrite", transaction => new Promise((resolve, reject) => {
|
|
193
241
|
const store = transaction.objectStore("profiles");
|
|
194
242
|
const request = store.get([appId, profileId]);
|
|
195
243
|
let next, failure;
|
|
@@ -208,9 +256,13 @@ export class IndexedDbReadIndex {
|
|
|
208
256
|
transaction.oncomplete = () => resolve(next);
|
|
209
257
|
transaction.onerror = () => reject(failure ?? transaction.error);
|
|
210
258
|
transaction.onabort = () => reject(failure ?? transaction.error ?? new Error("READ_STORAGE_ABORTED"));
|
|
211
|
-
});
|
|
259
|
+
}));
|
|
212
260
|
}
|
|
213
|
-
async close() {
|
|
261
|
+
async close() { this.closed = true; const pending = this.database; this.database = undefined; if (pending) {
|
|
262
|
+
const db = await pending.catch(error => { if (error?.message !== "READ_STORAGE_CLOSED")
|
|
263
|
+
throw error; return undefined; });
|
|
264
|
+
db?.close();
|
|
265
|
+
} }
|
|
214
266
|
}
|
|
215
267
|
export function sourcesInConversation(state, conversationId) {
|
|
216
268
|
return state.threads.filter(thread => (thread.conversationId ?? thread.id) === conversationId);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SocialGroup } from "./socialReadClient.js";
|
|
1
2
|
export type TeamAssignment = {
|
|
2
3
|
appId: string;
|
|
3
4
|
sourceThreadId: string;
|
|
@@ -6,6 +7,8 @@ export type TeamAssignment = {
|
|
|
6
7
|
actorId: string;
|
|
7
8
|
assignmentId: string;
|
|
8
9
|
createdAt: number;
|
|
10
|
+
groupId?: string;
|
|
11
|
+
groupRevision?: string;
|
|
9
12
|
};
|
|
10
13
|
export type TeamMigrationPlan = {
|
|
11
14
|
status: "assigned" | "noHistory" | "noCommonTeam" | "ready" | "needsSelection";
|
|
@@ -17,6 +20,12 @@ export type TeamMigrationPlan = {
|
|
|
17
20
|
messageCount?: number;
|
|
18
21
|
assignment?: TeamAssignment;
|
|
19
22
|
};
|
|
23
|
+
export type GroupTeamMigrationPlan = Omit<TeamMigrationPlan, "status"> & {
|
|
24
|
+
status: "assigned" | "noCommonTeam" | "ready" | "needsSelection";
|
|
25
|
+
group: SocialGroup;
|
|
26
|
+
canAssign: boolean;
|
|
27
|
+
legacyWritesClosed: boolean;
|
|
28
|
+
};
|
|
20
29
|
export type TeamMigrationResult = {
|
|
21
30
|
status: "assigned";
|
|
22
31
|
assignment: TeamAssignment;
|
|
@@ -51,6 +60,9 @@ export declare class TeamMigrationClient {
|
|
|
51
60
|
});
|
|
52
61
|
plan(peerId: string): Promise<TeamMigrationPlan>;
|
|
53
62
|
assign(peerId: string, teamId: string): Promise<TeamMigrationResult>;
|
|
63
|
+
planGroup(groupId: string): Promise<GroupTeamMigrationPlan>;
|
|
64
|
+
/** Only the current owner can migrate; expectedRevision fences roster changes. */
|
|
65
|
+
assignGroup(groupId: string, teamId: string, expectedRevision: string): Promise<TeamMigrationResult>;
|
|
54
66
|
reconcileDelivery(input: {
|
|
55
67
|
peerId: string;
|
|
56
68
|
teamId: string;
|
|
@@ -20,6 +20,14 @@ export class TeamMigrationClient {
|
|
|
20
20
|
async assign(peerId, teamId) {
|
|
21
21
|
return this.request("", { method: "POST", body: JSON.stringify({ appId: this.options.appId, profileId: this.options.profileId, peerId, teamId }) });
|
|
22
22
|
}
|
|
23
|
+
async planGroup(groupId) {
|
|
24
|
+
const query = new URLSearchParams({ appId: this.options.appId, profileId: this.options.profileId, groupId });
|
|
25
|
+
return this.request(`/groups/plan?${query}`);
|
|
26
|
+
}
|
|
27
|
+
/** Only the current owner can migrate; expectedRevision fences roster changes. */
|
|
28
|
+
async assignGroup(groupId, teamId, expectedRevision) {
|
|
29
|
+
return this.request("/groups", { method: "POST", body: JSON.stringify({ appId: this.options.appId, profileId: this.options.profileId, groupId, teamId, expectedRevision }) });
|
|
30
|
+
}
|
|
23
31
|
async reconcileDelivery(input) {
|
|
24
32
|
const query = new URLSearchParams({ ...input, appId: this.options.appId, profileId: this.options.profileId });
|
|
25
33
|
return this.request(`/delivery?${query}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pmate/chat-sdk",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
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",
|
|
@@ -28,6 +28,15 @@
|
|
|
28
28
|
"publishConfig": {
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"clean": "rimraf lib",
|
|
33
|
+
"build": "pnpm run clean && tsc -p tsconfig.build.json && cp src/voice/recorder-processor.js lib/voice/recorder-processor.js",
|
|
34
|
+
"prepack": "pnpm run build",
|
|
35
|
+
"npm:publish": "pnpm run build && npm publish --access public --registry https://registry.npmjs.org/",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"storybook": "storybook dev -p 6009",
|
|
38
|
+
"build-storybook": "storybook build --output-dir dist"
|
|
39
|
+
},
|
|
31
40
|
"dependencies": {
|
|
32
41
|
"@emoji-mart/data": "^1.2.1",
|
|
33
42
|
"@emoji-mart/react": "^1.1.1",
|
|
@@ -42,24 +51,16 @@
|
|
|
42
51
|
"devDependencies": {
|
|
43
52
|
"@storybook/addon-docs": "^9.0.17",
|
|
44
53
|
"@storybook/react-vite": "9.0.17",
|
|
45
|
-
"@tailwindcss/vite": "
|
|
46
|
-
"@types/react": "
|
|
47
|
-
"@types/react-dom": "
|
|
54
|
+
"@tailwindcss/vite": "catalog:",
|
|
55
|
+
"@types/react": "catalog:",
|
|
56
|
+
"@types/react-dom": "catalog:",
|
|
48
57
|
"fake-indexeddb": "^6.2.5",
|
|
49
|
-
"react": "
|
|
50
|
-
"react-dom": "
|
|
58
|
+
"react": "catalog:",
|
|
59
|
+
"react-dom": "catalog:",
|
|
51
60
|
"rimraf": "^6.1.2",
|
|
52
61
|
"storybook": "^9.0.17",
|
|
53
|
-
"tailwindcss": "
|
|
62
|
+
"tailwindcss": "catalog:",
|
|
54
63
|
"typescript": "5.9.2",
|
|
55
64
|
"vitest": "^4.0.17"
|
|
56
|
-
},
|
|
57
|
-
"scripts": {
|
|
58
|
-
"clean": "rimraf lib",
|
|
59
|
-
"build": "pnpm run clean && tsc -p tsconfig.build.json && cp src/voice/recorder-processor.js lib/voice/recorder-processor.js",
|
|
60
|
-
"npm:publish": "pnpm run build && npm publish --access public --registry https://registry.npmjs.org/",
|
|
61
|
-
"test": "vitest run",
|
|
62
|
-
"storybook": "storybook dev -p 6009",
|
|
63
|
-
"build-storybook": "storybook build --output-dir dist"
|
|
64
65
|
}
|
|
65
|
-
}
|
|
66
|
+
}
|