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