@comapeo/core 2.3.0 → 2.3.2
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/dist/blob-store/entries-stream.d.ts.map +1 -1
- package/dist/blob-store/index.d.ts +27 -15
- package/dist/blob-store/index.d.ts.map +1 -1
- package/dist/core-manager/index.d.ts +4 -4
- package/dist/core-manager/index.d.ts.map +1 -1
- package/dist/datastore/index.d.ts +1 -1
- package/dist/datatype/index.d.ts +141 -111
- package/dist/datatype/index.d.ts.map +1 -0
- package/dist/fastify-plugins/utils.d.ts +0 -9
- package/dist/fastify-plugins/utils.d.ts.map +1 -1
- package/dist/generated/extensions.d.ts +7 -0
- package/dist/generated/extensions.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/mapeo-project.d.ts +17 -17
- package/dist/mapeo-project.d.ts.map +1 -1
- package/dist/schema/project.d.ts +1 -1
- package/dist/sync/core-sync-state.d.ts +14 -6
- package/dist/sync/core-sync-state.d.ts.map +1 -1
- package/dist/sync/namespace-sync-state.d.ts +3 -13
- package/dist/sync/namespace-sync-state.d.ts.map +1 -1
- package/dist/sync/sync-api.d.ts +17 -25
- package/dist/sync/sync-api.d.ts.map +1 -1
- package/dist/sync/sync-state.d.ts +3 -13
- package/dist/sync/sync-state.d.ts.map +1 -1
- package/dist/translation-api.d.ts +2 -2
- package/dist/translation-api.d.ts.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -5
- package/src/blob-store/entries-stream.js +43 -18
- package/src/blob-store/index.js +161 -19
- package/src/core-manager/index.js +14 -9
- package/src/datatype/index.js +57 -19
- package/src/fastify-plugins/maps.js +1 -0
- package/src/fastify-plugins/utils.js +0 -13
- package/src/generated/extensions.d.ts +7 -0
- package/src/generated/extensions.js +12 -2
- package/src/generated/extensions.ts +19 -1
- package/src/mapeo-project.js +7 -76
- package/src/sync/core-sync-state.js +41 -14
- package/src/sync/namespace-sync-state.js +25 -22
- package/src/sync/sync-api.js +12 -43
- package/src/sync/sync-state.js +9 -19
- package/src/translation-api.js +2 -1
- package/src/types.ts +1 -1
- package/src/datatype/index.d.ts +0 -112
|
@@ -170,7 +170,7 @@ export var HaveExtension = {
|
|
|
170
170
|
},
|
|
171
171
|
};
|
|
172
172
|
function createBaseDownloadIntentExtension() {
|
|
173
|
-
return { downloadIntents: {} };
|
|
173
|
+
return { downloadIntents: {}, everything: false };
|
|
174
174
|
}
|
|
175
175
|
export var DownloadIntentExtension = {
|
|
176
176
|
encode: function (message, writer) {
|
|
@@ -180,6 +180,9 @@ export var DownloadIntentExtension = {
|
|
|
180
180
|
DownloadIntentExtension_DownloadIntentsEntry.encode({ key: key, value: value }, writer.uint32(10).fork())
|
|
181
181
|
.ldelim();
|
|
182
182
|
});
|
|
183
|
+
if (message.everything === true) {
|
|
184
|
+
writer.uint32(16).bool(message.everything);
|
|
185
|
+
}
|
|
183
186
|
return writer;
|
|
184
187
|
},
|
|
185
188
|
decode: function (input, length) {
|
|
@@ -198,6 +201,12 @@ export var DownloadIntentExtension = {
|
|
|
198
201
|
message.downloadIntents[entry1.key] = entry1.value;
|
|
199
202
|
}
|
|
200
203
|
continue;
|
|
204
|
+
case 2:
|
|
205
|
+
if (tag !== 16) {
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
message.everything = reader.bool();
|
|
209
|
+
continue;
|
|
201
210
|
}
|
|
202
211
|
if ((tag & 7) === 4 || tag === 0) {
|
|
203
212
|
break;
|
|
@@ -210,7 +219,7 @@ export var DownloadIntentExtension = {
|
|
|
210
219
|
return DownloadIntentExtension.fromPartial(base !== null && base !== void 0 ? base : {});
|
|
211
220
|
},
|
|
212
221
|
fromPartial: function (object) {
|
|
213
|
-
var _a;
|
|
222
|
+
var _a, _b;
|
|
214
223
|
var message = createBaseDownloadIntentExtension();
|
|
215
224
|
message.downloadIntents = Object.entries((_a = object.downloadIntents) !== null && _a !== void 0 ? _a : {}).reduce(function (acc, _a) {
|
|
216
225
|
var key = _a[0], value = _a[1];
|
|
@@ -219,6 +228,7 @@ export var DownloadIntentExtension = {
|
|
|
219
228
|
}
|
|
220
229
|
return acc;
|
|
221
230
|
}, {});
|
|
231
|
+
message.everything = (_b = object.everything) !== null && _b !== void 0 ? _b : false;
|
|
222
232
|
return message;
|
|
223
233
|
},
|
|
224
234
|
};
|
|
@@ -69,6 +69,13 @@ export function haveExtension_NamespaceToNumber(object: HaveExtension_Namespace)
|
|
|
69
69
|
/** A map of blob types and variants that a peer intends to download */
|
|
70
70
|
export interface DownloadIntentExtension {
|
|
71
71
|
downloadIntents: { [key: string]: DownloadIntentExtension_DownloadIntent };
|
|
72
|
+
/**
|
|
73
|
+
* If true, the peer intends to download all blobs - this is the default
|
|
74
|
+
* assumption when a peer has not sent a download intent, but if a peer
|
|
75
|
+
* changes their intent while connected, we need to send the new intent to
|
|
76
|
+
* download everything.
|
|
77
|
+
*/
|
|
78
|
+
everything: boolean;
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
export interface DownloadIntentExtension_DownloadIntent {
|
|
@@ -209,7 +216,7 @@ export const HaveExtension = {
|
|
|
209
216
|
};
|
|
210
217
|
|
|
211
218
|
function createBaseDownloadIntentExtension(): DownloadIntentExtension {
|
|
212
|
-
return { downloadIntents: {} };
|
|
219
|
+
return { downloadIntents: {}, everything: false };
|
|
213
220
|
}
|
|
214
221
|
|
|
215
222
|
export const DownloadIntentExtension = {
|
|
@@ -218,6 +225,9 @@ export const DownloadIntentExtension = {
|
|
|
218
225
|
DownloadIntentExtension_DownloadIntentsEntry.encode({ key: key as any, value }, writer.uint32(10).fork())
|
|
219
226
|
.ldelim();
|
|
220
227
|
});
|
|
228
|
+
if (message.everything === true) {
|
|
229
|
+
writer.uint32(16).bool(message.everything);
|
|
230
|
+
}
|
|
221
231
|
return writer;
|
|
222
232
|
},
|
|
223
233
|
|
|
@@ -238,6 +248,13 @@ export const DownloadIntentExtension = {
|
|
|
238
248
|
message.downloadIntents[entry1.key] = entry1.value;
|
|
239
249
|
}
|
|
240
250
|
continue;
|
|
251
|
+
case 2:
|
|
252
|
+
if (tag !== 16) {
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
message.everything = reader.bool();
|
|
257
|
+
continue;
|
|
241
258
|
}
|
|
242
259
|
if ((tag & 7) === 4 || tag === 0) {
|
|
243
260
|
break;
|
|
@@ -260,6 +277,7 @@ export const DownloadIntentExtension = {
|
|
|
260
277
|
}
|
|
261
278
|
return acc;
|
|
262
279
|
}, {});
|
|
280
|
+
message.everything = object.everything ?? false;
|
|
263
281
|
return message;
|
|
264
282
|
},
|
|
265
283
|
};
|
package/src/mapeo-project.js
CHANGED
|
@@ -49,10 +49,7 @@ import { omit } from './lib/omit.js'
|
|
|
49
49
|
import { MemberApi } from './member-api.js'
|
|
50
50
|
import {
|
|
51
51
|
SyncApi,
|
|
52
|
-
kAddBlobWantRange,
|
|
53
|
-
kClearBlobWantRanges,
|
|
54
52
|
kHandleDiscoveryKey,
|
|
55
|
-
kSetBlobDownloadFilter,
|
|
56
53
|
kWaitForInitialSyncWithPeer,
|
|
57
54
|
} from './sync/sync-api.js'
|
|
58
55
|
import { Logger } from './logger.js'
|
|
@@ -60,9 +57,8 @@ import { IconApi } from './icon-api.js'
|
|
|
60
57
|
import { readConfig } from './config-import.js'
|
|
61
58
|
import TranslationApi from './translation-api.js'
|
|
62
59
|
import { NotFoundError, nullIfNotFound } from './errors.js'
|
|
63
|
-
import { getErrorCode, getErrorMessage } from './lib/error.js'
|
|
64
60
|
/** @import { ProjectSettingsValue } from '@comapeo/schema' */
|
|
65
|
-
/** @import { CoreStorage, BlobFilter, BlobStoreEntriesStream, KeyPair, Namespace, ReplicationStream } from './types.js' */
|
|
61
|
+
/** @import { CoreStorage, BlobFilter, BlobStoreEntriesStream, KeyPair, Namespace, ReplicationStream, GenericBlobFilter } from './types.js' */
|
|
66
62
|
|
|
67
63
|
/** @typedef {Omit<ProjectSettingsValue, 'schemaName'>} EditableProjectSettings */
|
|
68
64
|
/** @typedef {ProjectSettingsValue['configMetadata']} ConfigMetadata */
|
|
@@ -82,13 +78,6 @@ export const kIsArchiveDevice = Symbol('isArchiveDevice (temp - test only)')
|
|
|
82
78
|
|
|
83
79
|
const EMPTY_PROJECT_SETTINGS = Object.freeze({})
|
|
84
80
|
|
|
85
|
-
/** @type {import('./types.js').BlobFilter} */
|
|
86
|
-
const NON_ARCHIVE_DEVICE_DOWNLOAD_FILTER = {
|
|
87
|
-
photo: ['preview', 'thumbnail'],
|
|
88
|
-
// Don't download any audio of video files, since previews and
|
|
89
|
-
// thumbnails aren't supported yet.
|
|
90
|
-
}
|
|
91
|
-
|
|
92
81
|
/**
|
|
93
82
|
* @extends {TypedEmitter<{ close: () => void }>}
|
|
94
83
|
*/
|
|
@@ -112,7 +101,6 @@ export class MapeoProject extends TypedEmitter {
|
|
|
112
101
|
#l
|
|
113
102
|
/** @type {Boolean} this avoids loading multiple configs in parallel */
|
|
114
103
|
#loadingConfig
|
|
115
|
-
#isArchiveDevice
|
|
116
104
|
|
|
117
105
|
static EMPTY_PROJECT_SETTINGS = EMPTY_PROJECT_SETTINGS
|
|
118
106
|
|
|
@@ -154,12 +142,9 @@ export class MapeoProject extends TypedEmitter {
|
|
|
154
142
|
this.#deviceId = getDeviceId(keyManager)
|
|
155
143
|
this.#projectKey = projectKey
|
|
156
144
|
this.#loadingConfig = false
|
|
157
|
-
this.#isArchiveDevice = isArchiveDevice
|
|
158
145
|
|
|
159
146
|
const getReplicationStream = this[kProjectReplicate].bind(this, true)
|
|
160
147
|
|
|
161
|
-
const blobDownloadFilter = getBlobDownloadFilter(isArchiveDevice)
|
|
162
|
-
|
|
163
148
|
///////// 1. Setup database
|
|
164
149
|
|
|
165
150
|
this.#sqlite = new Database(dbPath)
|
|
@@ -376,7 +361,8 @@ export class MapeoProject extends TypedEmitter {
|
|
|
376
361
|
|
|
377
362
|
this.#blobStore = new BlobStore({
|
|
378
363
|
coreManager: this.#coreManager,
|
|
379
|
-
|
|
364
|
+
isArchiveDevice: isArchiveDevice,
|
|
365
|
+
logger: this.#l,
|
|
380
366
|
})
|
|
381
367
|
|
|
382
368
|
this.#blobStore.on('error', (err) => {
|
|
@@ -412,7 +398,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
412
398
|
coreManager: this.#coreManager,
|
|
413
399
|
coreOwnership: this.#coreOwnership,
|
|
414
400
|
roles: this.#roles,
|
|
415
|
-
|
|
401
|
+
blobStore: this.#blobStore,
|
|
416
402
|
logger: this.#l,
|
|
417
403
|
getServerWebsocketUrls: async () => {
|
|
418
404
|
const members = await this.#memberApi.getMany()
|
|
@@ -434,48 +420,6 @@ export class MapeoProject extends TypedEmitter {
|
|
|
434
420
|
getReplicationStream,
|
|
435
421
|
})
|
|
436
422
|
|
|
437
|
-
/** @type {Map<string, BlobStoreEntriesStream>} */
|
|
438
|
-
const entriesReadStreams = new Map()
|
|
439
|
-
|
|
440
|
-
this.#coreManager.on('peer-download-intent', async (filter, peerId) => {
|
|
441
|
-
entriesReadStreams.get(peerId)?.destroy()
|
|
442
|
-
|
|
443
|
-
const entriesReadStream = this.#blobStore.createEntriesReadStream({
|
|
444
|
-
live: true,
|
|
445
|
-
filter,
|
|
446
|
-
})
|
|
447
|
-
entriesReadStreams.set(peerId, entriesReadStream)
|
|
448
|
-
|
|
449
|
-
entriesReadStream.once('close', () => {
|
|
450
|
-
if (entriesReadStreams.get(peerId) === entriesReadStream) {
|
|
451
|
-
entriesReadStreams.delete(peerId)
|
|
452
|
-
}
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
this.#syncApi[kClearBlobWantRanges](peerId)
|
|
456
|
-
|
|
457
|
-
try {
|
|
458
|
-
for await (const entry of entriesReadStream) {
|
|
459
|
-
const { blockOffset, blockLength } = entry.value.blob
|
|
460
|
-
this.#syncApi[kAddBlobWantRange](peerId, blockOffset, blockLength)
|
|
461
|
-
}
|
|
462
|
-
} catch (err) {
|
|
463
|
-
if (getErrorCode(err) === 'ERR_STREAM_PREMATURE_CLOSE') return
|
|
464
|
-
this.#l.log(
|
|
465
|
-
'Error getting blob entries stream for peer %h: %s',
|
|
466
|
-
peerId,
|
|
467
|
-
getErrorMessage(err)
|
|
468
|
-
)
|
|
469
|
-
}
|
|
470
|
-
})
|
|
471
|
-
|
|
472
|
-
this.#coreManager.creatorCore.on('peer-remove', (peer) => {
|
|
473
|
-
const peerKey = peer.protomux.stream.remotePublicKey
|
|
474
|
-
const peerId = peerKey.toString('hex')
|
|
475
|
-
entriesReadStreams.get(peerId)?.destroy()
|
|
476
|
-
entriesReadStreams.delete(peerId)
|
|
477
|
-
})
|
|
478
|
-
|
|
479
423
|
this.#translationApi = new TranslationApi({
|
|
480
424
|
dataType: this.#dataTypes.translation,
|
|
481
425
|
})
|
|
@@ -516,7 +460,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
516
460
|
localPeers.off('discovery-key', onDiscoverykey)
|
|
517
461
|
})
|
|
518
462
|
|
|
519
|
-
this.#l.log('Created project instance %h', projectKey)
|
|
463
|
+
this.#l.log('Created project instance %h, %s', projectKey, isArchiveDevice)
|
|
520
464
|
}
|
|
521
465
|
|
|
522
466
|
/**
|
|
@@ -785,16 +729,12 @@ export class MapeoProject extends TypedEmitter {
|
|
|
785
729
|
|
|
786
730
|
/** @param {boolean} isArchiveDevice */
|
|
787
731
|
async [kSetIsArchiveDevice](isArchiveDevice) {
|
|
788
|
-
|
|
789
|
-
const blobDownloadFilter = getBlobDownloadFilter(isArchiveDevice)
|
|
790
|
-
this.#blobStore.setDownloadFilter(blobDownloadFilter)
|
|
791
|
-
this.#syncApi[kSetBlobDownloadFilter](blobDownloadFilter)
|
|
792
|
-
this.#isArchiveDevice = isArchiveDevice
|
|
732
|
+
this.#blobStore.setIsArchiveDevice(isArchiveDevice)
|
|
793
733
|
}
|
|
794
734
|
|
|
795
735
|
/** @returns {boolean} */
|
|
796
736
|
get [kIsArchiveDevice]() {
|
|
797
|
-
return this.#isArchiveDevice
|
|
737
|
+
return this.#blobStore.isArchiveDevice
|
|
798
738
|
}
|
|
799
739
|
|
|
800
740
|
/**
|
|
@@ -1054,15 +994,6 @@ export class MapeoProject extends TypedEmitter {
|
|
|
1054
994
|
}
|
|
1055
995
|
}
|
|
1056
996
|
}
|
|
1057
|
-
|
|
1058
|
-
/**
|
|
1059
|
-
* @param {boolean} isArchiveDevice
|
|
1060
|
-
* @returns {null | BlobFilter}
|
|
1061
|
-
*/
|
|
1062
|
-
function getBlobDownloadFilter(isArchiveDevice) {
|
|
1063
|
-
return isArchiveDevice ? null : NON_ARCHIVE_DEVICE_DOWNLOAD_FILTER
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
997
|
/**
|
|
1067
998
|
* @param {import("@comapeo/schema").ProjectSettings & { forks: string[] }} projectDoc
|
|
1068
999
|
* @returns {EditableProjectSettings}
|
|
@@ -67,25 +67,41 @@ export class CoreSyncState {
|
|
|
67
67
|
/** @type {InternalState['remoteStates']} */
|
|
68
68
|
#remoteStates = new Map()
|
|
69
69
|
/** @type {InternalState['localState']} */
|
|
70
|
-
#localState
|
|
70
|
+
#localState
|
|
71
71
|
#preHavesLength = 0
|
|
72
72
|
#update
|
|
73
73
|
#peerSyncControllers
|
|
74
74
|
#namespace
|
|
75
|
+
#deviceId
|
|
75
76
|
#l
|
|
77
|
+
#hasDownloadFilter
|
|
76
78
|
|
|
77
79
|
/**
|
|
78
80
|
* @param {object} opts
|
|
79
81
|
* @param {() => void} opts.onUpdate Called when a state update is available (via getState())
|
|
80
82
|
* @param {Map<string, import('./peer-sync-controller.js').PeerSyncController>} opts.peerSyncControllers
|
|
81
83
|
* @param {Namespace} opts.namespace
|
|
84
|
+
* @param {string} opts.deviceId
|
|
85
|
+
* @param {(peerId: string) => boolean} opts.hasDownloadFilter
|
|
82
86
|
* @param {Logger} [opts.logger]
|
|
83
87
|
*/
|
|
84
|
-
constructor({
|
|
88
|
+
constructor({
|
|
89
|
+
onUpdate,
|
|
90
|
+
peerSyncControllers,
|
|
91
|
+
namespace,
|
|
92
|
+
deviceId,
|
|
93
|
+
hasDownloadFilter,
|
|
94
|
+
logger,
|
|
95
|
+
}) {
|
|
85
96
|
// The logger parameter is already namespaced by NamespaceSyncState
|
|
86
97
|
this.#l = logger || Logger.create('css')
|
|
87
98
|
this.#peerSyncControllers = peerSyncControllers
|
|
88
99
|
this.#namespace = namespace
|
|
100
|
+
this.#deviceId = deviceId
|
|
101
|
+
this.#hasDownloadFilter = hasDownloadFilter
|
|
102
|
+
this.#localState = new PeerState({
|
|
103
|
+
wantsEverything: !hasDownloadFilter(deviceId),
|
|
104
|
+
})
|
|
89
105
|
// Called whenever the state changes, so we clear the cache because next
|
|
90
106
|
// call to getState() will need to re-derive the state
|
|
91
107
|
this.#update = () => {
|
|
@@ -187,18 +203,21 @@ export class CoreSyncState {
|
|
|
187
203
|
* @returns {void}
|
|
188
204
|
*/
|
|
189
205
|
addWantRange(peerId, start, length) {
|
|
206
|
+
this.#l.log('Peer %S wants range %d-%d', peerId, start, start + length)
|
|
190
207
|
const peerState = this.#getOrCreatePeerState(peerId)
|
|
191
208
|
peerState.addWantRange(start, length)
|
|
192
209
|
this.#update()
|
|
193
210
|
}
|
|
194
211
|
|
|
195
212
|
/**
|
|
213
|
+
* Set whether a peer wants everything or only blocks specified by addWantRange()
|
|
196
214
|
* @param {PeerId} peerId
|
|
197
|
-
* @
|
|
215
|
+
* @param {boolean} wantsEverything
|
|
198
216
|
*/
|
|
199
|
-
|
|
217
|
+
setWantsEverything(peerId, wantsEverything) {
|
|
218
|
+
this.#l.log('Peer %S wants everything: %s', peerId, wantsEverything)
|
|
200
219
|
const peerState = this.#getOrCreatePeerState(peerId)
|
|
201
|
-
peerState.
|
|
220
|
+
peerState.setWantsEverything(wantsEverything)
|
|
202
221
|
this.#update()
|
|
203
222
|
}
|
|
204
223
|
|
|
@@ -206,8 +225,7 @@ export class CoreSyncState {
|
|
|
206
225
|
* @param {PeerId} peerId
|
|
207
226
|
*/
|
|
208
227
|
addPeer(peerId) {
|
|
209
|
-
|
|
210
|
-
this.#remoteStates.set(peerId, new PeerState())
|
|
228
|
+
this.#getOrCreatePeerState(peerId)
|
|
211
229
|
this.#update()
|
|
212
230
|
}
|
|
213
231
|
|
|
@@ -225,9 +243,12 @@ export class CoreSyncState {
|
|
|
225
243
|
* @param {PeerId} peerId
|
|
226
244
|
*/
|
|
227
245
|
#getOrCreatePeerState(peerId) {
|
|
246
|
+
if (peerId === this.#deviceId) return this.#localState
|
|
228
247
|
let peerState = this.#remoteStates.get(peerId)
|
|
229
248
|
if (!peerState) {
|
|
230
|
-
peerState = new PeerState(
|
|
249
|
+
peerState = new PeerState({
|
|
250
|
+
wantsEverything: !this.#hasDownloadFilter(peerId),
|
|
251
|
+
})
|
|
231
252
|
this.#remoteStates.set(peerId, peerState)
|
|
232
253
|
}
|
|
233
254
|
return peerState
|
|
@@ -305,9 +326,14 @@ export class PeerState {
|
|
|
305
326
|
* What blocks do we want? If `null`, we want everything.
|
|
306
327
|
* @type {null | Bitfield}
|
|
307
328
|
*/
|
|
308
|
-
#wants
|
|
329
|
+
#wants
|
|
309
330
|
/** @type {PeerNamespaceState['status']} */
|
|
310
331
|
status = 'stopped'
|
|
332
|
+
|
|
333
|
+
constructor({ wantsEverything = true } = {}) {
|
|
334
|
+
this.#wants = wantsEverything ? null : new RemoteBitfield()
|
|
335
|
+
}
|
|
336
|
+
|
|
311
337
|
get preHavesBitfield() {
|
|
312
338
|
return this.#preHaves
|
|
313
339
|
}
|
|
@@ -340,11 +366,11 @@ export class PeerState {
|
|
|
340
366
|
this.#wants.setRange(start, length, true)
|
|
341
367
|
}
|
|
342
368
|
/**
|
|
343
|
-
* Set
|
|
344
|
-
*
|
|
369
|
+
* Set whether this peer wants everything or only blocks specified by addWantRange()
|
|
370
|
+
* @param {boolean} wantsEverything
|
|
345
371
|
*/
|
|
346
|
-
|
|
347
|
-
this.#wants = new RemoteBitfield()
|
|
372
|
+
setWantsEverything(wantsEverything) {
|
|
373
|
+
this.#wants = wantsEverything ? null : new RemoteBitfield()
|
|
348
374
|
}
|
|
349
375
|
/**
|
|
350
376
|
* Returns whether the peer has the block at `index`. If a pre-have bitfield
|
|
@@ -427,6 +453,7 @@ export function deriveState(coreState) {
|
|
|
427
453
|
const truncate = 2 ** Math.min(32, length - i) - 1
|
|
428
454
|
|
|
429
455
|
const localHaves = coreState.localState.haveWord(i) & truncate
|
|
456
|
+
const localWants = coreState.localState.wantWord(i) & truncate
|
|
430
457
|
localState.have += bitCount32(localHaves)
|
|
431
458
|
|
|
432
459
|
let someoneElseWantsFromMe = 0
|
|
@@ -440,7 +467,7 @@ export function deriveState(coreState) {
|
|
|
440
467
|
remoteStates[peerId].want += bitCount32(theyWantFromMe)
|
|
441
468
|
someoneElseWantsFromMe |= theyWantFromMe
|
|
442
469
|
|
|
443
|
-
const iWantFromThem = peerHaves & ~localHaves
|
|
470
|
+
const iWantFromThem = peerHaves & ~localHaves & localWants
|
|
444
471
|
remoteStates[peerId].wanted += bitCount32(iWantFromThem)
|
|
445
472
|
iWantFromSomeoneElse |= iWantFromThem
|
|
446
473
|
}
|
|
@@ -20,6 +20,8 @@ export class NamespaceSyncState {
|
|
|
20
20
|
#cachedState = null
|
|
21
21
|
#peerSyncControllers
|
|
22
22
|
#logger
|
|
23
|
+
#deviceId
|
|
24
|
+
#blobStore
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* @param {object} opts
|
|
@@ -27,6 +29,7 @@ export class NamespaceSyncState {
|
|
|
27
29
|
* @param {import('../core-manager/index.js').CoreManager} opts.coreManager
|
|
28
30
|
* @param {() => void} opts.onUpdate Called when a state update is available (via getState())
|
|
29
31
|
* @param {Map<string, import('./peer-sync-controller.js').PeerSyncController>} opts.peerSyncControllers
|
|
32
|
+
* @param {import('../blob-store/index.js').BlobStore} opts.blobStore
|
|
30
33
|
* @param {Logger} [opts.logger]
|
|
31
34
|
*/
|
|
32
35
|
constructor({
|
|
@@ -34,12 +37,15 @@ export class NamespaceSyncState {
|
|
|
34
37
|
coreManager,
|
|
35
38
|
onUpdate,
|
|
36
39
|
peerSyncControllers,
|
|
40
|
+
blobStore,
|
|
37
41
|
logger,
|
|
38
42
|
}) {
|
|
39
43
|
// Currently we don't create a logger for this class, just pass it down
|
|
40
44
|
this.#logger = logger
|
|
41
45
|
this.#namespace = namespace
|
|
42
46
|
this.#peerSyncControllers = peerSyncControllers
|
|
47
|
+
this.#blobStore = blobStore
|
|
48
|
+
this.#deviceId = coreManager.deviceId
|
|
43
49
|
// Called whenever the state changes, so we clear the cache because next
|
|
44
50
|
// call to getState() will need to re-derive the state
|
|
45
51
|
this.#handleUpdate = () => {
|
|
@@ -60,6 +66,18 @@ export class NamespaceSyncState {
|
|
|
60
66
|
if (namespace !== this.#namespace) return
|
|
61
67
|
this.#insertPreHaves(msg)
|
|
62
68
|
})
|
|
69
|
+
|
|
70
|
+
if (this.#namespace !== 'blob') return
|
|
71
|
+
blobStore.on('blob-filter', (peerId, filter) => {
|
|
72
|
+
const wantsEverything = !filter
|
|
73
|
+
for (const css of this.#coreStates.values()) {
|
|
74
|
+
css.setWantsEverything(peerId, wantsEverything)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
blobStore.on('want-blob-range', ({ blobCoreId, peerId, start, length }) => {
|
|
78
|
+
const coreState = this.#getCoreState(blobCoreId)
|
|
79
|
+
coreState.addWantRange(peerId, start, length)
|
|
80
|
+
})
|
|
63
81
|
}
|
|
64
82
|
|
|
65
83
|
get namespace() {
|
|
@@ -136,28 +154,6 @@ export class NamespaceSyncState {
|
|
|
136
154
|
this.#getCoreState(coreDiscoveryId).insertPreHaves(peerId, start, bitfield)
|
|
137
155
|
}
|
|
138
156
|
|
|
139
|
-
/**
|
|
140
|
-
* @param {string} peerId
|
|
141
|
-
* @param {number} start
|
|
142
|
-
* @param {number} length
|
|
143
|
-
* @returns {void}
|
|
144
|
-
*/
|
|
145
|
-
addWantRange(peerId, start, length) {
|
|
146
|
-
for (const coreState of this.#coreStates.values()) {
|
|
147
|
-
coreState.addWantRange(peerId, start, length)
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* @param {string} peerId
|
|
153
|
-
* @returns {void}
|
|
154
|
-
*/
|
|
155
|
-
clearWantRanges(peerId) {
|
|
156
|
-
for (const coreState of this.#coreStates.values()) {
|
|
157
|
-
coreState.clearWantRanges(peerId)
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
157
|
/**
|
|
162
158
|
* @param {string} discoveryId
|
|
163
159
|
*/
|
|
@@ -169,6 +165,13 @@ export class NamespaceSyncState {
|
|
|
169
165
|
onUpdate: this.#handleUpdate,
|
|
170
166
|
peerSyncControllers: this.#peerSyncControllers,
|
|
171
167
|
namespace: this.#namespace,
|
|
168
|
+
deviceId: this.#deviceId,
|
|
169
|
+
hasDownloadFilter: (peerId) => {
|
|
170
|
+
return (
|
|
171
|
+
this.#namespace === 'blob' &&
|
|
172
|
+
!!this.#blobStore.getBlobFilter(peerId)
|
|
173
|
+
)
|
|
174
|
+
},
|
|
172
175
|
logger: Logger.create('css:' + this.#namespace, this.#logger, {
|
|
173
176
|
prefix: logPrefix,
|
|
174
177
|
}),
|
package/src/sync/sync-api.js
CHANGED
|
@@ -25,9 +25,6 @@ export const kRescindFullStopRequest = Symbol('foreground')
|
|
|
25
25
|
export const kWaitForInitialSyncWithPeer = Symbol(
|
|
26
26
|
'wait for initial sync with peer'
|
|
27
27
|
)
|
|
28
|
-
export const kSetBlobDownloadFilter = Symbol('set isArchiveDevice')
|
|
29
|
-
export const kAddBlobWantRange = Symbol('add blob want range')
|
|
30
|
-
export const kClearBlobWantRanges = Symbol('clear blob want ranges')
|
|
31
28
|
|
|
32
29
|
/**
|
|
33
30
|
* @typedef {'initial' | 'full'} SyncType
|
|
@@ -37,6 +34,15 @@ export const kClearBlobWantRanges = Symbol('clear blob want ranges')
|
|
|
37
34
|
* @typedef {'none' | 'presync' | 'all'} SyncEnabledState
|
|
38
35
|
*/
|
|
39
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @internal
|
|
39
|
+
* @typedef {object} BlobWantRange
|
|
40
|
+
* @property {number} start
|
|
41
|
+
* @property {number} length
|
|
42
|
+
* @property {string} blobCoreId
|
|
43
|
+
* @property {string} peerId
|
|
44
|
+
*/
|
|
45
|
+
|
|
40
46
|
/**
|
|
41
47
|
* @internal
|
|
42
48
|
* @typedef {object} RemoteDeviceNamespaceGroupSyncState
|
|
@@ -93,8 +99,6 @@ export class SyncApi extends TypedEmitter {
|
|
|
93
99
|
#getReplicationStream
|
|
94
100
|
/** @type {Map<string, WebSocket>} */
|
|
95
101
|
#serverWebsockets = new Map()
|
|
96
|
-
/** @type {null | BlobFilter} */
|
|
97
|
-
#blobDownloadFilter = null
|
|
98
102
|
|
|
99
103
|
/**
|
|
100
104
|
* @param {object} opts
|
|
@@ -103,7 +107,7 @@ export class SyncApi extends TypedEmitter {
|
|
|
103
107
|
* @param {import('../roles.js').Roles} opts.roles
|
|
104
108
|
* @param {() => Promise<Iterable<string>>} opts.getServerWebsocketUrls
|
|
105
109
|
* @param {() => ReplicationStream} opts.getReplicationStream
|
|
106
|
-
* @param {
|
|
110
|
+
* @param {import('../blob-store/index.js').BlobStore} opts.blobStore
|
|
107
111
|
* @param {number} [opts.throttleMs]
|
|
108
112
|
* @param {Logger} [opts.logger]
|
|
109
113
|
*/
|
|
@@ -115,7 +119,7 @@ export class SyncApi extends TypedEmitter {
|
|
|
115
119
|
getReplicationStream,
|
|
116
120
|
logger,
|
|
117
121
|
coreOwnership,
|
|
118
|
-
|
|
122
|
+
blobStore,
|
|
119
123
|
}) {
|
|
120
124
|
super()
|
|
121
125
|
this.#l = Logger.create('syncApi', logger)
|
|
@@ -128,6 +132,7 @@ export class SyncApi extends TypedEmitter {
|
|
|
128
132
|
coreManager,
|
|
129
133
|
throttleMs,
|
|
130
134
|
peerSyncControllers: this.#pscByPeerId,
|
|
135
|
+
blobStore,
|
|
131
136
|
logger,
|
|
132
137
|
})
|
|
133
138
|
this[kSyncState].setMaxListeners(0)
|
|
@@ -135,8 +140,6 @@ export class SyncApi extends TypedEmitter {
|
|
|
135
140
|
this.#updateState(namespaceSyncState)
|
|
136
141
|
})
|
|
137
142
|
|
|
138
|
-
this[kSetBlobDownloadFilter](blobDownloadFilter)
|
|
139
|
-
|
|
140
143
|
this.#coreManager.creatorCore.on('peer-add', this.#handlePeerAdd)
|
|
141
144
|
this.#coreManager.creatorCore.on('peer-remove', this.#handlePeerDisconnect)
|
|
142
145
|
|
|
@@ -156,37 +159,6 @@ export class SyncApi extends TypedEmitter {
|
|
|
156
159
|
.catch(noop)
|
|
157
160
|
}
|
|
158
161
|
|
|
159
|
-
/** @param {import('../types.js').BlobFilter | null} blobDownloadFilter */
|
|
160
|
-
[kSetBlobDownloadFilter](blobDownloadFilter) {
|
|
161
|
-
this.#blobDownloadFilter = blobDownloadFilter
|
|
162
|
-
if (!blobDownloadFilter) return // No download intents = intend to download everything
|
|
163
|
-
for (const peer of this.#coreManager.creatorCore.peers) {
|
|
164
|
-
this.#coreManager.sendDownloadIntents(blobDownloadFilter, peer)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Add some blob blocks this peer wants.
|
|
170
|
-
*
|
|
171
|
-
* @param {string} peerId
|
|
172
|
-
* @param {number} start
|
|
173
|
-
* @param {number} length
|
|
174
|
-
* @returns {void}
|
|
175
|
-
*/
|
|
176
|
-
[kAddBlobWantRange](peerId, start, length) {
|
|
177
|
-
this[kSyncState].addBlobWantRange(peerId, start, length)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Clear the blob blocks this peer wants.
|
|
182
|
-
*
|
|
183
|
-
* @param {string} peerId
|
|
184
|
-
* @returns {void}
|
|
185
|
-
*/
|
|
186
|
-
[kClearBlobWantRanges](peerId) {
|
|
187
|
-
this[kSyncState].clearBlobWantRanges(peerId)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
162
|
/** @type {import('../local-peers.js').LocalPeersEvents['discovery-key']} */
|
|
191
163
|
[kHandleDiscoveryKey](discoveryKey, protomux) {
|
|
192
164
|
const peerSyncController = this.#peerSyncControllers.get(protomux)
|
|
@@ -540,9 +512,6 @@ export class SyncApi extends TypedEmitter {
|
|
|
540
512
|
)
|
|
541
513
|
return
|
|
542
514
|
}
|
|
543
|
-
if (this.#blobDownloadFilter) {
|
|
544
|
-
this.#coreManager.sendDownloadIntents(this.#blobDownloadFilter, peer)
|
|
545
|
-
}
|
|
546
515
|
const peerSyncController = new PeerSyncController({
|
|
547
516
|
protomux,
|
|
548
517
|
coreManager: this.#coreManager,
|
package/src/sync/sync-state.js
CHANGED
|
@@ -23,10 +23,17 @@ export class SyncState extends TypedEmitter {
|
|
|
23
23
|
* @param {object} opts
|
|
24
24
|
* @param {import('../core-manager/index.js').CoreManager} opts.coreManager
|
|
25
25
|
* @param {Map<string, import('./peer-sync-controller.js').PeerSyncController>} opts.peerSyncControllers
|
|
26
|
+
* @param {import('../blob-store/index.js').BlobStore} opts.blobStore
|
|
26
27
|
* @param {number} [opts.throttleMs]
|
|
27
28
|
* @param {import('../logger.js').Logger} [opts.logger]
|
|
28
29
|
*/
|
|
29
|
-
constructor({
|
|
30
|
+
constructor({
|
|
31
|
+
coreManager,
|
|
32
|
+
peerSyncControllers,
|
|
33
|
+
blobStore,
|
|
34
|
+
throttleMs = 200,
|
|
35
|
+
logger,
|
|
36
|
+
}) {
|
|
30
37
|
super()
|
|
31
38
|
const throttledHandleUpdate = throttle(throttleMs, this.#handleUpdate)
|
|
32
39
|
for (const namespace of NAMESPACES) {
|
|
@@ -35,6 +42,7 @@ export class SyncState extends TypedEmitter {
|
|
|
35
42
|
coreManager,
|
|
36
43
|
onUpdate: throttledHandleUpdate,
|
|
37
44
|
peerSyncControllers,
|
|
45
|
+
blobStore,
|
|
38
46
|
logger,
|
|
39
47
|
})
|
|
40
48
|
}
|
|
@@ -68,24 +76,6 @@ export class SyncState extends TypedEmitter {
|
|
|
68
76
|
])
|
|
69
77
|
}
|
|
70
78
|
|
|
71
|
-
/**
|
|
72
|
-
* @param {string} peerId
|
|
73
|
-
* @param {number} start
|
|
74
|
-
* @param {number} length
|
|
75
|
-
* @returns {void}
|
|
76
|
-
*/
|
|
77
|
-
addBlobWantRange(peerId, start, length) {
|
|
78
|
-
this.#syncStates.blob.addWantRange(peerId, start, length)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* @param {string} peerId
|
|
83
|
-
* @returns {void}
|
|
84
|
-
*/
|
|
85
|
-
clearBlobWantRanges(peerId) {
|
|
86
|
-
this.#syncStates.blob.clearWantRanges(peerId)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
79
|
#handleUpdate = () => {
|
|
90
80
|
this.emit('state', this.getState())
|
|
91
81
|
}
|
package/src/translation-api.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { and, sql } from 'drizzle-orm'
|
|
2
2
|
import { kCreateWithDocId, kSelect } from './datatype/index.js'
|
|
3
|
-
import { hashObject } from './utils.js'
|
|
3
|
+
import { deNullify, hashObject } from './utils.js'
|
|
4
4
|
import { nullIfNotFound } from './errors.js'
|
|
5
5
|
import { omit } from './lib/omit.js'
|
|
6
6
|
/** @import { Translation, TranslationValue } from '@comapeo/schema' */
|
|
@@ -101,6 +101,7 @@ export default class TranslationApi {
|
|
|
101
101
|
.where(and.apply(null, filters))
|
|
102
102
|
.prepare()
|
|
103
103
|
.all()
|
|
104
|
+
.map(deNullify)
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
/**
|
package/src/types.ts
CHANGED
|
@@ -152,6 +152,6 @@ export type DefaultEmitterEvents<
|
|
|
152
152
|
|
|
153
153
|
export type BlobStoreEntriesStream = Readable & {
|
|
154
154
|
[Symbol.asyncIterator](): AsyncIterableIterator<
|
|
155
|
-
HyperdriveEntry & { driveId: string }
|
|
155
|
+
HyperdriveEntry & { driveId: string; blobCoreId: string }
|
|
156
156
|
>
|
|
157
157
|
}
|