@agentshouse/mdruntime 0.1.0-ca6edc1abd618 → 0.1.0-ca92b54804c33
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 +2 -2
- package/dist/adapter.d.ts +101 -70
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +3 -10
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +29 -47
- package/dist/bootstrap.d.ts +16 -14
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +272 -129
- package/dist/chain.d.ts +2 -1
- package/dist/chain.d.ts.map +1 -1
- package/dist/change.d.ts +1 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/client.d.ts +13 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +54 -6
- package/dist/collection.d.ts +7 -4
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +23 -17
- package/dist/compile.d.ts +1 -6
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +108 -243
- package/dist/connection.d.ts +14 -11
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +2264 -1933
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +7 -0
- package/dist/conversation.d.ts +21 -17
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +405 -253
- package/dist/durability.d.ts +6 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +3 -2
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +18 -8
- package/dist/file.d.ts +2 -11
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +6 -4
- package/dist/host.d.ts +3 -7
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +26 -29
- package/dist/identity.d.ts +0 -1
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +0 -3
- package/dist/index.d.ts +12 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/job.d.ts +14 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +28 -0
- package/dist/live.d.ts +5 -4
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +62 -20
- package/dist/media.d.ts +8 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +218 -0
- package/dist/memory.d.ts +12 -5
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +216 -85
- package/dist/messages.d.ts +3 -3
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +20 -19
- package/dist/model.d.ts +8 -10
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +5 -17
- package/dist/observation.d.ts +3 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +8 -3
- package/dist/outcome.d.ts +13 -5
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +16 -2
- package/dist/overlay.d.ts +1 -0
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +11 -0
- package/dist/paths.d.ts +1 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +4 -0
- package/dist/query.d.ts +8 -207
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -505
- package/dist/reach.d.ts +18 -11
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +195 -126
- package/dist/react.d.ts +3 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/readiness.d.ts +3 -17
- package/dist/readiness.d.ts.map +1 -1
- package/dist/readiness.js +4 -10
- package/dist/recovery.d.ts +1 -0
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +35 -1
- package/dist/refresh.d.ts +7 -5
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +33 -5
- package/dist/replica.d.ts +10 -8
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +52 -20
- package/dist/room-draft.d.ts +21 -5
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +98 -51
- package/dist/rows.d.ts +18 -16
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +45 -153
- package/dist/save.d.ts +2 -0
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +22 -13
- package/dist/search.d.ts +12 -63
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +9 -52
- package/dist/shared.d.ts +0 -1
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +0 -10
- package/dist/status.d.ts +4 -5
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -7
- package/dist/store.d.ts +101 -61
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +153 -161
- package/dist/stored.d.ts +66 -55
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +341 -273
- package/dist/tables.d.ts +90 -63
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +174 -98
- package/dist/transfer.d.ts +0 -5
- package/dist/transfer.d.ts.map +1 -1
- package/dist/validation.d.ts +3 -4
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +42 -100
- package/dist/windows.d.ts +1 -12
- package/dist/windows.d.ts.map +1 -1
- package/dist/windows.js +2 -75
- package/dist/wire.d.ts +17 -8
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +5 -2
- package/package.json +2 -2
- package/dist/fence.d.ts +0 -6
- package/dist/fence.d.ts.map +0 -1
- package/dist/fence.js +0 -10
- package/dist/fulltext.d.ts +0 -27
- package/dist/fulltext.d.ts.map +0 -1
- package/dist/fulltext.js +0 -368
- package/dist/reads.d.ts +0 -11
- package/dist/reads.d.ts.map +0 -1
- package/dist/reads.js +0 -100
- package/dist/words.d.ts +0 -19
- package/dist/words.d.ts.map +0 -1
- package/dist/words.js +0 -150
package/dist/stored.js
CHANGED
|
@@ -1,30 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { sql } from '@agentshouse/mdmodel';
|
|
2
|
+
import { composedOf } from './collection.js';
|
|
3
|
+
import { described, derived, heldOf } from './rows.js';
|
|
3
4
|
export const REPLICA_TABLES = [
|
|
4
|
-
'CREATE TABLE replica_keys (key INTEGER PRIMARY KEY AUTOINCREMENT, locator TEXT NOT NULL, transfer TEXT NOT NULL,
|
|
5
|
-
'CREATE TABLE replica_rooms (locator TEXT PRIMARY KEY, key INTEGER NOT NULL, record TEXT NOT NULL) WITHOUT ROWID',
|
|
6
|
-
'CREATE TABLE
|
|
7
|
-
|
|
8
|
-
'CREATE INDEX replica_transfer_parts_locator ON replica_transfer_parts (locator)',
|
|
9
|
-
'CREATE TABLE replica_entries (id INTEGER PRIMARY KEY, key INTEGER NOT NULL, layer INTEGER NOT NULL, path TEXT NOT NULL, visible INTEGER NOT NULL, present INTEGER NOT NULL, queried INTEGER NOT NULL, binary TEXT, revision TEXT, file_id TEXT, change TEXT, written TEXT, type TEXT, declared TEXT, folder TEXT NOT NULL, fields TEXT, content TEXT)',
|
|
5
|
+
'CREATE TABLE replica_keys (key INTEGER PRIMARY KEY AUTOINCREMENT, locator TEXT NOT NULL, transfer TEXT NOT NULL, fingerprint TEXT NOT NULL, rederiving INTEGER)',
|
|
6
|
+
'CREATE TABLE replica_rooms (locator TEXT PRIMARY KEY, key INTEGER NOT NULL, record TEXT NOT NULL, documents INTEGER NOT NULL DEFAULT 0) WITHOUT ROWID',
|
|
7
|
+
'CREATE TABLE replica_models (identity TEXT NOT NULL, version TEXT NOT NULL, schema TEXT NOT NULL, PRIMARY KEY (identity, version)) WITHOUT ROWID',
|
|
8
|
+
"CREATE TABLE replica_entries (id INTEGER PRIMARY KEY, key INTEGER NOT NULL, layer INTEGER NOT NULL, path TEXT NOT NULL, visible INTEGER NOT NULL, present INTEGER NOT NULL, queried INTEGER NOT NULL, binary TEXT, revision TEXT, file_id TEXT, change TEXT, written TEXT, type TEXT, declared TEXT, folder TEXT NOT NULL, fields TEXT, links TEXT, part TEXT, created TEXT, bytes INTEGER GENERATED ALWAYS AS (CASE WHEN binary IS NULL THEN length(CAST(content AS BLOB)) ELSE binary ->> '$.byteLength' END) STORED, content TEXT)",
|
|
10
9
|
'CREATE UNIQUE INDEX replica_entries_path ON replica_entries (key, layer, path)',
|
|
11
|
-
'CREATE INDEX replica_entries_visible ON replica_entries (key, visible, path)',
|
|
10
|
+
'CREATE INDEX replica_entries_visible ON replica_entries (key, visible, present, path, binary)',
|
|
12
11
|
'CREATE INDEX replica_entries_folder ON replica_entries (key, folder, path)',
|
|
13
|
-
'CREATE
|
|
14
|
-
'CREATE INDEX
|
|
15
|
-
|
|
16
|
-
'CREATE
|
|
17
|
-
'CREATE
|
|
12
|
+
'CREATE INDEX replica_entries_type ON replica_entries (key, type, visible, present, queried, folder)',
|
|
13
|
+
'CREATE INDEX replica_entries_key ON replica_entries (key)',
|
|
14
|
+
sql.VALUE_TABLE,
|
|
15
|
+
'CREATE TABLE replica_links (doc INTEGER NOT NULL, key INTEGER NOT NULL, kind TEXT NOT NULL, keys TEXT NOT NULL, places TEXT NOT NULL, target TEXT NOT NULL, field TEXT NOT NULL, PRIMARY KEY (doc, kind, keys, places, target)) WITHOUT ROWID',
|
|
16
|
+
'CREATE INDEX replica_links_target ON replica_links (key, target, kind, keys)',
|
|
17
|
+
'CREATE TABLE replica_folders (key INTEGER NOT NULL, layer INTEGER NOT NULL, path TEXT NOT NULL, present INTEGER NOT NULL, part TEXT, PRIMARY KEY (key, layer, path)) WITHOUT ROWID',
|
|
18
18
|
'CREATE TABLE replica_erasing (key INTEGER PRIMARY KEY, locator TEXT NOT NULL)',
|
|
19
|
-
'CREATE TABLE replica_received (key TEXT PRIMARY KEY, head TEXT NOT NULL) WITHOUT ROWID',
|
|
20
|
-
'CREATE TABLE replica_received_parts (key TEXT NOT NULL, part INTEGER NOT NULL, value TEXT NOT NULL, PRIMARY KEY (key, part)) WITHOUT ROWID',
|
|
21
19
|
'CREATE TABLE replica_facts (name TEXT PRIMARY KEY, value TEXT NOT NULL) WITHOUT ROWID',
|
|
22
20
|
'CREATE TABLE replica_presentation (app TEXT PRIMARY KEY, value TEXT NOT NULL) WITHOUT ROWID',
|
|
23
21
|
];
|
|
24
22
|
const CONFIRMED = 0;
|
|
25
23
|
const OVERLAY = 1;
|
|
26
24
|
const REFRESHED = 2;
|
|
27
|
-
const
|
|
25
|
+
const DERIVED_ROWS = 64;
|
|
26
|
+
const ENTRY_WEIGHT = 256;
|
|
27
|
+
const ABSENT = {
|
|
28
|
+
type: null,
|
|
29
|
+
declared: null,
|
|
30
|
+
queried: false,
|
|
31
|
+
folder: '',
|
|
32
|
+
fields: null,
|
|
33
|
+
links: null,
|
|
34
|
+
};
|
|
28
35
|
function text(value) {
|
|
29
36
|
return typeof value === 'string' ? value : null;
|
|
30
37
|
}
|
|
@@ -40,55 +47,23 @@ function flag(value) {
|
|
|
40
47
|
function paths(list) {
|
|
41
48
|
return JSON.stringify(list);
|
|
42
49
|
}
|
|
43
|
-
function compositionOf(connection, key) {
|
|
44
|
-
let held = compositions.get(connection);
|
|
45
|
-
if (held === undefined) {
|
|
46
|
-
held = new Map();
|
|
47
|
-
compositions.set(connection, held);
|
|
48
|
-
}
|
|
49
|
-
const [row] = connection.rows('SELECT composition FROM replica_keys WHERE key = ?', [key]);
|
|
50
|
-
const textual = text(row?.composition) ?? 'null';
|
|
51
|
-
const known = held.get(textual);
|
|
52
|
-
if (known !== undefined)
|
|
53
|
-
return known;
|
|
54
|
-
const parsed = JSON.parse(textual) ?? {
|
|
55
|
-
model: { status: 'unavailable', reason: 'not-acquired' },
|
|
56
|
-
selection: null,
|
|
57
|
-
};
|
|
58
|
-
held.clear();
|
|
59
|
-
held.set(textual, parsed);
|
|
60
|
-
return parsed;
|
|
61
|
-
}
|
|
62
|
-
export function fingerprint(composition) {
|
|
63
|
-
const shape = (model) => model.status === 'unavailable' ? null : model.types;
|
|
64
|
-
return JSON.stringify({
|
|
65
|
-
model: shape(composition.model),
|
|
66
|
-
selection: composition.selection === null
|
|
67
|
-
? null
|
|
68
|
-
: { roots: composition.selection.roots, model: shape(composition.selection.model) },
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
50
|
function dropDerived(connection, doc) {
|
|
72
51
|
connection.run('DELETE FROM replica_values WHERE doc = ?', [doc]);
|
|
73
52
|
connection.run('DELETE FROM replica_links WHERE doc = ?', [doc]);
|
|
74
53
|
}
|
|
75
|
-
function writeDerived(connection, doc, key,
|
|
76
|
-
|
|
77
|
-
|
|
54
|
+
function writeDerived(connection, doc, key, path, held, composition) {
|
|
55
|
+
const made = derived(path, held, composition);
|
|
56
|
+
if (made.values.length > 0) {
|
|
57
|
+
const inserted = sql.insertedValues(doc, made.values);
|
|
58
|
+
connection.run(inserted.sql, inserted.bind);
|
|
59
|
+
}
|
|
60
|
+
if (made.links.length > 0) {
|
|
61
|
+
connection.run('INSERT OR IGNORE INTO replica_links (doc, key, kind, keys, places, target, field) SELECT ?, ?, value ->> 0, value ->> 1, value ->> 2, value ->> 3, value ->> 4 FROM json_each(?)', [
|
|
78
62
|
doc,
|
|
79
63
|
key,
|
|
80
|
-
|
|
81
|
-
value.held,
|
|
82
|
-
value.item,
|
|
83
|
-
value.kind,
|
|
84
|
-
value.value,
|
|
85
|
-
value.size,
|
|
86
|
-
flag(value.single),
|
|
64
|
+
JSON.stringify(made.links.map((link) => [link.kind, link.keys, link.places, link.target, link.field])),
|
|
87
65
|
]);
|
|
88
66
|
}
|
|
89
|
-
for (const link of made.links) {
|
|
90
|
-
connection.run('INSERT OR IGNORE INTO replica_links (doc, key, kind, form, target, field) VALUES (?, ?, ?, ?, ?, ?)', [doc, key, link.kind, link.form, link.target, link.field]);
|
|
91
|
-
}
|
|
92
67
|
}
|
|
93
68
|
function entryId(connection, key, layer, path) {
|
|
94
69
|
const [row] = connection.rows('SELECT id FROM replica_entries WHERE key = ? AND layer = ? AND path = ?', [key, layer, path]);
|
|
@@ -99,26 +74,10 @@ function removeEntry(connection, key, layer, path) {
|
|
|
99
74
|
if (id === null)
|
|
100
75
|
return;
|
|
101
76
|
dropDerived(connection, id);
|
|
102
|
-
unindexDocuments(connection, [id]);
|
|
103
77
|
connection.run('DELETE FROM replica_entries WHERE id = ?', [id]);
|
|
104
78
|
}
|
|
105
|
-
function
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
function putEntry(connection, key, layer, entry, present, composition, searched = true) {
|
|
109
|
-
removeEntry(connection, key, layer, entry.path);
|
|
110
|
-
const made = present
|
|
111
|
-
? derived(entry.path, entry.binary === null ? entry.content : null, composition)
|
|
112
|
-
: {
|
|
113
|
-
type: null,
|
|
114
|
-
declared: null,
|
|
115
|
-
queried: false,
|
|
116
|
-
folder: '',
|
|
117
|
-
fields: null,
|
|
118
|
-
values: [],
|
|
119
|
-
links: [],
|
|
120
|
-
};
|
|
121
|
-
connection.run('INSERT INTO replica_entries (key, layer, path, visible, present, queried, content, binary, revision, file_id, change, written, type, declared, folder, fields) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [
|
|
79
|
+
function insertEntry(connection, key, layer, entry, present, made, part = null) {
|
|
80
|
+
connection.run('INSERT INTO replica_entries (key, layer, path, visible, present, queried, content, binary, revision, file_id, created, change, written, type, declared, folder, fields, links, part) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [
|
|
122
81
|
key,
|
|
123
82
|
layer,
|
|
124
83
|
entry.path,
|
|
@@ -129,87 +88,141 @@ function putEntry(connection, key, layer, entry, present, composition, searched
|
|
|
129
88
|
entry.binary === null ? null : JSON.stringify(entry.binary),
|
|
130
89
|
entry.revision,
|
|
131
90
|
entry.fileId,
|
|
91
|
+
entry.created,
|
|
132
92
|
entry.change === null ? null : JSON.stringify(entry.change),
|
|
133
93
|
entry.written,
|
|
134
94
|
made.type,
|
|
135
95
|
made.declared,
|
|
136
96
|
made.folder,
|
|
137
97
|
made.fields,
|
|
98
|
+
made.links,
|
|
99
|
+
part,
|
|
138
100
|
]);
|
|
101
|
+
}
|
|
102
|
+
function textOf(entry) {
|
|
103
|
+
return entry.binary === null ? entry.content : null;
|
|
104
|
+
}
|
|
105
|
+
function putEntry(connection, key, layer, entry, present, composition) {
|
|
106
|
+
removeEntry(connection, key, layer, entry.path);
|
|
107
|
+
const content = textOf(entry);
|
|
108
|
+
insertEntry(connection, key, layer, entry, present, present ? described(entry.path, content, entry.facts) : ABSENT);
|
|
139
109
|
if (!present)
|
|
140
110
|
return;
|
|
141
111
|
const id = entryId(connection, key, layer, entry.path);
|
|
142
|
-
writeDerived(connection, id, key,
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
112
|
+
writeDerived(connection, id, key, entry.path, heldOf(entry.path, content, entry.facts), composition);
|
|
113
|
+
}
|
|
114
|
+
function placeEntry(connection, key, entry, part) {
|
|
115
|
+
removeEntry(connection, key, CONFIRMED, entry.path);
|
|
116
|
+
insertEntry(connection, key, CONFIRMED, entry, true, described(entry.path, textOf(entry), entry.facts), part);
|
|
146
117
|
}
|
|
147
118
|
function revealed(connection, key, touched) {
|
|
148
119
|
if (touched.length === 0)
|
|
149
120
|
return;
|
|
150
121
|
connection.run(`UPDATE replica_entries SET visible = CASE WHEN layer = ${OVERLAY} THEN present ELSE NOT EXISTS (SELECT 1 FROM replica_entries o WHERE o.key = replica_entries.key AND o.layer = ${OVERLAY} AND o.path = replica_entries.path) END WHERE key = ? AND layer IN (${CONFIRMED}, ${OVERLAY}) AND path IN (SELECT value FROM json_each(?))`, [key, paths(touched)]);
|
|
151
122
|
}
|
|
152
|
-
function newKey(connection, locator, transfer,
|
|
153
|
-
|
|
123
|
+
function newKey(connection, locator, transfer, fingerprint, built) {
|
|
124
|
+
const owed = built ? null : 0;
|
|
125
|
+
connection.run('INSERT INTO replica_keys (locator, transfer, fingerprint, rederiving) VALUES (?, ?, ?, ?)', [locator, transfer, fingerprint, owed]);
|
|
154
126
|
const [row] = connection.rows('SELECT last_insert_rowid() AS key');
|
|
155
127
|
return number(row.key);
|
|
156
128
|
}
|
|
157
|
-
function recomposed(connection, key,
|
|
158
|
-
const [row] = connection.rows('SELECT
|
|
159
|
-
|
|
160
|
-
const held = connection.rows('SELECT 1 FROM replica_entries WHERE key = ? LIMIT 1', [key]);
|
|
161
|
-
if (held.length === 0 || (before !== null && fingerprint(before) === fingerprint(composition))) {
|
|
162
|
-
connection.run('UPDATE replica_keys SET composition = ? WHERE key = ?', [
|
|
163
|
-
JSON.stringify(composition),
|
|
164
|
-
key,
|
|
165
|
-
]);
|
|
129
|
+
function recomposed(connection, key, fingerprint) {
|
|
130
|
+
const [row] = connection.rows('SELECT fingerprint FROM replica_keys WHERE key = ?', [key]);
|
|
131
|
+
if (row === undefined || text(row.fingerprint) === fingerprint)
|
|
166
132
|
return;
|
|
167
|
-
|
|
168
|
-
connection.run(
|
|
169
|
-
JSON.stringify(composition),
|
|
170
|
-
key,
|
|
171
|
-
]);
|
|
133
|
+
const held = connection.rows('SELECT 1 FROM replica_entries WHERE key = ? LIMIT 1', [key]);
|
|
134
|
+
connection.run(`UPDATE replica_keys SET fingerprint = ?${held.length === 0 ? '' : ', rederiving = 0'} WHERE key = ?`, [fingerprint, key]);
|
|
172
135
|
}
|
|
173
136
|
export function entered(connection, locator, composition, rows) {
|
|
174
|
-
const key = newKey(connection, locator, '', composition);
|
|
137
|
+
const key = newKey(connection, locator, '', composedOf(composition).fingerprint, true);
|
|
175
138
|
for (const entry of rows)
|
|
176
|
-
putEntry(connection, key, CONFIRMED, entry, true, composition
|
|
139
|
+
putEntry(connection, key, CONFIRMED, entry, true, composition);
|
|
177
140
|
return key;
|
|
178
141
|
}
|
|
179
|
-
function dropTransfer(connection, locator) {
|
|
180
|
-
connection.run('DELETE FROM replica_transfers WHERE locator = ?', [locator]);
|
|
181
|
-
connection.run('DELETE FROM replica_transfer_parts WHERE locator = ?', [locator]);
|
|
182
|
-
}
|
|
183
142
|
export function stage(connection, request) {
|
|
184
|
-
|
|
185
|
-
request.locator
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
(held === undefined
|
|
189
|
-
? newKey(connection, request.locator, request.transfer, request.composition)
|
|
190
|
-
: number(held.key));
|
|
191
|
-
recomposed(connection, key, request.composition);
|
|
192
|
-
connection.run('INSERT OR REPLACE INTO replica_transfers (locator, key, record) VALUES (?, ?, ?)', [request.locator, key, request.record]);
|
|
193
|
-
if (request.written !== undefined) {
|
|
194
|
-
connection.run('DELETE FROM replica_transfer_parts WHERE locator = ? AND token = ?', [
|
|
195
|
-
request.locator,
|
|
196
|
-
request.written,
|
|
197
|
-
]);
|
|
198
|
-
}
|
|
199
|
-
const composition = compositionOf(connection, key);
|
|
200
|
-
for (const entry of request.entries) {
|
|
201
|
-
putEntry(connection, key, CONFIRMED, entry, true, composition);
|
|
143
|
+
if (request.key === null) {
|
|
144
|
+
for (const row of connection.rows(STAGED_KEYS, [request.locator])) {
|
|
145
|
+
erasing(connection, number(row.key), request.locator);
|
|
146
|
+
}
|
|
202
147
|
}
|
|
148
|
+
const key = request.key ??
|
|
149
|
+
newKey(connection, request.locator, request.transfer, request.fingerprint, false);
|
|
150
|
+
recomposed(connection, key, request.fingerprint);
|
|
151
|
+
owing(connection, key);
|
|
152
|
+
for (const entry of request.entries)
|
|
153
|
+
placeEntry(connection, key, entry, request.part);
|
|
203
154
|
for (const path of request.folders) {
|
|
204
|
-
connection.run('INSERT OR REPLACE INTO replica_folders (key, layer, path, present) VALUES (?, ?, ?, 1)', [key, CONFIRMED, path]);
|
|
155
|
+
connection.run('INSERT OR REPLACE INTO replica_folders (key, layer, path, present, part) VALUES (?, ?, ?, 1, ?)', [key, CONFIRMED, path, request.part]);
|
|
205
156
|
}
|
|
206
157
|
return key;
|
|
207
158
|
}
|
|
159
|
+
function owing(connection, key) {
|
|
160
|
+
connection.run('UPDATE replica_keys SET rederiving = coalesce(rederiving, last) FROM (SELECT coalesce(max(id), 0) AS last FROM replica_entries) WHERE key = ?', [key]);
|
|
161
|
+
}
|
|
162
|
+
const STAGED_KEYS = `SELECT k.key, k.transfer FROM replica_keys k WHERE k.locator = ?
|
|
163
|
+
AND NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)
|
|
164
|
+
AND NOT EXISTS (SELECT 1 FROM replica_erasing e WHERE e.key = k.key)
|
|
165
|
+
ORDER BY k.key DESC`;
|
|
166
|
+
const UNPARTED = `EXISTS (SELECT 1 FROM replica_entries e WHERE e.key = k.key AND e.part IS NULL)
|
|
167
|
+
OR EXISTS (SELECT 1 FROM replica_folders f WHERE f.key = k.key AND f.part IS NULL)`;
|
|
168
|
+
function partsOf(connection, key, layer) {
|
|
169
|
+
const scoped = layer === null ? '' : ` AND layer = ${layer}`;
|
|
170
|
+
return new Set(connection
|
|
171
|
+
.rows(`SELECT part FROM replica_entries WHERE key = ?${scoped} AND part IS NOT NULL UNION SELECT part FROM replica_folders WHERE key = ?${scoped} AND part IS NOT NULL`, [key, key])
|
|
172
|
+
.map((row) => text(row.part)));
|
|
173
|
+
}
|
|
174
|
+
function resumable(connection, locator) {
|
|
175
|
+
let kept = null;
|
|
176
|
+
for (const row of connection.rows(STAGED_KEYS, [locator])) {
|
|
177
|
+
const key = number(row.key);
|
|
178
|
+
const parted = kept === null &&
|
|
179
|
+
connection.rows(`SELECT 1 FROM replica_keys k WHERE k.key = ? AND NOT (${UNPARTED})`, [key])
|
|
180
|
+
.length > 0;
|
|
181
|
+
if (parted)
|
|
182
|
+
kept = { key, transfer: text(row.transfer) };
|
|
183
|
+
else
|
|
184
|
+
erasing(connection, key, locator);
|
|
185
|
+
}
|
|
186
|
+
return kept;
|
|
187
|
+
}
|
|
188
|
+
export function resume(connection, request) {
|
|
189
|
+
const wanted = new Set(request.parts);
|
|
190
|
+
const held = resumable(connection, request.locator);
|
|
191
|
+
const staged = held === null ? new Set() : partsOf(connection, held.key, null);
|
|
192
|
+
const dropped = [...staged].filter((part) => !wanted.has(part));
|
|
193
|
+
if (held !== null && dropped.length > 0) {
|
|
194
|
+
const named = JSON.stringify(dropped);
|
|
195
|
+
const rows = `SELECT id FROM replica_entries WHERE key = ? AND part IN (SELECT value FROM json_each(?))`;
|
|
196
|
+
for (const table of ['replica_values', 'replica_links']) {
|
|
197
|
+
connection.run(`DELETE FROM ${table} WHERE doc IN (${rows})`, [held.key, named]);
|
|
198
|
+
}
|
|
199
|
+
connection.run(`DELETE FROM replica_entries WHERE id IN (${rows})`, [held.key, named]);
|
|
200
|
+
connection.run('DELETE FROM replica_folders WHERE key = ? AND part IN (SELECT value FROM json_each(?))', [held.key, named]);
|
|
201
|
+
}
|
|
202
|
+
const kept = [...staged].filter((part) => wanted.has(part));
|
|
203
|
+
const publishedParts = request.published === null
|
|
204
|
+
? new Set()
|
|
205
|
+
: partsOf(connection, request.published, CONFIRMED);
|
|
206
|
+
const copied = request.parts.filter((part) => !staged.has(part) && publishedParts.has(part));
|
|
207
|
+
if (held === null && copied.length === 0) {
|
|
208
|
+
return { key: null, transfer: request.transfer, staged: [] };
|
|
209
|
+
}
|
|
210
|
+
const key = held?.key ?? newKey(connection, request.locator, request.transfer, request.fingerprint, false);
|
|
211
|
+
recomposed(connection, key, request.fingerprint);
|
|
212
|
+
if (copied.length > 0 && request.published !== null) {
|
|
213
|
+
owing(connection, key);
|
|
214
|
+
const named = JSON.stringify(copied);
|
|
215
|
+
connection.run(`INSERT INTO replica_entries (key, layer, path, visible, present, queried, content, binary, revision, file_id, created, change, written, type, declared, folder, fields, links, part) SELECT ?, ${CONFIRMED}, path, 1, 1, queried, content, binary, revision, file_id, created, change, written, type, declared, folder, fields, links, part FROM replica_entries WHERE key = ? AND layer = ${CONFIRMED} AND present = 1 AND part IN (SELECT value FROM json_each(?)) ORDER BY path`, [key, request.published, named]);
|
|
216
|
+
connection.run(`INSERT OR REPLACE INTO replica_folders (key, layer, path, present, part) SELECT ?, ${CONFIRMED}, path, 1, part FROM replica_folders WHERE key = ? AND layer = ${CONFIRMED} AND present = 1 AND part IN (SELECT value FROM json_each(?))`, [key, request.published, named]);
|
|
217
|
+
}
|
|
218
|
+
return { key, transfer: held?.transfer ?? request.transfer, staged: [...kept, ...copied] };
|
|
219
|
+
}
|
|
208
220
|
export function refreshing(connection, request) {
|
|
209
|
-
const held = connection.rows('SELECT 1 FROM
|
|
221
|
+
const held = connection.rows('SELECT 1 FROM replica_keys k WHERE k.key = ? AND NOT EXISTS (SELECT 1 FROM replica_erasing e WHERE e.key = k.key)', [request.key]);
|
|
210
222
|
if (held.length === 0)
|
|
211
223
|
return null;
|
|
212
|
-
|
|
224
|
+
recomposed(connection, request.key, request.composed.fingerprint);
|
|
225
|
+
const composition = request.composed.composition;
|
|
213
226
|
for (const entry of request.entries) {
|
|
214
227
|
putEntry(connection, request.key, REFRESHED, entry, true, composition);
|
|
215
228
|
}
|
|
@@ -220,8 +233,10 @@ export function refreshing(connection, request) {
|
|
|
220
233
|
binary: null,
|
|
221
234
|
revision: null,
|
|
222
235
|
fileId: null,
|
|
236
|
+
created: null,
|
|
223
237
|
change: null,
|
|
224
238
|
written: null,
|
|
239
|
+
facts: null,
|
|
225
240
|
}, false, composition);
|
|
226
241
|
}
|
|
227
242
|
for (const [list, present] of [
|
|
@@ -235,7 +250,6 @@ export function refreshing(connection, request) {
|
|
|
235
250
|
return null;
|
|
236
251
|
}
|
|
237
252
|
function dropLayer(connection, key, layer) {
|
|
238
|
-
unindexDocuments(connection, heldIds(connection, 'SELECT id FROM replica_entries WHERE key = ? AND layer = ?', [key, layer]));
|
|
239
253
|
for (const table of ['replica_values', 'replica_links']) {
|
|
240
254
|
connection.run(`DELETE FROM ${table} WHERE doc IN (SELECT id FROM replica_entries WHERE key = ? AND layer = ?)`, [key, layer]);
|
|
241
255
|
}
|
|
@@ -254,7 +268,6 @@ function refreshedPaths(connection, key) {
|
|
|
254
268
|
function applyRefresh(connection, key) {
|
|
255
269
|
const touched = refreshedPaths(connection, key);
|
|
256
270
|
const replaced = `SELECT c.id FROM replica_entries c JOIN replica_entries r ON r.key = c.key AND r.layer = ${REFRESHED} AND r.path = c.path WHERE c.key = ? AND c.layer = ${CONFIRMED}`;
|
|
257
|
-
unindexDocuments(connection, heldIds(connection, replaced, [key]));
|
|
258
271
|
for (const table of ['replica_values', 'replica_links']) {
|
|
259
272
|
connection.run(`DELETE FROM ${table} WHERE doc IN (${replaced})`, [key]);
|
|
260
273
|
}
|
|
@@ -273,33 +286,33 @@ function applyRefresh(connection, key) {
|
|
|
273
286
|
connection.run('DELETE FROM replica_folders WHERE key = ? AND layer = ?', [key, REFRESHED]);
|
|
274
287
|
return touched;
|
|
275
288
|
}
|
|
289
|
+
function beneathRoots(path) {
|
|
290
|
+
return `EXISTS (SELECT 1 FROM json_each(?) r WHERE ${path} = r.value OR (${path} >= r.value || '/' AND ${path} < r.value || '0'))`;
|
|
291
|
+
}
|
|
276
292
|
function counted(connection, key, roots) {
|
|
277
|
-
const
|
|
278
|
-
const bind = [key];
|
|
279
|
-
for (const root of roots)
|
|
280
|
-
bind.push(root, `${root}/`, `${root}0`);
|
|
281
|
-
const [row] = connection.rows(`SELECT count(*) AS held FROM replica_entries WHERE key = ? AND visible = 1 AND present = 1 AND (binary IS NOT NULL OR substr(path, -3) = '.md')${roots.length === 0 ? '' : ` AND NOT (${outside})`}`, bind);
|
|
293
|
+
const [row] = connection.rows(`SELECT count(*) AS held FROM replica_entries WHERE key = ? AND visible = 1 AND present = 1 AND (binary IS NOT NULL OR substr(path, -3) = '.md') AND NOT ${beneathRoots('replica_entries.path')}`, [key, JSON.stringify(roots)]);
|
|
282
294
|
return number(row.held);
|
|
283
295
|
}
|
|
284
|
-
export function counts(connection, rooms) {
|
|
285
|
-
return rooms.map(({ key, roots }) => counted(connection, key, roots));
|
|
286
|
-
}
|
|
287
296
|
function erasing(connection, key, locator) {
|
|
288
297
|
connection.run('INSERT OR REPLACE INTO replica_erasing (key, locator) VALUES (?, ?)', [
|
|
289
298
|
key,
|
|
290
299
|
locator,
|
|
291
300
|
]);
|
|
292
301
|
}
|
|
302
|
+
function saved(connection, key, entry, at) {
|
|
303
|
+
if (!entry.present || entry.created !== null)
|
|
304
|
+
return entry;
|
|
305
|
+
const [held] = connection.rows(`SELECT created FROM replica_entries WHERE key = ? AND layer = ${OVERLAY} AND path = ? AND present = 1`, [key, entry.path]);
|
|
306
|
+
return { ...entry, created: text(held?.created) ?? at };
|
|
307
|
+
}
|
|
293
308
|
function commitRoom(connection, room) {
|
|
294
309
|
const touched = new Set();
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
dropTransfer(connection, room.locator);
|
|
310
|
+
recomposed(connection, room.key, room.composed.fingerprint);
|
|
311
|
+
const composition = room.composed.composition;
|
|
312
|
+
if (room.published !== undefined &&
|
|
313
|
+
room.published.from !== null &&
|
|
314
|
+
room.published.from !== room.key) {
|
|
315
|
+
erasing(connection, room.published.from, room.locator);
|
|
303
316
|
}
|
|
304
317
|
if (room.refreshed === true) {
|
|
305
318
|
for (const path of applyRefresh(connection, room.key))
|
|
@@ -313,8 +326,9 @@ function commitRoom(connection, room) {
|
|
|
313
326
|
removeEntry(connection, room.key, CONFIRMED, path);
|
|
314
327
|
touched.add(path);
|
|
315
328
|
}
|
|
329
|
+
const savedAt = new Date().toISOString();
|
|
316
330
|
for (const entry of room.overlay?.set ?? []) {
|
|
317
|
-
putEntry(connection, room.key, OVERLAY, entry, entry.present, composition);
|
|
331
|
+
putEntry(connection, room.key, OVERLAY, saved(connection, room.key, entry, savedAt), entry.present, composition);
|
|
318
332
|
touched.add(entry.path);
|
|
319
333
|
}
|
|
320
334
|
for (const path of room.overlay?.cleared ?? []) {
|
|
@@ -324,27 +338,44 @@ function commitRoom(connection, room) {
|
|
|
324
338
|
const changed = [...touched];
|
|
325
339
|
revealed(connection, room.key, changed);
|
|
326
340
|
if (room.record !== undefined) {
|
|
327
|
-
connection.run(
|
|
328
|
-
|
|
329
|
-
room.key,
|
|
330
|
-
room.record,
|
|
331
|
-
]);
|
|
341
|
+
connection.run(KEEP_MODEL, [room.record, room.record, room.record, room.record]);
|
|
342
|
+
connection.run(`INSERT OR REPLACE INTO replica_rooms (locator, key, record) VALUES (?, ?, ${MODEL_LEFT})`, [room.locator, room.key, room.record, room.record, room.record]);
|
|
332
343
|
}
|
|
333
344
|
return room.published === undefined ? changed : null;
|
|
334
345
|
}
|
|
346
|
+
const SCHEMA = '$.model.snapshot.schema';
|
|
347
|
+
const KEEP_MODEL = `INSERT OR IGNORE INTO replica_models (identity, version, schema)
|
|
348
|
+
SELECT json_extract(?, '$.model.snapshot.identity'), json_extract(?, '$.model.snapshot.version'),
|
|
349
|
+
json_extract(?, '${SCHEMA}')
|
|
350
|
+
WHERE json_type(?, '${SCHEMA}') = 'text'`;
|
|
351
|
+
const MODEL_LEFT = `CASE WHEN json_type(?, '${SCHEMA}') = 'text' THEN json_set(?, '${SCHEMA}', json('null')) ELSE ? END`;
|
|
352
|
+
const UNHELD_MODELS = `DELETE FROM replica_models WHERE NOT EXISTS (
|
|
353
|
+
SELECT 1 FROM replica_rooms r
|
|
354
|
+
WHERE json_extract(r.record, '$.model.snapshot.identity') = replica_models.identity
|
|
355
|
+
AND json_extract(r.record, '$.model.snapshot.version') = replica_models.version
|
|
356
|
+
)`;
|
|
357
|
+
const HELD_RECORDS = `SELECT r.locator, r.key, CASE WHEN m.schema IS NULL THEN r.record
|
|
358
|
+
ELSE json_set(r.record, '${SCHEMA}', m.schema) END AS record
|
|
359
|
+
FROM replica_rooms r
|
|
360
|
+
LEFT JOIN replica_models m
|
|
361
|
+
ON m.identity = json_extract(r.record, '$.model.snapshot.identity')
|
|
362
|
+
AND m.version = json_extract(r.record, '$.model.snapshot.version')`;
|
|
335
363
|
export function commit(connection, rooms) {
|
|
336
|
-
const counted = {};
|
|
337
364
|
const changed = {};
|
|
338
365
|
const indexed = {};
|
|
339
366
|
for (const room of rooms) {
|
|
340
367
|
changed[room.locator] = commitRoom(connection, room);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
368
|
+
connection.run('UPDATE replica_rooms SET documents = ? WHERE locator = ? AND key = ?', [
|
|
369
|
+
counted(connection, room.key, room.roots ?? rootsOf(connection, room.locator)),
|
|
370
|
+
room.locator,
|
|
371
|
+
room.key,
|
|
372
|
+
]);
|
|
344
373
|
const [held] = connection.rows('SELECT rederiving FROM replica_keys WHERE key = ?', [room.key]);
|
|
345
374
|
indexed[room.locator] = held !== undefined && held.rederiving === null;
|
|
346
375
|
}
|
|
347
|
-
|
|
376
|
+
if (rooms.some((room) => room.record !== undefined))
|
|
377
|
+
connection.run(UNHELD_MODELS);
|
|
378
|
+
return { changed, indexed };
|
|
348
379
|
}
|
|
349
380
|
function rootsOf(connection, locator) {
|
|
350
381
|
const [row] = connection.rows('SELECT record FROM replica_rooms WHERE locator = ?', [locator]);
|
|
@@ -353,27 +384,25 @@ function rootsOf(connection, locator) {
|
|
|
353
384
|
}
|
|
354
385
|
export function fenceRooms(connection, locators) {
|
|
355
386
|
for (const locator of locators) {
|
|
356
|
-
for (const row of connection.rows('SELECT key FROM replica_keys WHERE locator = ? UNION SELECT key FROM replica_rooms WHERE locator = ?
|
|
387
|
+
for (const row of connection.rows('SELECT key FROM replica_keys WHERE locator = ? UNION SELECT key FROM replica_rooms WHERE locator = ?', [locator, locator])) {
|
|
357
388
|
erasing(connection, number(row.key), locator);
|
|
358
389
|
}
|
|
359
390
|
connection.run('DELETE FROM replica_rooms WHERE locator = ?', [locator]);
|
|
360
|
-
dropTransfer(connection, locator);
|
|
361
391
|
}
|
|
392
|
+
connection.run(UNHELD_MODELS);
|
|
362
393
|
return null;
|
|
363
394
|
}
|
|
364
|
-
export function abandon(connection, locator
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
return number(held.key);
|
|
395
|
+
export function abandon(connection, locator) {
|
|
396
|
+
for (const row of connection.rows(`SELECT k.key FROM replica_keys k WHERE k.locator = ?
|
|
397
|
+
AND NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)
|
|
398
|
+
AND NOT EXISTS (SELECT 1 FROM replica_erasing e WHERE e.key = k.key)`, [locator])) {
|
|
399
|
+
erasing(connection, number(row.key), locator);
|
|
400
|
+
}
|
|
401
|
+
return null;
|
|
372
402
|
}
|
|
373
403
|
export function release(connection, key) {
|
|
374
404
|
const [held] = connection.rows(`SELECT k.locator FROM replica_keys k WHERE k.key = ?
|
|
375
|
-
AND NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)
|
|
376
|
-
AND NOT EXISTS (SELECT 1 FROM replica_transfers t WHERE t.key = k.key)`, [key]);
|
|
405
|
+
AND NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)`, [key]);
|
|
377
406
|
if (held !== undefined)
|
|
378
407
|
erasing(connection, key, text(held.locator));
|
|
379
408
|
return null;
|
|
@@ -384,7 +413,6 @@ export function erase(connection, locator, limit) {
|
|
|
384
413
|
return { remaining: false };
|
|
385
414
|
const key = number(held.key);
|
|
386
415
|
const batch = 'SELECT id FROM replica_entries WHERE key = ? ORDER BY id LIMIT ?';
|
|
387
|
-
unindexDocuments(connection, heldIds(connection, batch, [key, limit]));
|
|
388
416
|
for (const table of ['replica_values', 'replica_links']) {
|
|
389
417
|
connection.run(`DELETE FROM ${table} WHERE doc IN (${batch})`, [key, limit]);
|
|
390
418
|
}
|
|
@@ -398,28 +426,46 @@ export function erase(connection, locator, limit) {
|
|
|
398
426
|
const [more] = connection.rows(`SELECT 1 FROM replica_erasing${locator === null ? '' : ' WHERE locator = ?'} LIMIT 1`, locator === null ? [] : [locator]);
|
|
399
427
|
return { remaining: more !== undefined };
|
|
400
428
|
}
|
|
401
|
-
|
|
429
|
+
function cursor(value) {
|
|
430
|
+
return value === null || value === undefined ? null : number(value);
|
|
431
|
+
}
|
|
432
|
+
export function rederive(connection, key, composed, bytes, ms, floor) {
|
|
433
|
+
const started = performance.now();
|
|
434
|
+
recomposed(connection, key, composed.fingerprint);
|
|
402
435
|
const [held] = connection.rows('SELECT rederiving FROM replica_keys WHERE key = ?', [key]);
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const composition =
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
436
|
+
const deriving = cursor(held?.rederiving);
|
|
437
|
+
if (deriving === null)
|
|
438
|
+
return { done: true, text: 0 };
|
|
439
|
+
const composition = composed.composition;
|
|
440
|
+
let last = deriving;
|
|
441
|
+
let spent = 0;
|
|
442
|
+
let built = 0;
|
|
443
|
+
let read = false;
|
|
444
|
+
reading: for (;;) {
|
|
445
|
+
const rows = connection.rows('SELECT id, path, type, fields, links, octet_length(content) AS size FROM replica_entries WHERE key = ? AND present = 1 AND id > ? ORDER BY id LIMIT ?', [key, last, DERIVED_ROWS]);
|
|
446
|
+
for (const row of rows) {
|
|
447
|
+
const id = number(row.id);
|
|
448
|
+
const path = text(row.path);
|
|
449
|
+
const size = row.size === null ? 0 : number(row.size);
|
|
450
|
+
dropDerived(connection, id);
|
|
451
|
+
writeDerived(connection, id, key, path, {
|
|
452
|
+
type: text(row.type),
|
|
453
|
+
fields: json(row.fields),
|
|
454
|
+
links: json(row.links) ?? [],
|
|
455
|
+
}, composition);
|
|
456
|
+
built += size;
|
|
457
|
+
last = id;
|
|
458
|
+
spent += ENTRY_WEIGHT + size;
|
|
459
|
+
if (spent >= bytes || (spent >= floor && performance.now() - started >= ms))
|
|
460
|
+
break reading;
|
|
461
|
+
}
|
|
462
|
+
if (rows.length < DERIVED_ROWS) {
|
|
463
|
+
read = true;
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
415
466
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
connection.run('UPDATE replica_keys SET rederiving = ? WHERE key = ?', [
|
|
419
|
-
done ? null : number(last.id),
|
|
420
|
-
key,
|
|
421
|
-
]);
|
|
422
|
-
return { done };
|
|
467
|
+
connection.run('UPDATE replica_keys SET rederiving = ? WHERE key = ?', [read ? null : last, key]);
|
|
468
|
+
return { done: read, text: built };
|
|
423
469
|
}
|
|
424
470
|
function heldEntry(row) {
|
|
425
471
|
return {
|
|
@@ -428,18 +474,33 @@ function heldEntry(row) {
|
|
|
428
474
|
binary: json(row.binary),
|
|
429
475
|
revision: text(row.revision),
|
|
430
476
|
fileId: text(row.file_id),
|
|
477
|
+
created: text(row.created),
|
|
431
478
|
change: json(row.change),
|
|
432
479
|
written: text(row.written),
|
|
433
480
|
type: text(row.type),
|
|
481
|
+
facts: factsHeld(row),
|
|
434
482
|
};
|
|
435
483
|
}
|
|
436
|
-
|
|
484
|
+
function factsHeld(row) {
|
|
485
|
+
const type = text(row.declared);
|
|
486
|
+
if (type === null)
|
|
487
|
+
return null;
|
|
488
|
+
return {
|
|
489
|
+
type,
|
|
490
|
+
fields: json(row.fields) ?? {},
|
|
491
|
+
links: json(row.links) ?? [],
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
const ENTRY_COLUMNS = 'e.path, e.content, e.binary, e.revision, e.file_id, e.created, e.change, e.written, e.type, e.declared, e.fields, e.links';
|
|
495
|
+
function unrefreshed(alias) {
|
|
496
|
+
return `NOT EXISTS (SELECT 1 FROM replica_entries r WHERE r.key = ${alias}.key AND r.layer = ${REFRESHED} AND r.path = ${alias}.path)`;
|
|
497
|
+
}
|
|
437
498
|
function viewed(view, alias) {
|
|
438
499
|
if (view === 'visible')
|
|
439
500
|
return `${alias}.visible = 1 AND ${alias}.present = 1`;
|
|
440
501
|
if (view === 'confirmed')
|
|
441
502
|
return `${alias}.layer = ${CONFIRMED}`;
|
|
442
|
-
return `${alias}.present = 1 AND (${alias}.layer = ${REFRESHED} OR (${alias}.layer = ${CONFIRMED} AND
|
|
503
|
+
return `${alias}.present = 1 AND (${alias}.layer = ${REFRESHED} OR (${alias}.layer = ${CONFIRMED} AND ${unrefreshed(alias)}))`;
|
|
443
504
|
}
|
|
444
505
|
export function entries(connection, key, view, list) {
|
|
445
506
|
if (list.length === 0)
|
|
@@ -459,103 +520,111 @@ export function typed(connection, key, view, list) {
|
|
|
459
520
|
binary: number(row.binary) === 1,
|
|
460
521
|
}));
|
|
461
522
|
}
|
|
462
|
-
export function referrers(connection, key, view, targets
|
|
523
|
+
export function referrers(connection, key, view, targets) {
|
|
463
524
|
if (targets.length === 0)
|
|
464
525
|
return [];
|
|
465
526
|
return connection
|
|
466
|
-
.rows(`SELECT DISTINCT e.path, l.target
|
|
527
|
+
.rows(`SELECT DISTINCT e.path, l.target FROM replica_links l JOIN replica_entries e ON e.id = l.doc WHERE l.key = ? AND l.target IN (SELECT value FROM json_each(?)) AND ${viewed(view, 'e')}`, [key, paths(targets)])
|
|
528
|
+
.map((row) => ({ path: text(row.path), target: text(row.target) }));
|
|
529
|
+
}
|
|
530
|
+
export function folders(connection, key, view, list) {
|
|
531
|
+
return list.filter((path) => connection.rows(`SELECT 1 FROM replica_folders f WHERE f.key = ? AND f.layer = ${CONFIRMED} AND f.present = 1 AND f.path = ? UNION ALL SELECT 1 FROM replica_entries e WHERE e.key = ? AND ${viewed(view, 'e')} AND e.path >= ? AND e.path < ? LIMIT 1`, [key, path, key, `${path}/`, `${path}0`]).length > 0);
|
|
532
|
+
}
|
|
533
|
+
export function typedWithin(connection, key, view, roots, within) {
|
|
534
|
+
return (connection.rows(`SELECT 1 FROM replica_entries e WHERE e.key = ? AND ${viewed(view, 'e')} AND e.declared IS NOT NULL AND e.declared <> 'any' AND ${within ? '' : 'NOT '}${beneathRoots('e.path')} LIMIT 1`, [key, JSON.stringify(roots)]).length > 0);
|
|
535
|
+
}
|
|
536
|
+
const ORPHANS = `SELECT k.key FROM replica_keys k
|
|
537
|
+
WHERE NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)
|
|
538
|
+
AND NOT EXISTS (SELECT 1 FROM replica_erasing e WHERE e.key = k.key)
|
|
539
|
+
AND (${UNPARTED} OR NOT EXISTS (
|
|
540
|
+
SELECT 1 FROM replica_entries e WHERE e.key = k.key
|
|
541
|
+
UNION ALL SELECT 1 FROM replica_folders f WHERE f.key = k.key
|
|
542
|
+
))`;
|
|
543
|
+
const STAGED = `SELECT DISTINCT k.locator FROM replica_keys k
|
|
544
|
+
WHERE NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)
|
|
545
|
+
AND NOT EXISTS (SELECT 1 FROM replica_erasing e WHERE e.key = k.key)
|
|
546
|
+
AND k.key NOT IN (${ORPHANS})`;
|
|
547
|
+
export function opened(connection) {
|
|
548
|
+
return {
|
|
549
|
+
orphans: connection.rows(ORPHANS).map((row) => number(row.key)),
|
|
550
|
+
erasing: connection.rows('SELECT 1 FROM replica_erasing LIMIT 1').length > 0,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
const ROOM_ROWS = `SELECT h.locator, h.key, h.record, r.documents, k.rederiving IS NULL AS indexed
|
|
554
|
+
FROM (${HELD_RECORDS}) h
|
|
555
|
+
JOIN replica_rooms r ON r.locator = h.locator
|
|
556
|
+
LEFT JOIN replica_keys k ON k.key = h.key`;
|
|
557
|
+
function roomRow(row) {
|
|
558
|
+
return {
|
|
559
|
+
locator: text(row.locator),
|
|
560
|
+
key: number(row.key),
|
|
561
|
+
record: text(row.record),
|
|
562
|
+
indexed: number(row.indexed) === 1,
|
|
563
|
+
documents: number(row.documents),
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
export function heldRooms(connection, locators) {
|
|
567
|
+
return (locators === null
|
|
568
|
+
? connection.rows(`${ROOM_ROWS} ORDER BY h.locator`)
|
|
569
|
+
: connection.rows(`${ROOM_ROWS} WHERE h.locator IN (SELECT value FROM json_each(?)) ORDER BY h.locator`, [JSON.stringify(locators)])).map(roomRow);
|
|
570
|
+
}
|
|
571
|
+
export function heldRoom(connection, locator) {
|
|
572
|
+
return heldRooms(connection, [locator])[0] ?? null;
|
|
573
|
+
}
|
|
574
|
+
export function overlaid(connection, key) {
|
|
575
|
+
return connection
|
|
576
|
+
.rows(`SELECT path FROM replica_entries WHERE key = ? AND layer = ${OVERLAY}`, [key])
|
|
577
|
+
.map((row) => text(row.path));
|
|
578
|
+
}
|
|
579
|
+
export function covering(connection) {
|
|
580
|
+
return connection
|
|
581
|
+
.rows(`SELECT r.locator, r.key, r.documents, json_extract(r.record, '$.transfer') AS transfer,
|
|
582
|
+
json_extract(r.record, '$.roomVersion') AS room_version,
|
|
583
|
+
coalesce(json_extract(r.record, '$.inventory.bytes'), 0) AS bytes,
|
|
584
|
+
json_extract(r.record, '$.replica.offline') AS offline,
|
|
585
|
+
k.rederiving IS NULL AS indexed
|
|
586
|
+
FROM replica_rooms r LEFT JOIN replica_keys k ON k.key = r.key ORDER BY r.locator`)
|
|
467
587
|
.map((row) => ({
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
588
|
+
locator: text(row.locator),
|
|
589
|
+
key: number(row.key),
|
|
590
|
+
transfer: text(row.transfer),
|
|
591
|
+
roomVersion: text(row.room_version),
|
|
592
|
+
indexed: number(row.indexed) === 1,
|
|
593
|
+
documents: number(row.documents),
|
|
594
|
+
bytes: number(row.bytes),
|
|
595
|
+
offline: text(row.offline),
|
|
471
596
|
}));
|
|
472
597
|
}
|
|
473
|
-
export function
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
let from = after;
|
|
477
|
-
for (;;) {
|
|
478
|
-
const rows = connection.rows(`SELECT ${ENTRY_COLUMNS} FROM replica_entries e WHERE e.key = ? AND ${viewed(view, 'e')}${from === null ? '' : ' AND e.path > ?'} ORDER BY e.path LIMIT 64`, from === null ? [key] : [key, from]);
|
|
479
|
-
for (const row of rows) {
|
|
480
|
-
const entry = heldEntry(row);
|
|
481
|
-
found.push(entry);
|
|
482
|
-
spent += 256 + (entry.content?.length ?? 0) * 2;
|
|
483
|
-
if (spent >= bytes)
|
|
484
|
-
return { entries: found, next: entry.path };
|
|
485
|
-
}
|
|
486
|
-
if (rows.length < 64)
|
|
487
|
-
return { entries: found, next: null };
|
|
488
|
-
from = found.at(-1).path;
|
|
489
|
-
}
|
|
598
|
+
export function offlineMode(connection, locator) {
|
|
599
|
+
const [held] = connection.rows("SELECT json_extract(record, '$.replica.offline') AS offline FROM replica_rooms WHERE locator = ?", [locator]);
|
|
600
|
+
return text(held?.offline);
|
|
490
601
|
}
|
|
491
|
-
export function
|
|
492
|
-
return connection
|
|
493
|
-
.rows(`SELECT e.path, e.revision FROM replica_entries e WHERE e.key = ? AND ${viewed(view, 'e')} AND e.content IS NOT NULL AND e.revision IS NOT NULL${after === null ? '' : ' AND e.path > ?'} ORDER BY e.path LIMIT ?`, after === null ? [key, limit] : [key, after, limit])
|
|
494
|
-
.map((row) => ({ path: text(row.path), revision: text(row.revision) }));
|
|
602
|
+
export function stagedLocators(connection) {
|
|
603
|
+
return connection.rows(STAGED).map((row) => text(row.locator));
|
|
495
604
|
}
|
|
496
|
-
export function
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
504
|
-
export function
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
export function keepPart(connection, request) {
|
|
509
|
-
connection.run('DELETE FROM replica_transfer_parts WHERE locator = ? AND token = ?', [
|
|
510
|
-
request.locator,
|
|
511
|
-
request.token,
|
|
512
|
-
]);
|
|
513
|
-
connection.run('INSERT INTO replica_transfer_parts (locator, token, part) SELECT ?, ?, ? WHERE EXISTS (SELECT 1 FROM replica_transfers WHERE locator = ?)', [request.locator, request.token, request.part, request.locator]);
|
|
605
|
+
export function heldLocators(connection) {
|
|
606
|
+
return connection
|
|
607
|
+
.rows(`SELECT locator FROM replica_rooms
|
|
608
|
+
UNION SELECT locator FROM authored_room_drafts
|
|
609
|
+
UNION SELECT locator FROM authored_queues
|
|
610
|
+
UNION ${STAGED}`)
|
|
611
|
+
.map((row) => text(row.locator));
|
|
612
|
+
}
|
|
613
|
+
export function demoted(connection) {
|
|
614
|
+
connection.run(`UPDATE replica_rooms SET record = json_set(record, '$.model.status', 'cached')
|
|
615
|
+
WHERE json_extract(record, '$.model.status') = 'current'`);
|
|
514
616
|
return null;
|
|
515
617
|
}
|
|
516
|
-
export function
|
|
517
|
-
const
|
|
618
|
+
export function facts(connection) {
|
|
619
|
+
const held = {};
|
|
518
620
|
for (const row of connection.rows('SELECT name, value FROM replica_facts')) {
|
|
519
|
-
|
|
621
|
+
held[text(row.name)] = text(row.value);
|
|
520
622
|
}
|
|
521
|
-
return
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
})),
|
|
527
|
-
transfers: connection.rows('SELECT locator, key, record FROM replica_transfers').map((row) => ({
|
|
528
|
-
locator: text(row.locator),
|
|
529
|
-
key: number(row.key),
|
|
530
|
-
record: text(row.record),
|
|
531
|
-
})),
|
|
532
|
-
keys: connection
|
|
533
|
-
.rows('SELECT key, locator, transfer, rederiving FROM replica_keys')
|
|
534
|
-
.map((row) => ({
|
|
535
|
-
key: number(row.key),
|
|
536
|
-
locator: text(row.locator),
|
|
537
|
-
transfer: text(row.transfer),
|
|
538
|
-
rederiving: row.rederiving === null ? null : number(row.rederiving),
|
|
539
|
-
})),
|
|
540
|
-
facts,
|
|
541
|
-
presentation: connection.rows('SELECT app, value FROM replica_presentation').map((row) => ({
|
|
542
|
-
app: text(row.app),
|
|
543
|
-
value: text(row.value),
|
|
544
|
-
})),
|
|
545
|
-
overlaid: connection
|
|
546
|
-
.rows(`SELECT key, path FROM replica_entries WHERE layer = ${OVERLAY}`)
|
|
547
|
-
.map((row) => ({ key: number(row.key), path: text(row.path) })),
|
|
548
|
-
orphans: connection
|
|
549
|
-
.rows(`SELECT k.key FROM replica_keys k
|
|
550
|
-
WHERE NOT EXISTS (SELECT 1 FROM replica_rooms r WHERE r.key = k.key)
|
|
551
|
-
AND NOT EXISTS (SELECT 1 FROM replica_transfers t WHERE t.key = k.key)
|
|
552
|
-
AND NOT EXISTS (SELECT 1 FROM replica_erasing e WHERE e.key = k.key)`)
|
|
553
|
-
.map((row) => number(row.key)),
|
|
554
|
-
erasing: connection.rows('SELECT key, locator FROM replica_erasing').map((row) => ({
|
|
555
|
-
key: number(row.key),
|
|
556
|
-
locator: text(row.locator),
|
|
557
|
-
})),
|
|
558
|
-
};
|
|
623
|
+
return held;
|
|
624
|
+
}
|
|
625
|
+
export function presentation(connection, app) {
|
|
626
|
+
const [held] = connection.rows('SELECT value FROM replica_presentation WHERE app = ?', [app]);
|
|
627
|
+
return text(held?.value);
|
|
559
628
|
}
|
|
560
629
|
export function ended(connection) {
|
|
561
630
|
for (const statement of REPLICA_TABLES) {
|
|
@@ -563,5 +632,4 @@ export function ended(connection) {
|
|
|
563
632
|
if (table !== undefined)
|
|
564
633
|
connection.run(`DELETE FROM ${table}`);
|
|
565
634
|
}
|
|
566
|
-
unindexAll(connection);
|
|
567
635
|
}
|