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