@agentshouse/mdruntime 0.1.0-cf28918c2bf91 → 0.1.0-cf53a3b60b194
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/README.md +24 -11
- package/dist/adapter.d.ts +188 -48
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +1 -9
- package/dist/attached-model.d.ts +15 -0
- package/dist/attached-model.d.ts.map +1 -0
- package/dist/attached-model.js +64 -0
- 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 +18 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +44 -7
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +224 -45
- 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 +9 -14
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +11 -56
- package/dist/change.d.ts +45 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +34 -0
- package/dist/client.d.ts +37 -15
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +159 -29
- package/dist/collection.d.ts +14 -13
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +66 -54
- package/dist/compile.d.ts +16 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +765 -0
- package/dist/connection.d.ts +68 -28
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +4091 -978
- package/dist/conversation.d.ts +57 -16
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +789 -243
- 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 +5 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +5 -0
- package/dist/entry.d.ts +19 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +151 -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 -19
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +7 -122
- package/dist/fulltext.d.ts +27 -0
- package/dist/fulltext.d.ts.map +1 -0
- package/dist/fulltext.js +368 -0
- package/dist/host.d.ts +21 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +163 -47
- package/dist/identity.d.ts +4 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +16 -1
- package/dist/index.d.ts +19 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/live.d.ts +22 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +124 -0
- package/dist/media.d.ts +8 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +208 -0
- package/dist/memory.d.ts +22 -8
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +207 -47
- package/dist/messages.d.ts +28 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +90 -0
- package/dist/model.d.ts +23 -14
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +22 -4
- 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 +21 -1
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +128 -17
- package/dist/open.d.ts +2 -0
- package/dist/open.d.ts.map +1 -0
- package/dist/open.js +1 -0
- 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 +73 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +81 -1
- package/dist/overlay.d.ts +15 -5
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +136 -180
- package/dist/paths.d.ts +1 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +4 -16
- package/dist/query.d.ts +78 -7
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +126 -43
- package/dist/reach.d.ts +54 -0
- package/dist/reach.d.ts.map +1 -0
- package/dist/reach.js +448 -0
- package/dist/react.d.ts +43 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +54 -0
- package/dist/readiness.d.ts +64 -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 +100 -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 +19 -7
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +125 -53
- package/dist/replica.d.ts +59 -33
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +322 -156
- package/dist/room-draft.d.ts +44 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +191 -40
- package/dist/rows.d.ts +39 -34
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +195 -81
- package/dist/save.d.ts +74 -6
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +898 -181
- package/dist/search.d.ts +41 -29
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +37 -105
- 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 +16 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +431 -0
- package/dist/status.d.ts +11 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -9
- package/dist/store.d.ts +132 -59
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +379 -344
- package/dist/stored.d.ts +154 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +578 -0
- package/dist/tables.d.ts +362 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +627 -0
- package/dist/turn.d.ts +9 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +75 -0
- 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 +194 -19
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +90 -61
- package/dist/words.d.ts +19 -0
- package/dist/words.d.ts.map +1 -0
- package/dist/words.js +156 -0
- package/package.json +33 -10
- package/AGENTS.md +0 -207
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
- package/dist/read-side.d.ts +0 -14
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -371
package/dist/tables.js
ADDED
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
import { answerQuery } from './compile.js';
|
|
2
|
+
import { searched, SEARCH_TABLES } from './fulltext.js';
|
|
3
|
+
import { droppedMessages, forgottenMessages, heldConversations, keptMessages, messageCount, messageWindow, MESSAGE_TABLES, turnArrived, } from './messages.js';
|
|
4
|
+
import { abandon, release, commit, counts, ended, entries, erase, fenceRooms, files, folders, opened, rederive, referrers, refreshing, REPLICA_TABLES, keepParts, stage, transferPart, typed, typedWithin, unrefresh, walk, } from './stored.js';
|
|
5
|
+
const STATEMENTS_HELD = 256;
|
|
6
|
+
export function sqliteConnection(engine, database) {
|
|
7
|
+
const decoder = new TextDecoder();
|
|
8
|
+
const held = new Map();
|
|
9
|
+
function prepared(sql) {
|
|
10
|
+
if (sql.includes(';'))
|
|
11
|
+
return null;
|
|
12
|
+
const found = held.get(sql);
|
|
13
|
+
held.delete(sql);
|
|
14
|
+
const statement = found ?? database.prepare(sql);
|
|
15
|
+
held.set(sql, statement);
|
|
16
|
+
if (held.size > STATEMENTS_HELD) {
|
|
17
|
+
const [oldest, evicted] = held.entries().next().value;
|
|
18
|
+
held.delete(oldest);
|
|
19
|
+
evicted.finalize();
|
|
20
|
+
}
|
|
21
|
+
return statement;
|
|
22
|
+
}
|
|
23
|
+
function stepped(statement, bind, each) {
|
|
24
|
+
try {
|
|
25
|
+
if (bind.length > 0 && statement.parameterCount > 0)
|
|
26
|
+
statement.bind(bind);
|
|
27
|
+
while (statement.step())
|
|
28
|
+
each();
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
statement.resetNoThrow(true);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
run(sql, bind = []) {
|
|
36
|
+
const statement = prepared(sql);
|
|
37
|
+
if (statement === null)
|
|
38
|
+
database.exec({ sql, bind });
|
|
39
|
+
else
|
|
40
|
+
stepped(statement, bind, () => { });
|
|
41
|
+
},
|
|
42
|
+
rows(sql, bind = []) {
|
|
43
|
+
const statement = prepared(sql);
|
|
44
|
+
if (statement === null) {
|
|
45
|
+
return database.exec({ sql, bind, rowMode: 'object', returnValue: 'resultRows' });
|
|
46
|
+
}
|
|
47
|
+
const names = statement.getColumnNames([]);
|
|
48
|
+
const rows = [];
|
|
49
|
+
stepped(statement, bind, () => {
|
|
50
|
+
const values = statement.get([]);
|
|
51
|
+
const row = Object.create(null);
|
|
52
|
+
for (const [index, name] of names.entries())
|
|
53
|
+
row[name] = values[index] ?? null;
|
|
54
|
+
rows.push(row);
|
|
55
|
+
});
|
|
56
|
+
return rows;
|
|
57
|
+
},
|
|
58
|
+
collate(name, compare) {
|
|
59
|
+
const answered = engine.capi.sqlite3_create_collation_v2(database.pointer, name, engine.capi.SQLITE_UTF8, 0, (_argument, leftLength, left, rightLength, right) => {
|
|
60
|
+
const heap = engine.wasm.heap8u();
|
|
61
|
+
return Math.sign(compare(decoder.decode(heap.subarray(left, left + leftLength)), decoder.decode(heap.subarray(right, right + rightLength))));
|
|
62
|
+
}, 0);
|
|
63
|
+
if (answered !== 0)
|
|
64
|
+
throw new Error(`the database refused the collation ${name}`);
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export const FILE_PART_BYTES = 1_048_576;
|
|
69
|
+
const PROBE_BYTES = 4 * FILE_PART_BYTES;
|
|
70
|
+
const TABLES = [
|
|
71
|
+
['authored_facts', 'name TEXT PRIMARY KEY, value TEXT NOT NULL'],
|
|
72
|
+
['authored_queues', 'locator TEXT PRIMARY KEY, ids TEXT NOT NULL'],
|
|
73
|
+
[
|
|
74
|
+
'authored_pending',
|
|
75
|
+
'locator TEXT NOT NULL, id TEXT NOT NULL, batch TEXT NOT NULL, state TEXT NOT NULL, files TEXT NOT NULL, PRIMARY KEY (locator, id)',
|
|
76
|
+
],
|
|
77
|
+
[
|
|
78
|
+
'authored_room_drafts',
|
|
79
|
+
'locator TEXT NOT NULL, path TEXT NOT NULL, record TEXT NOT NULL, files TEXT NOT NULL, PRIMARY KEY (locator, path)',
|
|
80
|
+
],
|
|
81
|
+
['authored_chat_drafts', 'conversation TEXT PRIMARY KEY, record TEXT NOT NULL'],
|
|
82
|
+
['authored_calls', 'operation TEXT PRIMARY KEY, record TEXT NOT NULL, files TEXT NOT NULL'],
|
|
83
|
+
['authored_fences', 'locator TEXT PRIMARY KEY'],
|
|
84
|
+
];
|
|
85
|
+
export const RUNTIME_TABLES = [
|
|
86
|
+
...TABLES.map(([name, columns]) => `CREATE TABLE ${name} (${columns}) WITHOUT ROWID`),
|
|
87
|
+
'CREATE TABLE file_parts (key TEXT NOT NULL, part INTEGER NOT NULL, bytes BLOB NOT NULL, PRIMARY KEY (key, part))',
|
|
88
|
+
'CREATE TABLE opened_files (key TEXT PRIMARY KEY, locator TEXT NOT NULL, path TEXT NOT NULL, revision TEXT NOT NULL, media_type TEXT NOT NULL, sha256 TEXT, byte_length INTEGER) WITHOUT ROWID',
|
|
89
|
+
'CREATE INDEX opened_files_name ON opened_files (locator, path, revision)',
|
|
90
|
+
...REPLICA_TABLES,
|
|
91
|
+
...MESSAGE_TABLES,
|
|
92
|
+
...SEARCH_TABLES,
|
|
93
|
+
];
|
|
94
|
+
const OWNERS = ['authored_pending', 'authored_room_drafts', 'authored_calls'];
|
|
95
|
+
const AUTHORED_KEYS = OWNERS.map((table) => `SELECT value FROM ${table}, json_each(${table}.files)`).join(' UNION ');
|
|
96
|
+
const UNOWNED_PARTS = `DELETE FROM file_parts WHERE key NOT IN (${AUTHORED_KEYS} UNION SELECT key FROM opened_files)`;
|
|
97
|
+
const READS = new Set([
|
|
98
|
+
'authored.queues',
|
|
99
|
+
'authored.roomDrafts',
|
|
100
|
+
'authored.chatDrafts',
|
|
101
|
+
'authored.calls',
|
|
102
|
+
'authored.fences',
|
|
103
|
+
'authored.fact',
|
|
104
|
+
'authored.file',
|
|
105
|
+
'replica.open',
|
|
106
|
+
'replica.query',
|
|
107
|
+
'replica.entries',
|
|
108
|
+
'replica.typed',
|
|
109
|
+
'replica.referrers',
|
|
110
|
+
'replica.walk',
|
|
111
|
+
'replica.files',
|
|
112
|
+
'replica.folders',
|
|
113
|
+
'replica.typedWithin',
|
|
114
|
+
'replica.counts',
|
|
115
|
+
'replica.search',
|
|
116
|
+
'replica.received',
|
|
117
|
+
'replica.receivedParts',
|
|
118
|
+
'replica.transferPart',
|
|
119
|
+
'opened.find',
|
|
120
|
+
'file.part',
|
|
121
|
+
'conversation.held',
|
|
122
|
+
'conversation.window',
|
|
123
|
+
'conversation.count',
|
|
124
|
+
'conversation.turn',
|
|
125
|
+
]);
|
|
126
|
+
const KINDS = new Set([
|
|
127
|
+
...READS,
|
|
128
|
+
'authored.amend',
|
|
129
|
+
'authored.roomDraft',
|
|
130
|
+
'authored.chatDraft',
|
|
131
|
+
'authored.call',
|
|
132
|
+
'authored.remember',
|
|
133
|
+
'authored.claim',
|
|
134
|
+
'authored.end',
|
|
135
|
+
'replica.stage',
|
|
136
|
+
'replica.keepParts',
|
|
137
|
+
'replica.refresh',
|
|
138
|
+
'replica.unrefresh',
|
|
139
|
+
'replica.commit',
|
|
140
|
+
'replica.fence',
|
|
141
|
+
'replica.erase',
|
|
142
|
+
'replica.abandon',
|
|
143
|
+
'replica.release',
|
|
144
|
+
'replica.rederive',
|
|
145
|
+
'replica.fact',
|
|
146
|
+
'replica.present',
|
|
147
|
+
'replica.receive',
|
|
148
|
+
'replica.unreceive',
|
|
149
|
+
'opened.begin',
|
|
150
|
+
'opened.part',
|
|
151
|
+
'opened.keep',
|
|
152
|
+
'opened.drop',
|
|
153
|
+
'opened.clean',
|
|
154
|
+
'conversation.keep',
|
|
155
|
+
'conversation.drop',
|
|
156
|
+
'space.probe',
|
|
157
|
+
]);
|
|
158
|
+
export function isRuntime(request) {
|
|
159
|
+
return KINDS.has(request.kind);
|
|
160
|
+
}
|
|
161
|
+
export function readsRuntime(request) {
|
|
162
|
+
return READS.has(request.kind);
|
|
163
|
+
}
|
|
164
|
+
function text(value) {
|
|
165
|
+
return typeof value === 'string' ? value : String(value);
|
|
166
|
+
}
|
|
167
|
+
function texts(rows, column) {
|
|
168
|
+
return rows.map((row) => text(row[column]));
|
|
169
|
+
}
|
|
170
|
+
function owned(files) {
|
|
171
|
+
return JSON.stringify([...new Set(files)]);
|
|
172
|
+
}
|
|
173
|
+
function fact(connection, name) {
|
|
174
|
+
const [held] = connection.rows('SELECT value FROM authored_facts WHERE name = ?', [name]);
|
|
175
|
+
return held === undefined ? null : text(held.value);
|
|
176
|
+
}
|
|
177
|
+
function remember(connection, name, value) {
|
|
178
|
+
connection.run('INSERT OR REPLACE INTO authored_facts (name, value) VALUES (?, ?)', [
|
|
179
|
+
name,
|
|
180
|
+
value,
|
|
181
|
+
]);
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
function fenced(connection, locator) {
|
|
185
|
+
return connection.rows('SELECT 1 FROM authored_fences WHERE locator = ?', [locator]).length > 0;
|
|
186
|
+
}
|
|
187
|
+
function stored(connection, key) {
|
|
188
|
+
return connection.rows('SELECT 1 FROM file_parts WHERE key = ? LIMIT 1', [key]).length > 0;
|
|
189
|
+
}
|
|
190
|
+
function storedPart(connection, key, part, bytes) {
|
|
191
|
+
connection.run('INSERT INTO file_parts (key, part, bytes) VALUES (?, ?, ?)', [key, part, bytes]);
|
|
192
|
+
}
|
|
193
|
+
async function storedFiles(connection, files) {
|
|
194
|
+
for (const { key, blob } of files) {
|
|
195
|
+
if (stored(connection, key))
|
|
196
|
+
continue;
|
|
197
|
+
for (let at = 0, part = 0; at < blob.size; at += FILE_PART_BYTES, part += 1) {
|
|
198
|
+
const bytes = new Uint8Array(await blob.slice(at, at + FILE_PART_BYTES).arrayBuffer());
|
|
199
|
+
storedPart(connection, key, part, bytes);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function filePart(connection, key, part) {
|
|
204
|
+
const [held] = connection.rows('SELECT bytes FROM file_parts WHERE key = ? AND part = ?', [
|
|
205
|
+
key,
|
|
206
|
+
part,
|
|
207
|
+
]);
|
|
208
|
+
return held?.bytes instanceof Uint8Array ? held.bytes : null;
|
|
209
|
+
}
|
|
210
|
+
function* fileParts(connection, key) {
|
|
211
|
+
for (let part = 0;; part += 1) {
|
|
212
|
+
const bytes = filePart(connection, key, part);
|
|
213
|
+
if (bytes === null)
|
|
214
|
+
return;
|
|
215
|
+
yield bytes;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async function spooled(connection, spool, { key, type }) {
|
|
219
|
+
for (const held of [...spool.held()]) {
|
|
220
|
+
if (held !== key && !stored(connection, held))
|
|
221
|
+
await spool.drop(held);
|
|
222
|
+
}
|
|
223
|
+
return spool.spool(key, type, fileParts(connection, key));
|
|
224
|
+
}
|
|
225
|
+
function unowned(connection) {
|
|
226
|
+
connection.run(UNOWNED_PARTS);
|
|
227
|
+
}
|
|
228
|
+
function queued(connection) {
|
|
229
|
+
return {
|
|
230
|
+
queues: connection.rows('SELECT locator, ids FROM authored_queues').map((row) => ({
|
|
231
|
+
locator: text(row.locator),
|
|
232
|
+
ids: JSON.parse(text(row.ids)),
|
|
233
|
+
})),
|
|
234
|
+
pending: connection
|
|
235
|
+
.rows('SELECT locator, id, batch, state FROM authored_pending')
|
|
236
|
+
.map((row) => ({
|
|
237
|
+
locator: text(row.locator),
|
|
238
|
+
id: text(row.id),
|
|
239
|
+
batch: text(row.batch),
|
|
240
|
+
state: text(row.state),
|
|
241
|
+
})),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function heldOrder(connection, locator) {
|
|
245
|
+
const [held] = connection.rows('SELECT ids FROM authored_queues WHERE locator = ?', [locator]);
|
|
246
|
+
return held === undefined ? [] : JSON.parse(text(held.ids));
|
|
247
|
+
}
|
|
248
|
+
function keptRows(connection, { locator, order, added, founded, roomDrafts, consumedRoomDrafts, amendments }) {
|
|
249
|
+
for (const { id, amendment } of amendments ?? []) {
|
|
250
|
+
amended(connection, { kind: 'authored.amend', locator, id, amendment });
|
|
251
|
+
}
|
|
252
|
+
for (const path of consumedRoomDrafts ?? []) {
|
|
253
|
+
connection.run('DELETE FROM authored_room_drafts WHERE locator = ? AND path = ?', [
|
|
254
|
+
locator,
|
|
255
|
+
path,
|
|
256
|
+
]);
|
|
257
|
+
}
|
|
258
|
+
for (const { path, record, files } of roomDrafts ?? []) {
|
|
259
|
+
connection.run('INSERT OR REPLACE INTO authored_room_drafts (locator, path, record, files) VALUES (?, ?, ?, ?)', [locator, path, record, owned(files)]);
|
|
260
|
+
}
|
|
261
|
+
if (order === undefined)
|
|
262
|
+
return;
|
|
263
|
+
const before = new Set(heldOrder(connection, locator));
|
|
264
|
+
const after = new Set(order);
|
|
265
|
+
if (after.size === 0)
|
|
266
|
+
connection.run('DELETE FROM authored_queues WHERE locator = ?', [locator]);
|
|
267
|
+
else {
|
|
268
|
+
connection.run('INSERT OR REPLACE INTO authored_queues (locator, ids) VALUES (?, ?)', [
|
|
269
|
+
locator,
|
|
270
|
+
JSON.stringify([...after]),
|
|
271
|
+
]);
|
|
272
|
+
}
|
|
273
|
+
for (const id of before) {
|
|
274
|
+
if (!after.has(id)) {
|
|
275
|
+
connection.run('DELETE FROM authored_pending WHERE locator = ? AND id = ?', [locator, id]);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
for (const { id, batch, state, files } of added ?? []) {
|
|
279
|
+
if (before.has(id) || !after.has(id))
|
|
280
|
+
continue;
|
|
281
|
+
connection.run('INSERT OR REPLACE INTO authored_pending (locator, id, batch, state, files) VALUES (?, ?, ?, ?, ?)', [locator, id, batch, state, owned(files)]);
|
|
282
|
+
}
|
|
283
|
+
for (const { id, batch, files } of founded ?? []) {
|
|
284
|
+
const [held] = connection.rows('SELECT files FROM authored_pending WHERE locator = ? AND id = ?', [locator, id]);
|
|
285
|
+
if (held === undefined || !before.has(id))
|
|
286
|
+
continue;
|
|
287
|
+
connection.run('UPDATE authored_pending SET batch = ?, files = ? WHERE locator = ? AND id = ?', [batch, owned([...JSON.parse(text(held.files)), ...files]), locator, id]);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async function kept(connection, { writes, files, whole, discards }) {
|
|
291
|
+
const refused = writes.find(({ locator }) => fenced(connection, locator));
|
|
292
|
+
if (refused !== undefined && whole)
|
|
293
|
+
return refused.locator;
|
|
294
|
+
await storedFiles(connection, files);
|
|
295
|
+
for (const write of writes) {
|
|
296
|
+
if (!fenced(connection, write.locator))
|
|
297
|
+
keptRows(connection, write);
|
|
298
|
+
}
|
|
299
|
+
for (const locator of discards ?? [])
|
|
300
|
+
roomWork(connection, locator);
|
|
301
|
+
unowned(connection);
|
|
302
|
+
return refused?.locator ?? null;
|
|
303
|
+
}
|
|
304
|
+
function amended(connection, { locator, id, amendment }) {
|
|
305
|
+
if (fenced(connection, locator))
|
|
306
|
+
return null;
|
|
307
|
+
const [held] = connection.rows('SELECT state FROM authored_pending WHERE locator = ? AND id = ?', [locator, id]);
|
|
308
|
+
if (held === undefined)
|
|
309
|
+
return null;
|
|
310
|
+
connection.run('UPDATE authored_pending SET state = ? WHERE locator = ? AND id = ?', [
|
|
311
|
+
JSON.stringify({
|
|
312
|
+
...JSON.parse(text(held.state)),
|
|
313
|
+
...JSON.parse(amendment),
|
|
314
|
+
}),
|
|
315
|
+
locator,
|
|
316
|
+
id,
|
|
317
|
+
]);
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
async function roomDraft(connection, { locator, path, kept: draft, files }) {
|
|
321
|
+
if (fenced(connection, locator))
|
|
322
|
+
return locator;
|
|
323
|
+
await storedFiles(connection, files);
|
|
324
|
+
if (draft === null) {
|
|
325
|
+
connection.run('DELETE FROM authored_room_drafts WHERE locator = ? AND path = ?', [
|
|
326
|
+
locator,
|
|
327
|
+
path,
|
|
328
|
+
]);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
connection.run('INSERT OR REPLACE INTO authored_room_drafts (locator, path, record, files) VALUES (?, ?, ?, ?)', [locator, path, draft.record, owned(draft.files)]);
|
|
332
|
+
}
|
|
333
|
+
unowned(connection);
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
function chatDraft(connection, { kept: drafts, dropped }) {
|
|
337
|
+
for (const conversation of dropped) {
|
|
338
|
+
connection.run('DELETE FROM authored_chat_drafts WHERE conversation = ?', [conversation]);
|
|
339
|
+
}
|
|
340
|
+
for (const { conversation, record } of drafts) {
|
|
341
|
+
connection.run('INSERT OR REPLACE INTO authored_chat_drafts (conversation, record) VALUES (?, ?)', [conversation, record]);
|
|
342
|
+
}
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
async function call(connection, { operation, kept: held, files }) {
|
|
346
|
+
await storedFiles(connection, files);
|
|
347
|
+
if (held === null)
|
|
348
|
+
connection.run('DELETE FROM authored_calls WHERE operation = ?', [operation]);
|
|
349
|
+
else {
|
|
350
|
+
connection.run('INSERT OR REPLACE INTO authored_calls (operation, record, files) VALUES (?, ?, ?)', [operation, held.record, owned(held.files)]);
|
|
351
|
+
}
|
|
352
|
+
unowned(connection);
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
function roomWork(connection, locator) {
|
|
356
|
+
connection.run('DELETE FROM authored_queues WHERE locator = ?', [locator]);
|
|
357
|
+
connection.run('DELETE FROM authored_pending WHERE locator = ?', [locator]);
|
|
358
|
+
}
|
|
359
|
+
function fence(connection, locators) {
|
|
360
|
+
for (const locator of locators) {
|
|
361
|
+
connection.run('INSERT OR REPLACE INTO authored_fences (locator) VALUES (?)', [locator]);
|
|
362
|
+
roomWork(connection, locator);
|
|
363
|
+
connection.run('DELETE FROM authored_room_drafts WHERE locator = ?', [locator]);
|
|
364
|
+
connection.run('DELETE FROM opened_files WHERE locator = ?', [locator]);
|
|
365
|
+
}
|
|
366
|
+
unowned(connection);
|
|
367
|
+
return fenceRooms(connection, locators);
|
|
368
|
+
}
|
|
369
|
+
function erased(connection, { locator, limit }) {
|
|
370
|
+
const done = erase(connection, locator, limit);
|
|
371
|
+
if (locator !== null && !done.remaining) {
|
|
372
|
+
connection.run('DELETE FROM authored_fences WHERE locator = ?', [locator]);
|
|
373
|
+
}
|
|
374
|
+
return done;
|
|
375
|
+
}
|
|
376
|
+
function claim(connection, holder) {
|
|
377
|
+
const held = fact(connection, 'holder');
|
|
378
|
+
if (held !== null)
|
|
379
|
+
return held === holder;
|
|
380
|
+
remember(connection, 'holder', holder);
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
function end(connection, holder) {
|
|
384
|
+
ended(connection);
|
|
385
|
+
forgottenMessages(connection);
|
|
386
|
+
if (holder !== null && claim(connection, holder))
|
|
387
|
+
return true;
|
|
388
|
+
for (const [table] of TABLES)
|
|
389
|
+
connection.run(`DELETE FROM ${table}`);
|
|
390
|
+
connection.run('DELETE FROM opened_files');
|
|
391
|
+
connection.run('DELETE FROM file_parts');
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
function count(value) {
|
|
395
|
+
return typeof value === 'bigint' ? Number(value) : typeof value === 'number' ? value : 0;
|
|
396
|
+
}
|
|
397
|
+
function openedFile(connection, { locator, path, revision }) {
|
|
398
|
+
const [held] = connection.rows('SELECT key, media_type, byte_length FROM opened_files WHERE locator = ? AND path = ? AND revision = ? AND byte_length IS NOT NULL', [locator, path, revision]);
|
|
399
|
+
return held === undefined
|
|
400
|
+
? null
|
|
401
|
+
: {
|
|
402
|
+
key: text(held.key),
|
|
403
|
+
byteLength: count(held.byte_length),
|
|
404
|
+
mediaType: text(held.media_type),
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function opening(connection, { key, locator, path, revision, mediaType }) {
|
|
408
|
+
if (fenced(connection, locator))
|
|
409
|
+
return false;
|
|
410
|
+
connection.run('DELETE FROM opened_files WHERE locator = ? AND path = ? AND revision = ? AND byte_length IS NULL', [locator, path, revision]);
|
|
411
|
+
unowned(connection);
|
|
412
|
+
connection.run('INSERT INTO opened_files (key, locator, path, revision, media_type) VALUES (?, ?, ?, ?, ?)', [key, locator, path, revision, mediaType]);
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
function fetching(connection, key) {
|
|
416
|
+
const [held] = connection.rows('SELECT locator, path, revision FROM opened_files WHERE key = ? AND byte_length IS NULL', [key]);
|
|
417
|
+
return held === undefined
|
|
418
|
+
? null
|
|
419
|
+
: { locator: text(held.locator), path: text(held.path), revision: text(held.revision) };
|
|
420
|
+
}
|
|
421
|
+
function openedPart(connection, { key, part, bytes }) {
|
|
422
|
+
if (fetching(connection, key) === null)
|
|
423
|
+
return false;
|
|
424
|
+
storedPart(connection, key, part, bytes);
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
function dropped(connection, key) {
|
|
428
|
+
connection.run('DELETE FROM opened_files WHERE key = ?', [key]);
|
|
429
|
+
connection.run('DELETE FROM file_parts WHERE key = ?', [key]);
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
function keptOpened(connection, { key, sha256, byteLength, parts }) {
|
|
433
|
+
const fetched = fetching(connection, key);
|
|
434
|
+
if (fetched === null)
|
|
435
|
+
return false;
|
|
436
|
+
const [held] = connection.rows('SELECT count(*) AS parts, min(part) AS first, max(part) AS last, coalesce(sum(length(bytes)), 0) AS bytes FROM file_parts WHERE key = ?', [key]);
|
|
437
|
+
const whole = count(held?.parts) === parts &&
|
|
438
|
+
count(held?.bytes) === byteLength &&
|
|
439
|
+
(parts === 0 || (count(held?.first) === 0 && count(held?.last) === parts - 1));
|
|
440
|
+
if (!whole) {
|
|
441
|
+
dropped(connection, key);
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
connection.run('UPDATE opened_files SET sha256 = ?, byte_length = ? WHERE key = ?', [
|
|
445
|
+
sha256,
|
|
446
|
+
byteLength,
|
|
447
|
+
key,
|
|
448
|
+
]);
|
|
449
|
+
connection.run('DELETE FROM opened_files WHERE locator = ? AND path = ? AND revision = ? AND key <> ?', [fetched.locator, fetched.path, fetched.revision, key]);
|
|
450
|
+
unowned(connection);
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
function cleaned(connection) {
|
|
454
|
+
connection.run('DELETE FROM opened_files');
|
|
455
|
+
const [freed] = connection.rows(`SELECT coalesce(sum(length(bytes)), 0) AS bytes FROM file_parts WHERE key NOT IN (${AUTHORED_KEYS})`);
|
|
456
|
+
unowned(connection);
|
|
457
|
+
return count(freed?.bytes);
|
|
458
|
+
}
|
|
459
|
+
async function committed(connection, { authored, rooms }) {
|
|
460
|
+
const refused = authored === undefined ? null : await kept(connection, authored);
|
|
461
|
+
if (refused !== null && authored?.whole === true) {
|
|
462
|
+
return { fenced: refused, counts: {}, changed: {}, indexed: {} };
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
fenced: refused,
|
|
466
|
+
...commit(connection, rooms.filter(({ locator }) => !fenced(connection, locator))),
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function receivedParts(connection, key, count) {
|
|
470
|
+
return connection
|
|
471
|
+
.rows('SELECT value FROM replica_received_parts WHERE key = ? AND part < ? ORDER BY part', [
|
|
472
|
+
key,
|
|
473
|
+
count,
|
|
474
|
+
])
|
|
475
|
+
.map((row) => JSON.parse(text(row.value)));
|
|
476
|
+
}
|
|
477
|
+
function receive(connection, { key, head, part }) {
|
|
478
|
+
connection.run('INSERT OR REPLACE INTO replica_received (key, head) VALUES (?, ?)', [key, head]);
|
|
479
|
+
if (part !== undefined) {
|
|
480
|
+
connection.run('INSERT OR REPLACE INTO replica_received_parts (key, part, value) VALUES (?, ?, ?)', [key, part.index, part.value]);
|
|
481
|
+
}
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
function unreceive(connection, keys) {
|
|
485
|
+
for (const key of keys) {
|
|
486
|
+
connection.run('DELETE FROM replica_received WHERE key = ?', [key]);
|
|
487
|
+
connection.run('DELETE FROM replica_received_parts WHERE key = ?', [key]);
|
|
488
|
+
}
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
export function answerRuntime(connection, request, spool) {
|
|
492
|
+
switch (request.kind) {
|
|
493
|
+
case 'authored.queues':
|
|
494
|
+
return queued(connection);
|
|
495
|
+
case 'authored.roomDrafts':
|
|
496
|
+
return texts(connection.rows('SELECT record FROM authored_room_drafts'), 'record');
|
|
497
|
+
case 'authored.chatDrafts':
|
|
498
|
+
return texts(connection.rows('SELECT record FROM authored_chat_drafts'), 'record');
|
|
499
|
+
case 'authored.calls':
|
|
500
|
+
return texts(connection.rows('SELECT record FROM authored_calls'), 'record');
|
|
501
|
+
case 'authored.fences':
|
|
502
|
+
return texts(connection.rows('SELECT locator FROM authored_fences'), 'locator');
|
|
503
|
+
case 'authored.fact':
|
|
504
|
+
return fact(connection, request.name);
|
|
505
|
+
case 'authored.file':
|
|
506
|
+
return spooled(connection, spool, request);
|
|
507
|
+
case 'authored.amend':
|
|
508
|
+
return amended(connection, request);
|
|
509
|
+
case 'authored.roomDraft':
|
|
510
|
+
return roomDraft(connection, request);
|
|
511
|
+
case 'authored.chatDraft':
|
|
512
|
+
return chatDraft(connection, request);
|
|
513
|
+
case 'authored.call':
|
|
514
|
+
return call(connection, request);
|
|
515
|
+
case 'authored.remember':
|
|
516
|
+
return remember(connection, request.name, request.value);
|
|
517
|
+
case 'authored.claim':
|
|
518
|
+
return claim(connection, request.holder);
|
|
519
|
+
case 'authored.end':
|
|
520
|
+
return end(connection, request.holder);
|
|
521
|
+
case 'replica.open':
|
|
522
|
+
return opened(connection);
|
|
523
|
+
case 'replica.query':
|
|
524
|
+
return answerQuery(connection, request.plan, request.rooms, request.budget);
|
|
525
|
+
case 'replica.entries':
|
|
526
|
+
return entries(connection, request.key, request.view, request.paths);
|
|
527
|
+
case 'replica.typed':
|
|
528
|
+
return typed(connection, request.key, request.view, request.paths);
|
|
529
|
+
case 'replica.referrers':
|
|
530
|
+
return referrers(connection, request.key, request.view, request.targets, request.kinds);
|
|
531
|
+
case 'replica.walk':
|
|
532
|
+
return walk(connection, request.key, request.view, request.after, request.bytes);
|
|
533
|
+
case 'replica.files':
|
|
534
|
+
return files(connection, request.key, request.view, request.after, request.limit);
|
|
535
|
+
case 'replica.folders':
|
|
536
|
+
return folders(connection, request.key, request.view, request.paths);
|
|
537
|
+
case 'replica.typedWithin':
|
|
538
|
+
return typedWithin(connection, request.key, request.view, request.roots, request.within);
|
|
539
|
+
case 'replica.counts':
|
|
540
|
+
return counts(connection, request.rooms);
|
|
541
|
+
case 'replica.search': {
|
|
542
|
+
const { kind: _kind, ...ask } = request;
|
|
543
|
+
return searched(connection, ask);
|
|
544
|
+
}
|
|
545
|
+
case 'replica.received':
|
|
546
|
+
return connection.rows('SELECT key, head FROM replica_received').map((row) => ({
|
|
547
|
+
key: text(row.key),
|
|
548
|
+
head: JSON.parse(text(row.head)),
|
|
549
|
+
}));
|
|
550
|
+
case 'replica.receivedParts':
|
|
551
|
+
return receivedParts(connection, request.key, request.count);
|
|
552
|
+
case 'replica.stage':
|
|
553
|
+
return fenced(connection, request.locator) ? null : stage(connection, request);
|
|
554
|
+
case 'replica.transferPart':
|
|
555
|
+
return transferPart(connection, request.locator, request.token);
|
|
556
|
+
case 'replica.keepParts':
|
|
557
|
+
return keepParts(connection, request.parts.filter(({ locator }) => !fenced(connection, locator)));
|
|
558
|
+
case 'replica.refresh':
|
|
559
|
+
return refreshing(connection, request);
|
|
560
|
+
case 'replica.unrefresh':
|
|
561
|
+
return unrefresh(connection, request.key);
|
|
562
|
+
case 'replica.commit':
|
|
563
|
+
return committed(connection, request);
|
|
564
|
+
case 'replica.fence':
|
|
565
|
+
return fence(connection, request.locators);
|
|
566
|
+
case 'replica.erase':
|
|
567
|
+
return erased(connection, request);
|
|
568
|
+
case 'replica.abandon':
|
|
569
|
+
return abandon(connection, request.locator, request.kept);
|
|
570
|
+
case 'replica.release':
|
|
571
|
+
return release(connection, request.key);
|
|
572
|
+
case 'replica.rederive':
|
|
573
|
+
return rederive(connection, request.key, request.composed, request.bytes, request.ms, request.floor);
|
|
574
|
+
case 'replica.fact':
|
|
575
|
+
connection.run('INSERT OR REPLACE INTO replica_facts (name, value) VALUES (?, ?)', [
|
|
576
|
+
request.name,
|
|
577
|
+
request.value,
|
|
578
|
+
]);
|
|
579
|
+
return null;
|
|
580
|
+
case 'replica.present':
|
|
581
|
+
connection.run('INSERT OR REPLACE INTO replica_presentation (app, value) VALUES (?, ?)', [
|
|
582
|
+
request.app,
|
|
583
|
+
request.value,
|
|
584
|
+
]);
|
|
585
|
+
return null;
|
|
586
|
+
case 'replica.receive':
|
|
587
|
+
return receive(connection, request);
|
|
588
|
+
case 'replica.unreceive':
|
|
589
|
+
return unreceive(connection, request.keys);
|
|
590
|
+
case 'opened.find':
|
|
591
|
+
return openedFile(connection, request);
|
|
592
|
+
case 'opened.begin':
|
|
593
|
+
return opening(connection, request);
|
|
594
|
+
case 'opened.part':
|
|
595
|
+
return openedPart(connection, request);
|
|
596
|
+
case 'opened.keep':
|
|
597
|
+
return keptOpened(connection, request);
|
|
598
|
+
case 'opened.drop':
|
|
599
|
+
return dropped(connection, request.key);
|
|
600
|
+
case 'opened.clean':
|
|
601
|
+
return cleaned(connection);
|
|
602
|
+
case 'file.part':
|
|
603
|
+
return filePart(connection, request.key, request.part);
|
|
604
|
+
case 'conversation.held':
|
|
605
|
+
return heldConversations(connection);
|
|
606
|
+
case 'conversation.keep': {
|
|
607
|
+
const { kind: _kind, ...write } = request;
|
|
608
|
+
return keptMessages(connection, write);
|
|
609
|
+
}
|
|
610
|
+
case 'conversation.window':
|
|
611
|
+
return messageWindow(connection, request.conversation, request.limit, request.before);
|
|
612
|
+
case 'conversation.count':
|
|
613
|
+
return messageCount(connection, request.conversation);
|
|
614
|
+
case 'conversation.turn':
|
|
615
|
+
return turnArrived(connection, request.conversation, request.turn);
|
|
616
|
+
case 'conversation.drop':
|
|
617
|
+
chatDraft(connection, {
|
|
618
|
+
kind: 'authored.chatDraft',
|
|
619
|
+
kept: [],
|
|
620
|
+
dropped: request.conversations,
|
|
621
|
+
});
|
|
622
|
+
droppedMessages(connection, request.conversations);
|
|
623
|
+
return null;
|
|
624
|
+
case 'space.probe':
|
|
625
|
+
return spool.probe(PROBE_BYTES).then(() => null);
|
|
626
|
+
}
|
|
627
|
+
}
|
package/dist/turn.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function yielded(): Promise<void>;
|
|
2
|
+
export declare const ROW_WEIGHT = 256;
|
|
3
|
+
export type Later<T> = T | Promise<T>;
|
|
4
|
+
export declare function after<T, R>(value: Later<T>, next: (value: T) => Later<R>): Later<R>;
|
|
5
|
+
export declare function paced(): (weight: number) => Later<void>;
|
|
6
|
+
export declare function inUnits<T>(items: Iterable<T>, each: (item: T) => number, ended?: () => void, paused?: () => Promise<void>): Later<void>;
|
|
7
|
+
export declare function finished<R>(steps: Generator<number, R, void>): R;
|
|
8
|
+
export declare function stepped<R>(steps: Generator<number, R, void>): Later<R>;
|
|
9
|
+
//# sourceMappingURL=turn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn.d.ts","sourceRoot":"","sources":["../src/turn.ts"],"names":[],"mappings":"AAAA,wBAAgB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CASvC;AAOD,eAAO,MAAM,UAAU,MAAM,CAAC;AAE9B,MAAM,MAAM,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAEtC,wBAAgB,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAEnF;AAmBD,wBAAgB,KAAK,IAAI,CAAC,MAAM,EAAE,MAAM,KAAK,KAAK,CAAC,IAAI,CAAC,CAGvD;AAsBD,wBAAgB,OAAO,CAAC,CAAC,EACvB,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAClB,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,EACzB,KAAK,GAAE,MAAM,IAAe,EAC5B,MAAM,GAAE,MAAM,OAAO,CAAC,IAAI,CAAW,GACpC,KAAK,CAAC,IAAI,CAAC,CAUb;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAIhE;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAStE"}
|