@pmate/chat-sdk 0.12.0-next.44 → 0.12.0-next.45
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.
|
@@ -39,8 +39,11 @@ export class DurableChatSender {
|
|
|
39
39
|
this.db = new Dexie(options.name ?? "pmate-chat-outbox", { indexedDB: options.indexedDB ?? globalThis.indexedDB, IDBKeyRange: globalThis.IDBKeyRange });
|
|
40
40
|
this.db.version(1).stores({ outbox: "[scope+hash],scope,[scope+threadId]" });
|
|
41
41
|
this.db.version(2).stores({ scopeEpochs: "scope" });
|
|
42
|
+
this.db.version(3).stores({ outbox: "[scope+hash],scope,[scope+threadId],[scope+pendingState+hash]" });
|
|
42
43
|
this.outbox = this.db.table("outbox");
|
|
43
44
|
this.epochs = this.db.table("scopeEpochs");
|
|
45
|
+
this.outbox.hook("creating", (_key, row) => { row.pendingState = row.confirmed ? 0 : 1; });
|
|
46
|
+
this.outbox.hook("updating", (changes, _key, row) => ({ pendingState: (Object.hasOwn(changes, "confirmed") ? changes.confirmed : row.confirmed) ? 0 : 1 }));
|
|
44
47
|
this.db.on("versionchange", () => { this.db.close({ disableAutoOpen: false }); return false; });
|
|
45
48
|
this.ready = this.restore();
|
|
46
49
|
void this.ready.catch(() => { });
|
|
@@ -61,6 +64,7 @@ export class DurableChatSender {
|
|
|
61
64
|
const key = messageScopeKey(scope), db = new Dexie(options.name ?? "pmate-chat-outbox", { indexedDB: options.indexedDB ?? globalThis.indexedDB, IDBKeyRange: globalThis.IDBKeyRange });
|
|
62
65
|
db.version(1).stores({ outbox: "[scope+hash],scope,[scope+threadId]" });
|
|
63
66
|
db.version(2).stores({ scopeEpochs: "scope" });
|
|
67
|
+
db.version(3).stores({ outbox: "[scope+hash],scope,[scope+threadId],[scope+pendingState+hash]" });
|
|
64
68
|
try {
|
|
65
69
|
return await db.transaction("r", db.table("outbox"), db.table("scopeEpochs"), async () => {
|
|
66
70
|
if (((await db.table("scopeEpochs").get(key))?.version ?? 0) !== version)
|
|
@@ -90,7 +94,9 @@ export class DurableChatSender {
|
|
|
90
94
|
await this.epochs.put({ scope: this.scope, version });
|
|
91
95
|
await this.deleteRows();
|
|
92
96
|
}
|
|
93
|
-
|
|
97
|
+
const total = await this.outbox.where("scope").equals(this.scope).count();
|
|
98
|
+
const indexed = await this.outbox.where("[scope+pendingState+hash]").between([this.scope, 0, Dexie.minKey], [this.scope, 1, Dexie.maxKey], true, true).count();
|
|
99
|
+
return { version, advanced: version > current, legacy: total !== indexed };
|
|
94
100
|
});
|
|
95
101
|
this.version = loaded.version;
|
|
96
102
|
if (loaded.advanced)
|
|
@@ -100,7 +106,45 @@ export class DurableChatSender {
|
|
|
100
106
|
const current = await this.readAccess();
|
|
101
107
|
if (current && (!current.allowed || current.version !== this.version))
|
|
102
108
|
return;
|
|
103
|
-
|
|
109
|
+
if (access?.allowed === false)
|
|
110
|
+
return;
|
|
111
|
+
let after;
|
|
112
|
+
const restored = new Map();
|
|
113
|
+
while (!this.closed && !this.withdrawn) {
|
|
114
|
+
const batch = await this.db.transaction("rw", this.outbox, this.epochs, async () => {
|
|
115
|
+
if (((await this.epochs.get(this.scope))?.version ?? 0) !== this.version)
|
|
116
|
+
throw new Error("OUTBOX_SCOPE_CHANGED");
|
|
117
|
+
const keys = await (loaded.legacy
|
|
118
|
+
? this.outbox.where("[scope+hash]").between([this.scope, after ?? Dexie.minKey], [this.scope, Dexie.maxKey], after === undefined, true)
|
|
119
|
+
: this.outbox.where("[scope+pendingState+hash]").between([this.scope, 1, after ?? Dexie.minKey], [this.scope, 1, Dexie.maxKey], after === undefined, true)).limit(128).primaryKeys();
|
|
120
|
+
if (!keys.length)
|
|
121
|
+
return { last: undefined, records: [] };
|
|
122
|
+
const last = keys.at(-1)[1];
|
|
123
|
+
// Key-only metadata covers confirmed history without reading wire bodies.
|
|
124
|
+
// Missing metadata identifies rows written by an older SDK after upgrade.
|
|
125
|
+
const confirmed = loaded.legacy ? await this.outbox.where("[scope+pendingState+hash]").between([this.scope, 0, keys[0][1]], [this.scope, 0, last], true, true).primaryKeys() : [];
|
|
126
|
+
const confirmedHashes = new Set(confirmed.map(key => key[1]));
|
|
127
|
+
const needed = keys.filter(key => !confirmedHashes.has(key[1]));
|
|
128
|
+
const rows = (await this.outbox.bulkGet(needed)).filter((row) => Boolean(row));
|
|
129
|
+
const repairs = rows.filter(row => row.pendingState !== (row.confirmed ? 0 : 1));
|
|
130
|
+
if (repairs.length)
|
|
131
|
+
await this.outbox.bulkPut(repairs.map(row => ({ ...row, pendingState: row.confirmed ? 0 : 1 })));
|
|
132
|
+
return { last, records: rows.filter(row => !row.confirmed && (row.accessVersion ?? 0) === this.version) };
|
|
133
|
+
});
|
|
134
|
+
for (const row of batch.records)
|
|
135
|
+
restored.set(row.hash, row);
|
|
136
|
+
if (batch.last === undefined)
|
|
137
|
+
break;
|
|
138
|
+
after = batch.last;
|
|
139
|
+
// Leave the database transaction before yielding to input/rendering work.
|
|
140
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
141
|
+
}
|
|
142
|
+
if (this.closed || this.withdrawn)
|
|
143
|
+
return;
|
|
144
|
+
const latest = await this.readAccess();
|
|
145
|
+
if (latest && (!latest.allowed || latest.version !== this.version))
|
|
146
|
+
return;
|
|
147
|
+
this.values = restored;
|
|
104
148
|
this.emit();
|
|
105
149
|
}
|
|
106
150
|
async checkAccess() {
|
package/package.json
CHANGED