@agentshouse/mdruntime 0.1.0-cacb4c3f6734e → 0.1.0-caee7332d2235
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/AGENTS.md +277 -222
- package/README.md +20 -10
- package/dist/adapter.d.ts +95 -20
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +15 -2
- package/dist/attempt.d.ts +16 -0
- package/dist/attempt.d.ts.map +1 -0
- package/dist/attempt.js +70 -0
- package/dist/binary.d.ts +23 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +18 -5
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +113 -37
- package/dist/change-set.d.ts +7 -2
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +54 -16
- package/dist/change.d.ts +15 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +26 -1
- package/dist/chat/acts.d.ts +33 -0
- package/dist/chat/acts.d.ts.map +1 -0
- package/dist/chat/acts.js +36 -0
- package/dist/chat/context.d.ts +3 -0
- package/dist/chat/context.d.ts.map +1 -0
- package/dist/chat/context.js +10 -0
- package/dist/chat/earlier.d.ts +10 -0
- package/dist/chat/earlier.d.ts.map +1 -0
- package/dist/chat/earlier.js +54 -0
- package/dist/chat/input.d.ts +9 -0
- package/dist/chat/input.d.ts.map +1 -0
- package/dist/chat/input.js +99 -0
- package/dist/chat/kit.d.ts +61 -0
- package/dist/chat/kit.d.ts.map +1 -0
- package/dist/chat/kit.js +123 -0
- package/dist/chat/lasting.d.ts +6 -0
- package/dist/chat/lasting.d.ts.map +1 -0
- package/dist/chat/lasting.js +19 -0
- package/dist/chat/list.d.ts +27 -0
- package/dist/chat/list.d.ts.map +1 -0
- package/dist/chat/list.js +99 -0
- package/dist/chat/markdown.d.ts +21 -0
- package/dist/chat/markdown.d.ts.map +1 -0
- package/dist/chat/markdown.js +246 -0
- package/dist/chat/message.d.ts +6 -0
- package/dist/chat/message.d.ts.map +1 -0
- package/dist/chat/message.js +192 -0
- package/dist/chat/messages.d.ts +22 -0
- package/dist/chat/messages.d.ts.map +1 -0
- package/dist/chat/messages.js +171 -0
- package/dist/chat/name.d.ts +6 -0
- package/dist/chat/name.d.ts.map +1 -0
- package/dist/chat/name.js +37 -0
- package/dist/chat/opened.d.ts +11 -0
- package/dist/chat/opened.d.ts.map +1 -0
- package/dist/chat/opened.js +17 -0
- package/dist/chat/overlay.d.ts +31 -0
- package/dist/chat/overlay.d.ts.map +1 -0
- package/dist/chat/overlay.js +97 -0
- package/dist/chat/parts.d.ts +57 -0
- package/dist/chat/parts.d.ts.map +1 -0
- package/dist/chat/parts.js +119 -0
- package/dist/chat/pieces.d.ts +62 -0
- package/dist/chat/pieces.d.ts.map +1 -0
- package/dist/chat/pieces.js +1110 -0
- package/dist/chat/process.d.ts +20 -0
- package/dist/chat/process.d.ts.map +1 -0
- package/dist/chat/process.js +32 -0
- package/dist/chat/provider.d.ts +63 -0
- package/dist/chat/provider.d.ts.map +1 -0
- package/dist/chat/provider.js +630 -0
- package/dist/chat/question.d.ts +9 -0
- package/dist/chat/question.d.ts.map +1 -0
- package/dist/chat/question.js +69 -0
- package/dist/chat/questions.d.ts +31 -0
- package/dist/chat/questions.d.ts.map +1 -0
- package/dist/chat/questions.js +111 -0
- package/dist/chat/read.d.ts +5 -0
- package/dist/chat/read.d.ts.map +1 -0
- package/dist/chat/read.js +13 -0
- package/dist/chat/ready.d.ts +34 -0
- package/dist/chat/ready.d.ts.map +1 -0
- package/dist/chat/ready.js +91 -0
- package/dist/chat/steady.d.ts +3 -0
- package/dist/chat/steady.d.ts.map +1 -0
- package/dist/chat/steady.js +26 -0
- package/dist/chat/style.d.ts +4 -0
- package/dist/chat/style.d.ts.map +1 -0
- package/dist/chat/style.js +302 -0
- package/dist/chat.d.ts +14 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +8 -0
- package/dist/client.d.ts +30 -13
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +79 -16
- package/dist/collection.d.ts +23 -13
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +349 -36
- package/dist/connection.d.ts +50 -16
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1968 -424
- package/dist/conversation.d.ts +31 -10
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +521 -170
- package/dist/durability.d.ts +7 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +17 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +157 -0
- package/dist/expectation.d.ts +21 -0
- package/dist/expectation.d.ts.map +1 -0
- package/dist/expectation.js +70 -0
- package/dist/failure.d.ts +13 -0
- package/dist/failure.d.ts.map +1 -0
- package/dist/failure.js +19 -0
- package/dist/fence.d.ts +6 -0
- package/dist/fence.d.ts.map +1 -0
- package/dist/fence.js +10 -0
- package/dist/host.d.ts +15 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +102 -19
- package/dist/identity.d.ts +5 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +19 -1
- package/dist/index.d.ts +16 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/memory.d.ts +11 -4
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +91 -24
- package/dist/model.d.ts +14 -4
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +12 -1
- package/dist/observation.d.ts +7 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +29 -12
- package/dist/open.d.ts +2 -0
- package/dist/open.d.ts.map +1 -0
- package/dist/open.js +1 -0
- package/dist/outcome.d.ts +38 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +45 -1
- package/dist/overlay.d.ts +18 -5
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +247 -165
- package/dist/query.d.ts +60 -4
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +41 -38
- package/dist/reach.d.ts +8 -3
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +73 -24
- package/dist/react.d.ts +31 -20
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +13 -21
- package/dist/read-side.d.ts +16 -7
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +495 -102
- package/dist/readiness.d.ts +53 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +33 -0
- package/dist/reads.d.ts +11 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +98 -0
- package/dist/recovery.d.ts +25 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +197 -0
- package/dist/refresh.d.ts +13 -3
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +72 -42
- package/dist/replica.d.ts +17 -8
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +165 -79
- package/dist/room-draft.d.ts +38 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +141 -33
- package/dist/rows.d.ts +10 -13
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +19 -14
- package/dist/save.d.ts +35 -3
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +522 -107
- package/dist/search.d.ts +52 -8
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +192 -47
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +75 -0
- package/dist/shared.d.ts +15 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +398 -0
- package/dist/status.d.ts +6 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +0 -8
- package/dist/store.d.ts +91 -18
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +704 -283
- package/dist/turn.d.ts +9 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +64 -0
- package/dist/windows.d.ts +26 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +93 -0
- package/dist/wire.d.ts +157 -33
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +65 -31
- package/package.json +26 -14
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
package/dist/store.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import { sameChange } from './overlay.js';
|
|
1
|
+
import { sameBinary, sameChange } from './overlay.js';
|
|
2
|
+
import { differences } from './shared.js';
|
|
3
|
+
import { inUnits, ROW_WEIGHT, stepped } from './turn.js';
|
|
4
|
+
import { precedes } from './windows.js';
|
|
2
5
|
const DATABASE = 'mdruntime';
|
|
6
|
+
const AUTHORED = 'mdruntime-authored';
|
|
7
|
+
const FENCES = 'fences';
|
|
3
8
|
const ROOMS = 'rooms';
|
|
4
9
|
const DOCUMENTS = 'documents';
|
|
5
|
-
const
|
|
6
|
-
const
|
|
10
|
+
const BINARY_FILES = 'binaryFiles';
|
|
11
|
+
const TRANSFERS = 'transfers';
|
|
12
|
+
const RECEIVED = 'received';
|
|
13
|
+
const RECEIVED_PARTS = 'receivedParts';
|
|
7
14
|
const PENDING_CHANGES = 'pendingChanges';
|
|
8
15
|
const DECLARATIONS = 'declarations';
|
|
9
16
|
const ROOM_DRAFTS = 'roomDrafts';
|
|
@@ -11,54 +18,242 @@ const PRESENTATION = 'presentation';
|
|
|
11
18
|
const CONVERSATIONS = 'conversations';
|
|
12
19
|
const MESSAGES = 'messages';
|
|
13
20
|
const CHAT_DRAFTS = 'chatDrafts';
|
|
21
|
+
const CALLS = 'calls';
|
|
22
|
+
const QUEUES = 'queues';
|
|
23
|
+
const FACTS = 'facts';
|
|
14
24
|
const CURRENT = 'current';
|
|
15
|
-
const
|
|
25
|
+
const MEMBER = 'member';
|
|
26
|
+
const TARGETS = 'targets';
|
|
27
|
+
const GENERATION = 'generation';
|
|
28
|
+
const HOLDER = 'holder';
|
|
29
|
+
const BINDING = 'binding';
|
|
30
|
+
export const STORE_SCHEMA = 25;
|
|
31
|
+
const REPLICA_SHAPE = 23;
|
|
32
|
+
export const AUTHORED_SCHEMA = 3;
|
|
33
|
+
const AUTHORED_STORES = [
|
|
34
|
+
[QUEUES, { keyPath: 'locator' }],
|
|
35
|
+
[PENDING_CHANGES, { keyPath: ['locator', 'id'] }],
|
|
36
|
+
[ROOM_DRAFTS, { keyPath: ['locator', 'path'] }],
|
|
37
|
+
[CHAT_DRAFTS, { keyPath: 'conversation' }],
|
|
38
|
+
[CALLS, { keyPath: 'operation' }],
|
|
39
|
+
[FACTS, undefined],
|
|
40
|
+
[FENCES, { keyPath: 'locator' }],
|
|
41
|
+
];
|
|
42
|
+
const REPLICA_STORES = [
|
|
43
|
+
[ROOMS, { keyPath: 'locator' }],
|
|
44
|
+
[DOCUMENTS, { keyPath: ['locator', 'transfer', 'path'] }],
|
|
45
|
+
[BINARY_FILES, { keyPath: ['locator', 'transfer', 'path'] }],
|
|
46
|
+
[TRANSFERS, { keyPath: 'locator' }],
|
|
47
|
+
[RECEIVED, { keyPath: 'key' }],
|
|
48
|
+
[RECEIVED_PARTS, { keyPath: ['key', 'index'] }],
|
|
49
|
+
[DECLARATIONS, undefined],
|
|
50
|
+
[PRESENTATION, { keyPath: 'app' }],
|
|
51
|
+
[CONVERSATIONS, { keyPath: 'conversation' }],
|
|
52
|
+
[MESSAGES, { keyPath: ['conversation', 'identity'] }],
|
|
53
|
+
];
|
|
54
|
+
const READ_BATCH = 256;
|
|
55
|
+
const MESSAGE_INDEXES = [
|
|
56
|
+
['conversation', 'conversation'],
|
|
57
|
+
['position', ['conversation', 'position']],
|
|
58
|
+
['turn', ['conversation', 'turn']],
|
|
59
|
+
];
|
|
16
60
|
function settled(request) {
|
|
17
61
|
return new Promise((resolve, reject) => {
|
|
18
62
|
request.onsuccess = () => resolve(request.result);
|
|
19
63
|
request.onerror = () => reject(request.error);
|
|
20
64
|
});
|
|
21
65
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
66
|
+
const turns = new Map();
|
|
67
|
+
function idle(database) {
|
|
68
|
+
return turns.get(database.name) ?? Promise.resolve();
|
|
69
|
+
}
|
|
70
|
+
export function transacted(database, stores, mode, work) {
|
|
71
|
+
let release = () => { };
|
|
72
|
+
const turn = new Promise((resolve) => {
|
|
73
|
+
release = resolve;
|
|
74
|
+
});
|
|
75
|
+
const before = idle(database);
|
|
76
|
+
const queued = before.then(() => turn);
|
|
77
|
+
turns.set(database.name, queued);
|
|
78
|
+
void queued.then(() => {
|
|
79
|
+
if (turns.get(database.name) === queued)
|
|
80
|
+
turns.delete(database.name);
|
|
81
|
+
});
|
|
82
|
+
return before.then(() => {
|
|
83
|
+
let transaction;
|
|
84
|
+
try {
|
|
85
|
+
transaction = database.transaction(stores, mode);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
release();
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
const committed = new Promise((resolve, reject) => {
|
|
92
|
+
transaction.oncomplete = () => {
|
|
93
|
+
release();
|
|
94
|
+
resolve();
|
|
95
|
+
};
|
|
96
|
+
transaction.onabort = () => {
|
|
97
|
+
release();
|
|
98
|
+
reject(transaction.error);
|
|
99
|
+
};
|
|
100
|
+
transaction.onerror = () => reject(transaction.error);
|
|
101
|
+
});
|
|
102
|
+
let value;
|
|
103
|
+
try {
|
|
104
|
+
value = work(transaction);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
void committed.catch(() => { });
|
|
108
|
+
transaction.abort();
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
return Promise.all([value, committed]).then(([held]) => held);
|
|
27
112
|
});
|
|
28
113
|
}
|
|
29
114
|
const EMPTY_DECLARATIONS = {
|
|
30
115
|
lastCompleted: null,
|
|
31
|
-
generation: null,
|
|
32
116
|
providerLimits: [],
|
|
33
117
|
capabilities: [],
|
|
34
118
|
};
|
|
35
119
|
function declaration(database) {
|
|
36
|
-
|
|
37
|
-
return settled(transaction.objectStore(DECLARATIONS).get(CURRENT)).then((record) => record ?? EMPTY_DECLARATIONS);
|
|
120
|
+
return transacted(database, DECLARATIONS, 'readonly', (transaction) => settled(transaction.objectStore(DECLARATIONS).get(CURRENT))).then((record) => record ?? EMPTY_DECLARATIONS);
|
|
38
121
|
}
|
|
39
122
|
function updateDeclaration(database, update) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
123
|
+
return transacted(database, DECLARATIONS, 'readwrite', (transaction) => {
|
|
124
|
+
const store = transaction.objectStore(DECLARATIONS);
|
|
125
|
+
const prior = store.get(CURRENT);
|
|
126
|
+
prior.onsuccess = () => {
|
|
127
|
+
store.put(update(prior.result ?? EMPTY_DECLARATIONS), CURRENT);
|
|
128
|
+
};
|
|
129
|
+
});
|
|
47
130
|
}
|
|
48
131
|
function amendPending(database, locator, id, amendment) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
132
|
+
return transacted(database, PENDING_CHANGES, 'readwrite', (transaction) => {
|
|
133
|
+
const pending = transaction.objectStore(PENDING_CHANGES);
|
|
134
|
+
const request = pending.get([locator, id]);
|
|
135
|
+
request.onsuccess = () => {
|
|
136
|
+
const record = request.result;
|
|
137
|
+
if (record !== undefined)
|
|
138
|
+
pending.put({ ...record, ...amendment });
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function accepted({ conversation: _held, ...message }) {
|
|
143
|
+
return message;
|
|
144
|
+
}
|
|
145
|
+
function roomRecord(record) {
|
|
146
|
+
const { documents, binaries, revisions, fileIds, changes, written, ...room } = record;
|
|
147
|
+
return room;
|
|
148
|
+
}
|
|
149
|
+
function chatDraftRecord(record) {
|
|
150
|
+
return { ...record, revision: record.revision ?? '' };
|
|
151
|
+
}
|
|
152
|
+
function underTransfer(locator, transfer, after) {
|
|
153
|
+
return after === undefined
|
|
154
|
+
? IDBKeyRange.bound([locator, transfer], [locator, transfer, []])
|
|
155
|
+
: IDBKeyRange.bound([locator, transfer, after], [locator, transfer, []], true);
|
|
156
|
+
}
|
|
157
|
+
function underRoom(locator) {
|
|
158
|
+
return IDBKeyRange.bound([locator], [locator, []]);
|
|
159
|
+
}
|
|
160
|
+
async function readTransfer(store, locator, transfer, receive) {
|
|
161
|
+
let range = underTransfer(locator, transfer);
|
|
162
|
+
for (;;) {
|
|
163
|
+
const batch = await settled(store.getAll(range, READ_BATCH));
|
|
164
|
+
for (const record of batch)
|
|
165
|
+
receive(record);
|
|
166
|
+
if (batch.length < READ_BATCH)
|
|
167
|
+
return;
|
|
168
|
+
range = underTransfer(locator, transfer, batch[batch.length - 1].path);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function emptyLayer() {
|
|
172
|
+
return { documents: {}, binaries: {}, revisions: {}, fileIds: {}, changes: {}, written: {} };
|
|
173
|
+
}
|
|
174
|
+
async function readLayer(transaction, locator, transfer) {
|
|
175
|
+
const layer = emptyLayer();
|
|
176
|
+
const head = (held) => {
|
|
177
|
+
if (held.revision !== null)
|
|
178
|
+
layer.revisions[held.path] = held.revision;
|
|
179
|
+
if (held.fileId !== null)
|
|
180
|
+
layer.fileIds[held.path] = held.fileId;
|
|
181
|
+
if (held.change !== null)
|
|
182
|
+
layer.changes[held.path] = held.change;
|
|
183
|
+
};
|
|
184
|
+
await readTransfer(transaction.objectStore(DOCUMENTS), locator, transfer, (document) => {
|
|
185
|
+
layer.documents[document.path] = document.content;
|
|
186
|
+
if (document.written !== null)
|
|
187
|
+
layer.written[document.path] = document.written;
|
|
188
|
+
head(document);
|
|
189
|
+
});
|
|
190
|
+
await readTransfer(transaction.objectStore(BINARY_FILES), locator, transfer, (file) => {
|
|
191
|
+
layer.binaries[file.path] = file.binary;
|
|
192
|
+
head(file);
|
|
193
|
+
});
|
|
194
|
+
return layer;
|
|
195
|
+
}
|
|
196
|
+
function documentRecord(locator, transfer, layer, path) {
|
|
197
|
+
return {
|
|
198
|
+
locator,
|
|
199
|
+
transfer,
|
|
200
|
+
path,
|
|
201
|
+
content: layer.documents[path],
|
|
202
|
+
revision: layer.revisions[path] ?? null,
|
|
203
|
+
fileId: layer.fileIds[path] ?? null,
|
|
204
|
+
change: layer.changes[path] ?? null,
|
|
205
|
+
written: layer.written[path] ?? null,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function binaryFileRecord(locator, transfer, layer, path) {
|
|
209
|
+
return {
|
|
210
|
+
locator,
|
|
211
|
+
transfer,
|
|
212
|
+
path,
|
|
213
|
+
revision: layer.revisions[path] ?? null,
|
|
214
|
+
fileId: layer.fileIds[path] ?? null,
|
|
215
|
+
change: layer.changes[path] ?? null,
|
|
216
|
+
binary: layer.binaries[path],
|
|
56
217
|
};
|
|
57
|
-
return completed(transaction);
|
|
58
218
|
}
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
219
|
+
function* changedIn(next, base) {
|
|
220
|
+
if (base === null)
|
|
221
|
+
return new Set([...Object.keys(next.documents), ...Object.keys(next.binaries)]);
|
|
222
|
+
return new Set([
|
|
223
|
+
...(yield* differences(base.documents, next.documents)),
|
|
224
|
+
...(yield* differences(base.revisions, next.revisions)),
|
|
225
|
+
...(yield* differences(base.fileIds, next.fileIds)),
|
|
226
|
+
...(yield* differences(base.written, next.written)),
|
|
227
|
+
...(yield* differences(base.changes, next.changes, sameChange)),
|
|
228
|
+
...(yield* differences(base.binaries, next.binaries, sameBinary)),
|
|
229
|
+
]);
|
|
230
|
+
}
|
|
231
|
+
function writeLayer(transaction, locator, transfer, layer) {
|
|
232
|
+
const { next, base, paths } = layer;
|
|
233
|
+
const documents = transaction.objectStore(DOCUMENTS);
|
|
234
|
+
const files = transaction.objectStore(BINARY_FILES);
|
|
235
|
+
let last = null;
|
|
236
|
+
return inUnits(paths, (path) => {
|
|
237
|
+
if (next.documents[path] !== undefined) {
|
|
238
|
+
last = documents.put(documentRecord(locator, transfer, next, path));
|
|
239
|
+
}
|
|
240
|
+
else if (base?.documents[path] !== undefined) {
|
|
241
|
+
last = documents.delete([locator, transfer, path]);
|
|
242
|
+
}
|
|
243
|
+
if (next.binaries[path] !== undefined) {
|
|
244
|
+
last = files.put(binaryFileRecord(locator, transfer, next, path));
|
|
245
|
+
}
|
|
246
|
+
else if (base?.binaries[path] !== undefined) {
|
|
247
|
+
last = files.delete([locator, transfer, path]);
|
|
248
|
+
}
|
|
249
|
+
return ROW_WEIGHT + (next.documents[path]?.length ?? 0);
|
|
250
|
+
}, () => { }, () => {
|
|
251
|
+
const request = last;
|
|
252
|
+
last = null;
|
|
253
|
+
return request === null
|
|
254
|
+
? Promise.resolve()
|
|
255
|
+
: new Promise((resolve) => request.addEventListener('success', () => resolve()));
|
|
256
|
+
});
|
|
62
257
|
}
|
|
63
258
|
function removeRoomEntries(store, locator) {
|
|
64
259
|
const keys = store.getAllKeys();
|
|
@@ -69,179 +264,213 @@ function removeRoomEntries(store, locator) {
|
|
|
69
264
|
}
|
|
70
265
|
};
|
|
71
266
|
}
|
|
72
|
-
|
|
267
|
+
function removeDatabase(name) {
|
|
73
268
|
return new Promise((resolve, reject) => {
|
|
74
|
-
const removal = indexedDB.deleteDatabase(
|
|
269
|
+
const removal = indexedDB.deleteDatabase(name);
|
|
75
270
|
removal.onsuccess = () => resolve();
|
|
76
271
|
removal.onerror = () => reject(removal.error);
|
|
77
272
|
});
|
|
78
273
|
}
|
|
79
|
-
export async function
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
async all() {
|
|
103
|
-
const transaction = database.transaction([ROOMS, DOCUMENTS, BINARY_TARGETS, ATTACHMENTS], 'readonly');
|
|
104
|
-
const [rooms, documents, targets, attachments] = await Promise.all([
|
|
105
|
-
settled(transaction.objectStore(ROOMS).getAll()),
|
|
106
|
-
settled(transaction.objectStore(DOCUMENTS).getAll()),
|
|
107
|
-
settled(transaction.objectStore(BINARY_TARGETS).getAll()),
|
|
108
|
-
settled(transaction.objectStore(ATTACHMENTS).getAll()),
|
|
109
|
-
]);
|
|
110
|
-
const byRoom = new Map();
|
|
111
|
-
for (const { order, ...room } of rooms) {
|
|
112
|
-
byRoom.set(room.locator, {
|
|
113
|
-
...room,
|
|
114
|
-
documents: {},
|
|
115
|
-
revisions: {},
|
|
116
|
-
changes: {},
|
|
117
|
-
written: {},
|
|
118
|
-
binaryTargets: [],
|
|
119
|
-
attachments: [],
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
const head = (room, held) => {
|
|
123
|
-
if (held.revision !== null) {
|
|
124
|
-
room.revisions[held.path] = held.revision;
|
|
125
|
-
}
|
|
126
|
-
if (held.change !== null) {
|
|
127
|
-
room.changes[held.path] = held.change;
|
|
128
|
-
}
|
|
274
|
+
export async function authoredHolder() {
|
|
275
|
+
const authored = await openAuthored();
|
|
276
|
+
try {
|
|
277
|
+
const held = await transacted(authored, FACTS, 'readonly', (transaction) => settled(transaction.objectStore(FACTS).get(HOLDER)));
|
|
278
|
+
return typeof held === 'string' ? held : null;
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
authored.close();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
export async function authoredBy(holder) {
|
|
285
|
+
const authored = await openAuthored();
|
|
286
|
+
try {
|
|
287
|
+
let owned = true;
|
|
288
|
+
await transacted(authored, FACTS, 'readwrite', (transaction) => {
|
|
289
|
+
const facts = transaction.objectStore(FACTS);
|
|
290
|
+
const held = facts.get(HOLDER);
|
|
291
|
+
held.onsuccess = () => {
|
|
292
|
+
if (held.result === undefined)
|
|
293
|
+
facts.put(holder, HOLDER);
|
|
294
|
+
else
|
|
295
|
+
owned = held.result === holder;
|
|
129
296
|
};
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
change: next.changes[path] ?? null,
|
|
175
|
-
written: next.written[path] ?? null,
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
const targets = transaction.objectStore(BINARY_TARGETS);
|
|
179
|
-
const beforeTargets = new Set(previous?.binaryTargets ?? []);
|
|
180
|
-
const afterTargets = new Set(next.binaryTargets);
|
|
181
|
-
for (const path of beforeTargets) {
|
|
182
|
-
if (!afterTargets.has(path))
|
|
183
|
-
targets.delete([next.locator, path]);
|
|
297
|
+
});
|
|
298
|
+
return owned;
|
|
299
|
+
}
|
|
300
|
+
finally {
|
|
301
|
+
authored.close();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
export async function authoredBinding() {
|
|
305
|
+
const authored = await openAuthored();
|
|
306
|
+
try {
|
|
307
|
+
const held = await transacted(authored, FACTS, 'readonly', (transaction) => settled(transaction.objectStore(FACTS).get(BINDING)));
|
|
308
|
+
return typeof held === 'string' ? held : null;
|
|
309
|
+
}
|
|
310
|
+
finally {
|
|
311
|
+
authored.close();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
export async function bindAuthored(binding) {
|
|
315
|
+
const authored = await openAuthored();
|
|
316
|
+
try {
|
|
317
|
+
await transacted(authored, FACTS, 'readwrite', (transaction) => {
|
|
318
|
+
transaction.objectStore(FACTS).put(binding, BINDING);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
authored.close();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
export async function removeRuntimeStore(holder) {
|
|
326
|
+
await removeDatabase(DATABASE);
|
|
327
|
+
if (holder !== undefined && (await authoredBy(holder)))
|
|
328
|
+
return true;
|
|
329
|
+
await removeDatabase(AUTHORED);
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
export function openDatabase(name, version, upgrade) {
|
|
333
|
+
return new Promise((resolve, reject) => {
|
|
334
|
+
const opening = indexedDB.open(name, version);
|
|
335
|
+
opening.onupgradeneeded = (event) => upgrade(opening, event.oldVersion);
|
|
336
|
+
opening.onsuccess = () => resolve(opening.result);
|
|
337
|
+
opening.onerror = () => {
|
|
338
|
+
if (opening.error?.name !== 'VersionError') {
|
|
339
|
+
reject(opening.error);
|
|
340
|
+
return;
|
|
184
341
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
342
|
+
removeDatabase(name)
|
|
343
|
+
.then(() => openDatabase(name, version, upgrade))
|
|
344
|
+
.then(resolve, reject);
|
|
345
|
+
};
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
function openReplica() {
|
|
349
|
+
return openDatabase(DATABASE, STORE_SCHEMA, (opening, oldVersion) => {
|
|
350
|
+
const database = opening.result;
|
|
351
|
+
if (oldVersion < REPLICA_SHAPE) {
|
|
352
|
+
for (const name of [...database.objectStoreNames])
|
|
353
|
+
database.deleteObjectStore(name);
|
|
354
|
+
}
|
|
355
|
+
for (const [name, parameters] of REPLICA_STORES) {
|
|
356
|
+
if (database.objectStoreNames.contains(name))
|
|
357
|
+
continue;
|
|
358
|
+
database.createObjectStore(name, parameters);
|
|
359
|
+
}
|
|
360
|
+
const messages = opening.transaction.objectStore(MESSAGES);
|
|
361
|
+
for (const [name, keyPath] of MESSAGE_INDEXES) {
|
|
362
|
+
if (!messages.indexNames.contains(name))
|
|
363
|
+
messages.createIndex(name, keyPath);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
function openAuthored() {
|
|
368
|
+
return openDatabase(AUTHORED, AUTHORED_SCHEMA, (opening) => {
|
|
369
|
+
const database = opening.result;
|
|
370
|
+
for (const [name, parameters] of AUTHORED_STORES) {
|
|
371
|
+
if (!database.objectStoreNames.contains(name))
|
|
372
|
+
database.createObjectStore(name, parameters);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
export async function openRuntimeStore(fenced) {
|
|
377
|
+
const authored = await openAuthored();
|
|
378
|
+
const database = await openReplica().catch((error) => {
|
|
379
|
+
authored.close();
|
|
380
|
+
throw error;
|
|
381
|
+
});
|
|
382
|
+
return {
|
|
383
|
+
all() {
|
|
384
|
+
return transacted(database, [ROOMS, DOCUMENTS, BINARY_FILES], 'readonly', async (transaction) => {
|
|
385
|
+
const rooms = await settled(transaction.objectStore(ROOMS).getAll());
|
|
386
|
+
const records = [];
|
|
387
|
+
for (const room of rooms.filter((held) => !fenced(held.locator))) {
|
|
388
|
+
records.push({
|
|
389
|
+
...room,
|
|
390
|
+
...(await readLayer(transaction, room.locator, room.transfer)),
|
|
194
391
|
});
|
|
195
392
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
for (const attachment of next.attachments) {
|
|
205
|
-
if (held.get(attachment.reference) === attachment)
|
|
206
|
-
continue;
|
|
207
|
-
attachments.put({ ...attachment, locator: next.locator });
|
|
208
|
-
}
|
|
209
|
-
if (previousQueue !== undefined) {
|
|
393
|
+
return records;
|
|
394
|
+
});
|
|
395
|
+
},
|
|
396
|
+
async keepAuthored({ locator, queue, founded, saved, roomDrafts, consumedRoomDrafts }) {
|
|
397
|
+
if (fenced(locator))
|
|
398
|
+
return;
|
|
399
|
+
await transacted(authored, [QUEUES, PENDING_CHANGES, ROOM_DRAFTS], 'readwrite', (transaction) => {
|
|
400
|
+
const order = transaction.objectStore(QUEUES);
|
|
210
401
|
const pending = transaction.objectStore(PENDING_CHANGES);
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
402
|
+
const held = order.get(locator);
|
|
403
|
+
held.onsuccess = () => {
|
|
404
|
+
const before = new Set(held.result?.order ?? []);
|
|
405
|
+
const after = new Set(queue.map((batch) => batch.id));
|
|
406
|
+
if (queue.length === 0)
|
|
407
|
+
order.delete(locator);
|
|
408
|
+
else
|
|
409
|
+
order.put({ locator, order: [...after] });
|
|
410
|
+
for (const id of before) {
|
|
411
|
+
if (!after.has(id))
|
|
412
|
+
pending.delete([locator, id]);
|
|
413
|
+
}
|
|
414
|
+
for (const batch of queue) {
|
|
415
|
+
if (!before.has(batch.id)) {
|
|
416
|
+
pending.put({
|
|
417
|
+
locator,
|
|
418
|
+
id: batch.id,
|
|
419
|
+
batch,
|
|
420
|
+
drafts: saved?.get(batch.id) ?? [],
|
|
421
|
+
submission: { state: 'waiting' },
|
|
422
|
+
lost: false,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
227
425
|
}
|
|
426
|
+
for (const batch of founded ?? []) {
|
|
427
|
+
if (!before.has(batch.id))
|
|
428
|
+
continue;
|
|
429
|
+
const stored = pending.get([locator, batch.id]);
|
|
430
|
+
stored.onsuccess = () => {
|
|
431
|
+
const record = stored.result;
|
|
432
|
+
if (record !== undefined)
|
|
433
|
+
pending.put({ ...record, batch });
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
const draftStore = transaction.objectStore(ROOM_DRAFTS);
|
|
438
|
+
for (const path of consumedRoomDrafts ?? [])
|
|
439
|
+
draftStore.delete([locator, path]);
|
|
440
|
+
for (const record of roomDrafts ?? [])
|
|
441
|
+
draftStore.put(record);
|
|
442
|
+
});
|
|
443
|
+
},
|
|
444
|
+
async write({ next, previous, stored }) {
|
|
445
|
+
if (next === previous || fenced(next.locator))
|
|
446
|
+
return;
|
|
447
|
+
const moved = previous?.transfer !== next.transfer;
|
|
448
|
+
const base = moved ? (stored ?? null) : previous;
|
|
449
|
+
const paths = await stepped(changedIn(next, base));
|
|
450
|
+
if (fenced(next.locator))
|
|
451
|
+
return;
|
|
452
|
+
await transacted(database, [ROOMS, DOCUMENTS, BINARY_FILES, TRANSFERS], 'readwrite', (transaction) => {
|
|
453
|
+
transaction.objectStore(ROOMS).put(roomRecord(next));
|
|
454
|
+
if (moved && previous !== null) {
|
|
455
|
+
const range = underTransfer(previous.locator, previous.transfer);
|
|
456
|
+
transaction.objectStore(DOCUMENTS).delete(range);
|
|
457
|
+
transaction.objectStore(BINARY_FILES).delete(range);
|
|
228
458
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
for (const record of roomDrafts ?? [])
|
|
234
|
-
draftStore.put(record);
|
|
235
|
-
await completed(transaction);
|
|
459
|
+
if (moved)
|
|
460
|
+
transaction.objectStore(TRANSFERS).delete(next.locator);
|
|
461
|
+
return writeLayer(transaction, next.locator, next.transfer, { next, base, paths });
|
|
462
|
+
});
|
|
236
463
|
},
|
|
237
464
|
async revokeModels(records) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
465
|
+
await transacted(database, ROOMS, 'readwrite', (transaction) => {
|
|
466
|
+
const rooms = transaction.objectStore(ROOMS);
|
|
467
|
+
const revoked = { status: 'unavailable', reason: 'revoked' };
|
|
468
|
+
for (const record of records) {
|
|
469
|
+
if (fenced(record.locator))
|
|
470
|
+
continue;
|
|
471
|
+
rooms.put(roomRecord({ ...record, model: revoked, selectionModel: revoked }));
|
|
472
|
+
}
|
|
473
|
+
});
|
|
245
474
|
},
|
|
246
475
|
async lastCompleted() {
|
|
247
476
|
return (await declaration(database)).lastCompleted;
|
|
@@ -262,134 +491,326 @@ export async function openRuntimeStore() {
|
|
|
262
491
|
capabilities,
|
|
263
492
|
}));
|
|
264
493
|
},
|
|
494
|
+
async member() {
|
|
495
|
+
const held = await transacted(database, DECLARATIONS, 'readonly', (transaction) => settled(transaction.objectStore(DECLARATIONS).get(MEMBER)));
|
|
496
|
+
return held ?? null;
|
|
497
|
+
},
|
|
498
|
+
async keepMember(member) {
|
|
499
|
+
await transacted(database, DECLARATIONS, 'readwrite', (transaction) => {
|
|
500
|
+
transaction.objectStore(DECLARATIONS).put(member, MEMBER);
|
|
501
|
+
});
|
|
502
|
+
},
|
|
503
|
+
async targets() {
|
|
504
|
+
const held = await transacted(database, DECLARATIONS, 'readonly', (transaction) => settled(transaction.objectStore(DECLARATIONS).get(TARGETS)));
|
|
505
|
+
return held ?? {};
|
|
506
|
+
},
|
|
507
|
+
async keepTargets(targets) {
|
|
508
|
+
await transacted(database, DECLARATIONS, 'readwrite', (transaction) => {
|
|
509
|
+
transaction.objectStore(DECLARATIONS).put(targets, TARGETS);
|
|
510
|
+
});
|
|
511
|
+
},
|
|
265
512
|
async queues() {
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
settled(transaction.objectStore(ROOMS).getAll()),
|
|
513
|
+
const [queues, pending] = await transacted(authored, [QUEUES, PENDING_CHANGES], 'readonly', (transaction) => Promise.all([
|
|
514
|
+
settled(transaction.objectStore(QUEUES).getAll()),
|
|
269
515
|
settled(transaction.objectStore(PENDING_CHANGES).getAll()),
|
|
270
|
-
]);
|
|
516
|
+
]));
|
|
271
517
|
const byId = new Map(pending.map((record) => [`${record.locator}\0${record.id}`, record]));
|
|
272
|
-
return
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
518
|
+
return queues
|
|
519
|
+
.filter((queue) => !fenced(queue.locator))
|
|
520
|
+
.map((queue) => ({
|
|
521
|
+
locator: queue.locator,
|
|
522
|
+
pending: queue.order.map((id) => {
|
|
523
|
+
const record = byId.get(`${queue.locator}\0${id}`);
|
|
276
524
|
if (record === undefined)
|
|
277
|
-
throw new Error(`Missing Pending Change ${
|
|
525
|
+
throw new Error(`Missing Pending Change ${queue.locator}/${id}`);
|
|
278
526
|
const { batch, drafts, submission, lost, upload, accepted } = record;
|
|
279
527
|
return { batch, state: { drafts, submission, lost, upload, accepted } };
|
|
280
528
|
}),
|
|
281
529
|
}));
|
|
282
530
|
},
|
|
283
|
-
submit(locator, id, submission, lost) {
|
|
284
|
-
|
|
531
|
+
async submit(locator, id, submission, lost) {
|
|
532
|
+
if (!fenced(locator))
|
|
533
|
+
await amendPending(authored, locator, id, { submission, lost });
|
|
285
534
|
},
|
|
286
|
-
accept(locator, id, roomVersion) {
|
|
287
|
-
|
|
535
|
+
async accept(locator, id, roomVersion) {
|
|
536
|
+
if (!fenced(locator))
|
|
537
|
+
await amendPending(authored, locator, id, { accepted: roomVersion });
|
|
288
538
|
},
|
|
289
|
-
keepUpload(locator, id, upload) {
|
|
290
|
-
|
|
539
|
+
async keepUpload(locator, id, upload) {
|
|
540
|
+
if (!fenced(locator))
|
|
541
|
+
await amendPending(authored, locator, id, { upload });
|
|
291
542
|
},
|
|
292
543
|
async roomDrafts() {
|
|
293
|
-
const
|
|
294
|
-
return
|
|
544
|
+
const held = await transacted(authored, ROOM_DRAFTS, 'readonly', (transaction) => settled(transaction.objectStore(ROOM_DRAFTS).getAll()));
|
|
545
|
+
return held.filter((record) => !fenced(record.locator));
|
|
295
546
|
},
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
return settled(transaction.objectStore(PRESENTATION).getAll());
|
|
547
|
+
presentation() {
|
|
548
|
+
return transacted(database, PRESENTATION, 'readonly', (transaction) => settled(transaction.objectStore(PRESENTATION).getAll()));
|
|
299
549
|
},
|
|
300
550
|
async present(record) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
},
|
|
305
|
-
async conversations() {
|
|
306
|
-
const transaction = database.transaction([CONVERSATIONS, MESSAGES], 'readonly');
|
|
307
|
-
const [reads, messages] = await Promise.all([
|
|
308
|
-
settled(transaction.objectStore(CONVERSATIONS).getAll()),
|
|
309
|
-
settled(transaction.objectStore(MESSAGES).getAll()),
|
|
310
|
-
]);
|
|
311
|
-
const held = new Map();
|
|
312
|
-
for (const { conversation, read } of reads) {
|
|
313
|
-
held.set(conversation, { conversation, read, messages: [] });
|
|
314
|
-
}
|
|
315
|
-
for (const { conversation, ...message } of messages) {
|
|
316
|
-
const entry = held.get(conversation) ?? { conversation, read: undefined, messages: [] };
|
|
317
|
-
entry.messages.push(message);
|
|
318
|
-
held.set(conversation, entry);
|
|
319
|
-
}
|
|
320
|
-
return [...held.values()];
|
|
551
|
+
await transacted(database, PRESENTATION, 'readwrite', (transaction) => {
|
|
552
|
+
transaction.objectStore(PRESENTATION).put(record);
|
|
553
|
+
});
|
|
321
554
|
},
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
transaction
|
|
330
|
-
|
|
331
|
-
.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
555
|
+
conversations() {
|
|
556
|
+
return transacted(database, [CONVERSATIONS, MESSAGES], 'readonly', async (transaction) => {
|
|
557
|
+
const records = await settled(transaction.objectStore(CONVERSATIONS).getAll());
|
|
558
|
+
const held = new Map(records.map(({ conversation, read, partial }) => [
|
|
559
|
+
conversation,
|
|
560
|
+
{ conversation, read, partial: partial === true },
|
|
561
|
+
]));
|
|
562
|
+
const keys = transaction.objectStore(MESSAGES).index('conversation');
|
|
563
|
+
await new Promise((resolve, reject) => {
|
|
564
|
+
const cursor = keys.openKeyCursor(null, 'nextunique');
|
|
565
|
+
cursor.onerror = () => reject(cursor.error);
|
|
566
|
+
cursor.onsuccess = () => {
|
|
567
|
+
const at = cursor.result;
|
|
568
|
+
if (at === null) {
|
|
569
|
+
resolve();
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const conversation = at.key;
|
|
573
|
+
if (!held.has(conversation)) {
|
|
574
|
+
held.set(conversation, { conversation, read: undefined, partial: true });
|
|
575
|
+
}
|
|
576
|
+
at.continue();
|
|
577
|
+
};
|
|
578
|
+
});
|
|
579
|
+
return [...held.values()];
|
|
580
|
+
});
|
|
581
|
+
},
|
|
582
|
+
async keepMessages({ conversation, messages, read }) {
|
|
583
|
+
await transacted(database, [CONVERSATIONS, MESSAGES], 'readwrite', (transaction) => {
|
|
584
|
+
const stored = transaction.objectStore(MESSAGES);
|
|
585
|
+
for (const message of messages) {
|
|
586
|
+
stored.put({ ...message, conversation });
|
|
587
|
+
}
|
|
588
|
+
if (read !== undefined) {
|
|
589
|
+
transaction.objectStore(CONVERSATIONS).put({
|
|
590
|
+
conversation,
|
|
591
|
+
read: read.at,
|
|
592
|
+
...(read.partial ? { partial: true } : {}),
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
},
|
|
597
|
+
messageWindow(conversation, limit, before) {
|
|
598
|
+
return transacted(database, MESSAGES, 'readonly', async (transaction) => {
|
|
599
|
+
const index = transaction.objectStore(MESSAGES).index('position');
|
|
600
|
+
const range = IDBKeyRange.bound([conversation], [conversation, before?.[0] ?? []]);
|
|
601
|
+
const latest = [];
|
|
602
|
+
let more = false;
|
|
603
|
+
await new Promise((resolve, reject) => {
|
|
604
|
+
const cursor = index.openCursor(range, 'prev');
|
|
605
|
+
cursor.onerror = () => reject(cursor.error);
|
|
606
|
+
cursor.onsuccess = () => {
|
|
607
|
+
const at = cursor.result;
|
|
608
|
+
if (at === null) {
|
|
609
|
+
resolve();
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
const record = at.value;
|
|
613
|
+
if (!precedes(record, before)) {
|
|
614
|
+
at.continue();
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (latest.length === limit) {
|
|
618
|
+
more = true;
|
|
619
|
+
resolve();
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
latest.push(accepted(record));
|
|
623
|
+
at.continue();
|
|
624
|
+
};
|
|
625
|
+
});
|
|
626
|
+
return { messages: latest.reverse(), more };
|
|
627
|
+
});
|
|
628
|
+
},
|
|
629
|
+
messageCount(conversation) {
|
|
630
|
+
return transacted(database, MESSAGES, 'readonly', (transaction) => settled(transaction.objectStore(MESSAGES).index('conversation').count(conversation)));
|
|
631
|
+
},
|
|
632
|
+
async messagesAfter(conversation, identity) {
|
|
633
|
+
const range = identity === null
|
|
634
|
+
? IDBKeyRange.bound([conversation], [conversation, []])
|
|
635
|
+
: IDBKeyRange.bound([conversation, identity], [conversation, []], true);
|
|
636
|
+
const held = await transacted(database, MESSAGES, 'readonly', (transaction) => settled(transaction.objectStore(MESSAGES).getAll(range, READ_BATCH)));
|
|
637
|
+
return held.map(accepted);
|
|
638
|
+
},
|
|
639
|
+
async messageIdentities(conversation) {
|
|
640
|
+
const keys = await transacted(database, MESSAGES, 'readonly', (transaction) => settled(transaction.objectStore(MESSAGES).index('conversation').getAllKeys(conversation)));
|
|
641
|
+
return keys.map((key) => key[1]);
|
|
642
|
+
},
|
|
643
|
+
async turnArrived(conversation, turn) {
|
|
644
|
+
const held = await transacted(database, MESSAGES, 'readonly', (transaction) => settled(transaction.objectStore(MESSAGES).index('turn').count([conversation, turn])));
|
|
645
|
+
return held > 0;
|
|
336
646
|
},
|
|
337
647
|
async dropConversations(conversations) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
648
|
+
await transacted(authored, CHAT_DRAFTS, 'readwrite', (drafts) => {
|
|
649
|
+
for (const conversation of conversations)
|
|
650
|
+
drafts.objectStore(CHAT_DRAFTS).delete(conversation);
|
|
651
|
+
});
|
|
652
|
+
await transacted(database, [CONVERSATIONS, MESSAGES], 'readwrite', (transaction) => {
|
|
653
|
+
const messages = transaction.objectStore(MESSAGES);
|
|
654
|
+
for (const conversation of conversations) {
|
|
655
|
+
transaction.objectStore(CONVERSATIONS).delete(conversation);
|
|
656
|
+
const keys = messages.index('conversation').getAllKeys(conversation);
|
|
657
|
+
keys.onsuccess = () => {
|
|
658
|
+
for (const key of keys.result)
|
|
659
|
+
messages.delete(key);
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
});
|
|
350
663
|
},
|
|
351
664
|
async chatDrafts() {
|
|
352
|
-
const
|
|
353
|
-
return
|
|
665
|
+
const held = await transacted(authored, CHAT_DRAFTS, 'readonly', (transaction) => settled(transaction.objectStore(CHAT_DRAFTS).getAll()));
|
|
666
|
+
return held.map(chatDraftRecord);
|
|
354
667
|
},
|
|
355
|
-
async
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
668
|
+
async keepChatDrafts(records) {
|
|
669
|
+
await transacted(authored, CHAT_DRAFTS, 'readwrite', (transaction) => {
|
|
670
|
+
for (const record of records)
|
|
671
|
+
transaction.objectStore(CHAT_DRAFTS).put(record);
|
|
672
|
+
});
|
|
359
673
|
},
|
|
360
674
|
async dropChatDraft(conversation) {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
675
|
+
await transacted(authored, CHAT_DRAFTS, 'readwrite', (transaction) => {
|
|
676
|
+
transaction.objectStore(CHAT_DRAFTS).delete(conversation);
|
|
677
|
+
});
|
|
678
|
+
},
|
|
679
|
+
calls() {
|
|
680
|
+
return transacted(authored, CALLS, 'readonly', (transaction) => settled(transaction.objectStore(CALLS).getAll()));
|
|
681
|
+
},
|
|
682
|
+
async keepCall(record) {
|
|
683
|
+
await transacted(authored, CALLS, 'readwrite', (transaction) => {
|
|
684
|
+
transaction.objectStore(CALLS).put(record);
|
|
685
|
+
});
|
|
686
|
+
},
|
|
687
|
+
async dropCall(operation) {
|
|
688
|
+
await transacted(authored, CALLS, 'readwrite', (transaction) => {
|
|
689
|
+
transaction.objectStore(CALLS).delete(operation);
|
|
690
|
+
});
|
|
364
691
|
},
|
|
365
692
|
async keepRoomDraft(record) {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
await
|
|
693
|
+
if (fenced(record.locator))
|
|
694
|
+
return;
|
|
695
|
+
await transacted(authored, ROOM_DRAFTS, 'readwrite', (transaction) => {
|
|
696
|
+
transaction.objectStore(ROOM_DRAFTS).put(record);
|
|
697
|
+
});
|
|
369
698
|
},
|
|
370
699
|
async discardRoomDraft(locator, path) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
await
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
700
|
+
if (fenced(locator))
|
|
701
|
+
return;
|
|
702
|
+
await transacted(authored, ROOM_DRAFTS, 'readwrite', (transaction) => {
|
|
703
|
+
transaction.objectStore(ROOM_DRAFTS).delete([locator, path]);
|
|
704
|
+
});
|
|
705
|
+
},
|
|
706
|
+
async fences() {
|
|
707
|
+
const held = await transacted(authored, FENCES, 'readonly', (transaction) => settled(transaction.objectStore(FENCES).getAll()));
|
|
708
|
+
return held.map((fence) => fence.locator);
|
|
709
|
+
},
|
|
710
|
+
async erase(locators) {
|
|
711
|
+
await transacted(authored, [FENCES, QUEUES, PENDING_CHANGES, ROOM_DRAFTS], 'readwrite', (work) => {
|
|
712
|
+
for (const locator of locators) {
|
|
713
|
+
work.objectStore(FENCES).put({ locator });
|
|
714
|
+
work.objectStore(QUEUES).delete(locator);
|
|
715
|
+
for (const name of [PENDING_CHANGES, ROOM_DRAFTS]) {
|
|
716
|
+
removeRoomEntries(work.objectStore(name), locator);
|
|
717
|
+
}
|
|
381
718
|
}
|
|
382
|
-
}
|
|
383
|
-
await
|
|
719
|
+
});
|
|
720
|
+
await transacted(database, [ROOMS, DOCUMENTS, BINARY_FILES, TRANSFERS], 'readwrite', (transaction) => {
|
|
721
|
+
for (const locator of locators) {
|
|
722
|
+
transaction.objectStore(ROOMS).delete(locator);
|
|
723
|
+
transaction.objectStore(TRANSFERS).delete(locator);
|
|
724
|
+
transaction.objectStore(DOCUMENTS).delete(underRoom(locator));
|
|
725
|
+
transaction.objectStore(BINARY_FILES).delete(underRoom(locator));
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
await transacted(authored, FENCES, 'readwrite', (fences) => {
|
|
729
|
+
for (const locator of locators)
|
|
730
|
+
fences.objectStore(FENCES).delete(locator);
|
|
731
|
+
});
|
|
732
|
+
},
|
|
733
|
+
async discard(locator) {
|
|
734
|
+
await transacted(authored, [QUEUES, PENDING_CHANGES], 'readwrite', (transaction) => {
|
|
735
|
+
transaction.objectStore(QUEUES).delete(locator);
|
|
736
|
+
removeRoomEntries(transaction.objectStore(PENDING_CHANGES), locator);
|
|
737
|
+
});
|
|
738
|
+
},
|
|
739
|
+
async transfers() {
|
|
740
|
+
const held = await transacted(database, TRANSFERS, 'readonly', (transaction) => settled(transaction.objectStore(TRANSFERS).getAll()));
|
|
741
|
+
return held.filter((record) => !fenced(record.locator));
|
|
742
|
+
},
|
|
743
|
+
staged(record) {
|
|
744
|
+
return transacted(database, [DOCUMENTS, BINARY_FILES], 'readonly', (transaction) => readLayer(transaction, record.locator, record.transfer));
|
|
745
|
+
},
|
|
746
|
+
async stage(record, page) {
|
|
747
|
+
if (fenced(record.locator))
|
|
748
|
+
return;
|
|
749
|
+
const paths = await stepped(changedIn(page, null));
|
|
750
|
+
if (fenced(record.locator))
|
|
751
|
+
return;
|
|
752
|
+
await transacted(database, [TRANSFERS, DOCUMENTS, BINARY_FILES], 'readwrite', (transaction) => {
|
|
753
|
+
transaction.objectStore(TRANSFERS).put(record);
|
|
754
|
+
return writeLayer(transaction, record.locator, record.transfer, {
|
|
755
|
+
next: page,
|
|
756
|
+
base: null,
|
|
757
|
+
paths,
|
|
758
|
+
});
|
|
759
|
+
});
|
|
760
|
+
},
|
|
761
|
+
async abandon(locator) {
|
|
762
|
+
if (fenced(locator))
|
|
763
|
+
return;
|
|
764
|
+
await transacted(database, [TRANSFERS, DOCUMENTS, BINARY_FILES], 'readwrite', (transaction) => {
|
|
765
|
+
const transfers = transaction.objectStore(TRANSFERS);
|
|
766
|
+
const held = transfers.get(locator);
|
|
767
|
+
held.onsuccess = () => {
|
|
768
|
+
const record = held.result;
|
|
769
|
+
if (record === undefined)
|
|
770
|
+
return;
|
|
771
|
+
transfers.delete(locator);
|
|
772
|
+
const range = underTransfer(locator, record.transfer);
|
|
773
|
+
transaction.objectStore(DOCUMENTS).delete(range);
|
|
774
|
+
transaction.objectStore(BINARY_FILES).delete(range);
|
|
775
|
+
};
|
|
776
|
+
});
|
|
777
|
+
},
|
|
778
|
+
receivedHeads() {
|
|
779
|
+
return transacted(database, RECEIVED, 'readonly', (transaction) => settled(transaction.objectStore(RECEIVED).getAll()));
|
|
780
|
+
},
|
|
781
|
+
async receivedParts(key, count) {
|
|
782
|
+
const kept = await transacted(database, RECEIVED_PARTS, 'readonly', (transaction) => settled(transaction
|
|
783
|
+
.objectStore(RECEIVED_PARTS)
|
|
784
|
+
.getAll(IDBKeyRange.bound([key, 0], [key, count], false, true))));
|
|
785
|
+
return kept.map((part) => part.value);
|
|
786
|
+
},
|
|
787
|
+
async keepReceived(key, head, part) {
|
|
788
|
+
await transacted(database, [RECEIVED, RECEIVED_PARTS], 'readwrite', (transaction) => {
|
|
789
|
+
transaction.objectStore(RECEIVED).put({ key, head });
|
|
790
|
+
if (part !== undefined)
|
|
791
|
+
transaction.objectStore(RECEIVED_PARTS).put({ key, ...part });
|
|
792
|
+
});
|
|
793
|
+
},
|
|
794
|
+
async dropReceived(keys) {
|
|
795
|
+
await transacted(database, [RECEIVED, RECEIVED_PARTS], 'readwrite', (transaction) => {
|
|
796
|
+
for (const key of keys) {
|
|
797
|
+
transaction.objectStore(RECEIVED).delete(key);
|
|
798
|
+
transaction.objectStore(RECEIVED_PARTS).delete(IDBKeyRange.bound([key], [key, []]));
|
|
799
|
+
}
|
|
800
|
+
});
|
|
384
801
|
},
|
|
385
802
|
async generation() {
|
|
386
|
-
|
|
803
|
+
const held = await transacted(authored, FACTS, 'readonly', (transaction) => settled(transaction.objectStore(FACTS).get(GENERATION)));
|
|
804
|
+
return held ?? null;
|
|
387
805
|
},
|
|
388
|
-
rememberGeneration(value) {
|
|
389
|
-
|
|
806
|
+
async rememberGeneration(value) {
|
|
807
|
+
await transacted(authored, FACTS, 'readwrite', (transaction) => {
|
|
808
|
+
transaction.objectStore(FACTS).put(value, GENERATION);
|
|
809
|
+
});
|
|
390
810
|
},
|
|
391
811
|
close() {
|
|
392
|
-
database
|
|
812
|
+
for (const held of [authored, database])
|
|
813
|
+
void idle(held).then(() => held.close());
|
|
393
814
|
},
|
|
394
815
|
};
|
|
395
816
|
}
|