@dxos/teleport-extension-object-sync 0.8.4-main.fffef41 → 0.9.0
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/LICENSE +102 -5
- package/dist/lib/{node-esm → neutral}/index.mjs +214 -185
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/types/src/blob-store.d.ts +11 -1
- package/dist/types/src/blob-store.d.ts.map +1 -1
- package/dist/types/src/blob-sync-extension.d.ts +4 -4
- package/dist/types/src/blob-sync-extension.d.ts.map +1 -1
- package/dist/types/src/blob-sync.d.ts +4 -4
- package/dist/types/src/blob-sync.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/sqlite-blob-store.d.ts +31 -0
- package/dist/types/src/sqlite-blob-store.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +23 -19
- package/src/blob-store.ts +12 -1
- package/src/blob-sync-extension.ts +4 -4
- package/src/blob-sync.node.test.ts +0 -1
- package/src/blob-sync.ts +4 -4
- package/src/index.ts +1 -0
- package/src/sqlite-blob-store.ts +228 -0
- package/dist/lib/browser/index.mjs +0 -647
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
|
@@ -1,647 +0,0 @@
|
|
|
1
|
-
import "@dxos/node-std/globals";
|
|
2
|
-
|
|
3
|
-
// src/blob-sync-extension.ts
|
|
4
|
-
import { DeferredTask, sleep, synchronized } from "@dxos/async";
|
|
5
|
-
import { Context } from "@dxos/context";
|
|
6
|
-
import { invariant } from "@dxos/invariant";
|
|
7
|
-
import { log } from "@dxos/log";
|
|
8
|
-
import { RpcClosedError } from "@dxos/protocols";
|
|
9
|
-
import { schema } from "@dxos/protocols/proto";
|
|
10
|
-
import { RpcExtension } from "@dxos/teleport";
|
|
11
|
-
import { BitField } from "@dxos/util";
|
|
12
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
13
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
14
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
15
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
16
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
17
|
-
}
|
|
18
|
-
var __dxlog_file = "/__w/dxos/dxos/packages/core/mesh/teleport-extension-object-sync/src/blob-sync-extension.ts";
|
|
19
|
-
var MIN_WANT_LIST_UPDATE_INTERVAL = false ? 5 : 500;
|
|
20
|
-
var MAX_CONCURRENT_UPLOADS = 20;
|
|
21
|
-
var BlobSyncExtension = class extends RpcExtension {
|
|
22
|
-
_params;
|
|
23
|
-
_ctx = new Context({
|
|
24
|
-
onError: (err) => log.catch(err, void 0, {
|
|
25
|
-
F: __dxlog_file,
|
|
26
|
-
L: 35,
|
|
27
|
-
S: this,
|
|
28
|
-
C: (f, a) => f(...a)
|
|
29
|
-
})
|
|
30
|
-
}, {
|
|
31
|
-
F: __dxlog_file,
|
|
32
|
-
L: 35
|
|
33
|
-
});
|
|
34
|
-
_lastWantListUpdate = 0;
|
|
35
|
-
_localWantList = {
|
|
36
|
-
blobs: []
|
|
37
|
-
};
|
|
38
|
-
_updateWantList = new DeferredTask(this._ctx, async () => {
|
|
39
|
-
if (this._lastWantListUpdate + MIN_WANT_LIST_UPDATE_INTERVAL > Date.now()) {
|
|
40
|
-
await sleep(this._lastWantListUpdate + MIN_WANT_LIST_UPDATE_INTERVAL - Date.now());
|
|
41
|
-
if (this._ctx.disposed) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
log("want", {
|
|
46
|
-
list: this._localWantList
|
|
47
|
-
}, {
|
|
48
|
-
F: __dxlog_file,
|
|
49
|
-
L: 49,
|
|
50
|
-
S: this,
|
|
51
|
-
C: (f, a) => f(...a)
|
|
52
|
-
});
|
|
53
|
-
await this.rpc.BlobSyncService.want(this._localWantList);
|
|
54
|
-
this._lastWantListUpdate = Date.now();
|
|
55
|
-
});
|
|
56
|
-
_currentUploads = 0;
|
|
57
|
-
_upload = new DeferredTask(this._ctx, async () => {
|
|
58
|
-
if (this._currentUploads >= MAX_CONCURRENT_UPLOADS) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
const blobChunks = await this._pickBlobChunks(MAX_CONCURRENT_UPLOADS - this._currentUploads);
|
|
62
|
-
if (!blobChunks) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
for (const blobChunk of blobChunks) {
|
|
66
|
-
if (this._ctx.disposed) {
|
|
67
|
-
break;
|
|
68
|
-
}
|
|
69
|
-
this._currentUploads++;
|
|
70
|
-
this.push(blobChunk).catch((err) => {
|
|
71
|
-
if (err instanceof RpcClosedError) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
log.warn("push failed", {
|
|
75
|
-
err
|
|
76
|
-
}, {
|
|
77
|
-
F: __dxlog_file,
|
|
78
|
-
L: 76,
|
|
79
|
-
S: this,
|
|
80
|
-
C: (f, a) => f(...a)
|
|
81
|
-
});
|
|
82
|
-
}).finally(() => {
|
|
83
|
-
this._currentUploads--;
|
|
84
|
-
this.reconcileUploads();
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
/**
|
|
89
|
-
* Set of id's remote peer wants.
|
|
90
|
-
*/
|
|
91
|
-
remoteWantList = {
|
|
92
|
-
blobs: []
|
|
93
|
-
};
|
|
94
|
-
constructor(_params) {
|
|
95
|
-
super({
|
|
96
|
-
exposed: {
|
|
97
|
-
BlobSyncService: schema.getService("dxos.mesh.teleport.blobsync.BlobSyncService")
|
|
98
|
-
},
|
|
99
|
-
requested: {
|
|
100
|
-
BlobSyncService: schema.getService("dxos.mesh.teleport.blobsync.BlobSyncService")
|
|
101
|
-
},
|
|
102
|
-
timeout: 2e4,
|
|
103
|
-
encodingOptions: {
|
|
104
|
-
preserveAny: true
|
|
105
|
-
}
|
|
106
|
-
}), this._params = _params;
|
|
107
|
-
}
|
|
108
|
-
async onOpen(context) {
|
|
109
|
-
log("open", void 0, {
|
|
110
|
-
F: __dxlog_file,
|
|
111
|
-
L: 108,
|
|
112
|
-
S: this,
|
|
113
|
-
C: (f, a) => f(...a)
|
|
114
|
-
});
|
|
115
|
-
await super.onOpen(context);
|
|
116
|
-
await this._params.onOpen();
|
|
117
|
-
}
|
|
118
|
-
async onClose(err) {
|
|
119
|
-
log("close", void 0, {
|
|
120
|
-
F: __dxlog_file,
|
|
121
|
-
L: 114,
|
|
122
|
-
S: this,
|
|
123
|
-
C: (f, a) => f(...a)
|
|
124
|
-
});
|
|
125
|
-
await this._ctx.dispose();
|
|
126
|
-
await this._params.onClose();
|
|
127
|
-
await super.onClose(err);
|
|
128
|
-
}
|
|
129
|
-
async onAbort(err) {
|
|
130
|
-
log("abort", void 0, {
|
|
131
|
-
F: __dxlog_file,
|
|
132
|
-
L: 121,
|
|
133
|
-
S: this,
|
|
134
|
-
C: (f, a) => f(...a)
|
|
135
|
-
});
|
|
136
|
-
await this._ctx.dispose();
|
|
137
|
-
await this._params.onAbort();
|
|
138
|
-
await super.onAbort(err);
|
|
139
|
-
}
|
|
140
|
-
async getHandlers() {
|
|
141
|
-
return {
|
|
142
|
-
BlobSyncService: {
|
|
143
|
-
want: async (wantList) => {
|
|
144
|
-
log("remote want", {
|
|
145
|
-
remoteWantList: wantList
|
|
146
|
-
}, {
|
|
147
|
-
F: __dxlog_file,
|
|
148
|
-
L: 131,
|
|
149
|
-
S: this,
|
|
150
|
-
C: (f, a) => f(...a)
|
|
151
|
-
});
|
|
152
|
-
this.remoteWantList = wantList;
|
|
153
|
-
this.reconcileUploads();
|
|
154
|
-
},
|
|
155
|
-
push: async (data) => {
|
|
156
|
-
log("received", {
|
|
157
|
-
data
|
|
158
|
-
}, {
|
|
159
|
-
F: __dxlog_file,
|
|
160
|
-
L: 136,
|
|
161
|
-
S: this,
|
|
162
|
-
C: (f, a) => f(...a)
|
|
163
|
-
});
|
|
164
|
-
await this._params.onPush(data);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
async push(data) {
|
|
170
|
-
if (this._ctx.disposed) {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
log("push", {
|
|
174
|
-
data
|
|
175
|
-
}, {
|
|
176
|
-
F: __dxlog_file,
|
|
177
|
-
L: 148,
|
|
178
|
-
S: this,
|
|
179
|
-
C: (f, a) => f(...a)
|
|
180
|
-
});
|
|
181
|
-
await this.rpc.BlobSyncService.push(data);
|
|
182
|
-
}
|
|
183
|
-
updateWantList(wantList) {
|
|
184
|
-
if (this._ctx.disposed) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
this._localWantList = wantList;
|
|
188
|
-
this._updateWantList.schedule();
|
|
189
|
-
}
|
|
190
|
-
reconcileUploads() {
|
|
191
|
-
if (this._ctx.disposed) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
this._upload.schedule();
|
|
195
|
-
}
|
|
196
|
-
async _pickBlobChunks(amount = 1) {
|
|
197
|
-
if (this._ctx.disposed) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
if (!this.remoteWantList.blobs || this.remoteWantList.blobs?.length === 0) {
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
const shuffled = [
|
|
204
|
-
...this.remoteWantList.blobs
|
|
205
|
-
].sort(() => Math.random() - 0.5);
|
|
206
|
-
const chunks = [];
|
|
207
|
-
for (const header of shuffled) {
|
|
208
|
-
const meta = await this._params.blobStore.getMeta(header.id);
|
|
209
|
-
if (!meta) {
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
invariant(meta.bitfield, void 0, {
|
|
213
|
-
F: __dxlog_file,
|
|
214
|
-
L: 187,
|
|
215
|
-
S: this,
|
|
216
|
-
A: [
|
|
217
|
-
"meta.bitfield",
|
|
218
|
-
""
|
|
219
|
-
]
|
|
220
|
-
});
|
|
221
|
-
invariant(meta.chunkSize, void 0, {
|
|
222
|
-
F: __dxlog_file,
|
|
223
|
-
L: 188,
|
|
224
|
-
S: this,
|
|
225
|
-
A: [
|
|
226
|
-
"meta.chunkSize",
|
|
227
|
-
""
|
|
228
|
-
]
|
|
229
|
-
});
|
|
230
|
-
invariant(meta.length, void 0, {
|
|
231
|
-
F: __dxlog_file,
|
|
232
|
-
L: 189,
|
|
233
|
-
S: this,
|
|
234
|
-
A: [
|
|
235
|
-
"meta.length",
|
|
236
|
-
""
|
|
237
|
-
]
|
|
238
|
-
});
|
|
239
|
-
if (header.chunkSize && header.chunkSize !== meta.chunkSize) {
|
|
240
|
-
log.warn("Invalid chunk size", {
|
|
241
|
-
header,
|
|
242
|
-
meta
|
|
243
|
-
}, {
|
|
244
|
-
F: __dxlog_file,
|
|
245
|
-
L: 192,
|
|
246
|
-
S: this,
|
|
247
|
-
C: (f, a) => f(...a)
|
|
248
|
-
});
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
const requestBitfield = header.bitfield ?? BitField.ones(meta.length / meta.chunkSize);
|
|
252
|
-
const presentData = BitField.and(requestBitfield, meta.bitfield);
|
|
253
|
-
const chunkIndices = BitField.findIndexes(presentData).sort(() => Math.random() - 0.5);
|
|
254
|
-
for (const idx of chunkIndices) {
|
|
255
|
-
const chunkData = await this._params.blobStore.get(header.id, {
|
|
256
|
-
offset: idx * meta.chunkSize,
|
|
257
|
-
length: Math.min(meta.chunkSize, meta.length - idx * meta.chunkSize)
|
|
258
|
-
});
|
|
259
|
-
chunks.push({
|
|
260
|
-
id: header.id,
|
|
261
|
-
totalLength: meta.length,
|
|
262
|
-
chunkSize: meta.chunkSize,
|
|
263
|
-
chunkOffset: idx * meta.chunkSize,
|
|
264
|
-
payload: chunkData
|
|
265
|
-
});
|
|
266
|
-
if (chunks.length >= amount) {
|
|
267
|
-
return chunks;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
return chunks;
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
_ts_decorate([
|
|
275
|
-
synchronized
|
|
276
|
-
], BlobSyncExtension.prototype, "push", null);
|
|
277
|
-
|
|
278
|
-
// src/blob-sync.ts
|
|
279
|
-
import { Mutex, Trigger, trackLeaks } from "@dxos/async";
|
|
280
|
-
import { Context as Context2, cancelWithContext } from "@dxos/context";
|
|
281
|
-
import { invariant as invariant2 } from "@dxos/invariant";
|
|
282
|
-
import { PublicKey } from "@dxos/keys";
|
|
283
|
-
import { log as log2 } from "@dxos/log";
|
|
284
|
-
import { BlobMeta } from "@dxos/protocols/proto/dxos/echo/blob";
|
|
285
|
-
import { BitField as BitField2, ComplexMap } from "@dxos/util";
|
|
286
|
-
function _ts_decorate2(decorators, target, key, desc) {
|
|
287
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
288
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
289
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
290
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
291
|
-
}
|
|
292
|
-
var __dxlog_file2 = "/__w/dxos/dxos/packages/core/mesh/teleport-extension-object-sync/src/blob-sync.ts";
|
|
293
|
-
var BlobSync = class {
|
|
294
|
-
_params;
|
|
295
|
-
_ctx = new Context2(void 0, {
|
|
296
|
-
F: __dxlog_file2,
|
|
297
|
-
L: 30
|
|
298
|
-
});
|
|
299
|
-
_mutex = new Mutex();
|
|
300
|
-
_downloadRequests = new ComplexMap((key) => PublicKey.from(key).toHex());
|
|
301
|
-
_extensions = /* @__PURE__ */ new Set();
|
|
302
|
-
constructor(_params) {
|
|
303
|
-
this._params = _params;
|
|
304
|
-
}
|
|
305
|
-
async open() {
|
|
306
|
-
}
|
|
307
|
-
async close() {
|
|
308
|
-
await this._ctx.dispose();
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Resolves when the object with the given id is fully downloaded in the blob store.
|
|
312
|
-
*
|
|
313
|
-
* @param id hex-encoded id of the object to download.
|
|
314
|
-
*/
|
|
315
|
-
async download(ctx, id) {
|
|
316
|
-
log2("download", {
|
|
317
|
-
id
|
|
318
|
-
}, {
|
|
319
|
-
F: __dxlog_file2,
|
|
320
|
-
L: 53,
|
|
321
|
-
S: this,
|
|
322
|
-
C: (f, a) => f(...a)
|
|
323
|
-
});
|
|
324
|
-
const request = await this._mutex.executeSynchronized(async () => {
|
|
325
|
-
const existingRequest = this._downloadRequests.get(id);
|
|
326
|
-
if (existingRequest) {
|
|
327
|
-
existingRequest.counter++;
|
|
328
|
-
return existingRequest;
|
|
329
|
-
}
|
|
330
|
-
const meta = await this._params.blobStore.getMeta(id);
|
|
331
|
-
const request2 = {
|
|
332
|
-
trigger: new Trigger(),
|
|
333
|
-
counter: 1,
|
|
334
|
-
want: {
|
|
335
|
-
id,
|
|
336
|
-
chunkSize: meta?.chunkSize,
|
|
337
|
-
bitfield: meta?.bitfield && Uint8Array.from(BitField2.invert(meta.bitfield))
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
if (meta?.state === BlobMeta.State.FULLY_PRESENT) {
|
|
341
|
-
request2.trigger.wake();
|
|
342
|
-
} else {
|
|
343
|
-
this._downloadRequests.set(id, request2);
|
|
344
|
-
this._updateExtensionsWantList();
|
|
345
|
-
}
|
|
346
|
-
return request2;
|
|
347
|
-
});
|
|
348
|
-
ctx?.onDispose(() => this._mutex.executeSynchronized(async () => {
|
|
349
|
-
const request2 = this._downloadRequests.get(id);
|
|
350
|
-
if (!request2) {
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
if (--request2.counter === 0) {
|
|
354
|
-
this._downloadRequests.delete(id);
|
|
355
|
-
}
|
|
356
|
-
this._updateExtensionsWantList();
|
|
357
|
-
}));
|
|
358
|
-
return ctx ? cancelWithContext(ctx, request.trigger.wait()) : request.trigger.wait();
|
|
359
|
-
}
|
|
360
|
-
createExtension() {
|
|
361
|
-
const extension = new BlobSyncExtension({
|
|
362
|
-
blobStore: this._params.blobStore,
|
|
363
|
-
onOpen: async () => {
|
|
364
|
-
log2("extension opened", void 0, {
|
|
365
|
-
F: __dxlog_file2,
|
|
366
|
-
L: 105,
|
|
367
|
-
S: this,
|
|
368
|
-
C: (f, a) => f(...a)
|
|
369
|
-
});
|
|
370
|
-
this._extensions.add(extension);
|
|
371
|
-
extension.updateWantList(this._getWantList());
|
|
372
|
-
},
|
|
373
|
-
onClose: async () => {
|
|
374
|
-
log2("extension closed", void 0, {
|
|
375
|
-
F: __dxlog_file2,
|
|
376
|
-
L: 110,
|
|
377
|
-
S: this,
|
|
378
|
-
C: (f, a) => f(...a)
|
|
379
|
-
});
|
|
380
|
-
this._extensions.delete(extension);
|
|
381
|
-
},
|
|
382
|
-
onAbort: async () => {
|
|
383
|
-
log2("extension aborted", void 0, {
|
|
384
|
-
F: __dxlog_file2,
|
|
385
|
-
L: 114,
|
|
386
|
-
S: this,
|
|
387
|
-
C: (f, a) => f(...a)
|
|
388
|
-
});
|
|
389
|
-
this._extensions.delete(extension);
|
|
390
|
-
},
|
|
391
|
-
onPush: async (blobChunk) => {
|
|
392
|
-
if (!this._downloadRequests.has(blobChunk.id)) {
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
log2("received", {
|
|
396
|
-
blobChunk
|
|
397
|
-
}, {
|
|
398
|
-
F: __dxlog_file2,
|
|
399
|
-
L: 121,
|
|
400
|
-
S: this,
|
|
401
|
-
C: (f, a) => f(...a)
|
|
402
|
-
});
|
|
403
|
-
const meta = await this._params.blobStore.setChunk(blobChunk);
|
|
404
|
-
if (meta.state === BlobMeta.State.FULLY_PRESENT) {
|
|
405
|
-
this._downloadRequests.get(blobChunk.id)?.trigger.wake();
|
|
406
|
-
this._downloadRequests.delete(blobChunk.id);
|
|
407
|
-
} else {
|
|
408
|
-
invariant2(meta.bitfield, void 0, {
|
|
409
|
-
F: __dxlog_file2,
|
|
410
|
-
L: 127,
|
|
411
|
-
S: this,
|
|
412
|
-
A: [
|
|
413
|
-
"meta.bitfield",
|
|
414
|
-
""
|
|
415
|
-
]
|
|
416
|
-
});
|
|
417
|
-
this._downloadRequests.get(blobChunk.id).want.bitfield = BitField2.invert(meta.bitfield);
|
|
418
|
-
}
|
|
419
|
-
this._updateExtensionsWantList();
|
|
420
|
-
this._reconcileUploads();
|
|
421
|
-
}
|
|
422
|
-
});
|
|
423
|
-
return extension;
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* Notify extensions that a blob with the given id was added to the blob store.
|
|
427
|
-
*/
|
|
428
|
-
async notifyBlobAdded(_id) {
|
|
429
|
-
this._reconcileUploads();
|
|
430
|
-
}
|
|
431
|
-
_getWantList() {
|
|
432
|
-
return {
|
|
433
|
-
blobs: Array.from(this._downloadRequests.values()).map((request) => request.want)
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
_reconcileUploads() {
|
|
437
|
-
for (const extension of this._extensions) {
|
|
438
|
-
extension.reconcileUploads();
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
_updateExtensionsWantList() {
|
|
442
|
-
for (const extension of this._extensions) {
|
|
443
|
-
extension.updateWantList(this._getWantList());
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
};
|
|
447
|
-
BlobSync = _ts_decorate2([
|
|
448
|
-
trackLeaks("open", "close")
|
|
449
|
-
], BlobSync);
|
|
450
|
-
|
|
451
|
-
// src/blob-store.ts
|
|
452
|
-
import path from "@dxos/node-std/path";
|
|
453
|
-
import { synchronized as synchronized2 } from "@dxos/async";
|
|
454
|
-
import { subtleCrypto } from "@dxos/crypto";
|
|
455
|
-
import { invariant as invariant3 } from "@dxos/invariant";
|
|
456
|
-
import { PublicKey as PublicKey2 } from "@dxos/keys";
|
|
457
|
-
import { schema as schema2 } from "@dxos/protocols/proto";
|
|
458
|
-
import { BlobMeta as BlobMeta2 } from "@dxos/protocols/proto/dxos/echo/blob";
|
|
459
|
-
import { BitField as BitField3, arrayToBuffer } from "@dxos/util";
|
|
460
|
-
function _ts_decorate3(decorators, target, key, desc) {
|
|
461
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
462
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
463
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
464
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
465
|
-
}
|
|
466
|
-
var __dxlog_file3 = "/__w/dxos/dxos/packages/core/mesh/teleport-extension-object-sync/src/blob-store.ts";
|
|
467
|
-
var DEFAULT_CHUNK_SIZE = 4096;
|
|
468
|
-
var BlobMetaCodec = schema2.getCodecForType("dxos.echo.blob.BlobMeta");
|
|
469
|
-
var BlobStore = class {
|
|
470
|
-
_directory;
|
|
471
|
-
constructor(_directory) {
|
|
472
|
-
this._directory = _directory;
|
|
473
|
-
}
|
|
474
|
-
async getMeta(id) {
|
|
475
|
-
return this._getMeta(id);
|
|
476
|
-
}
|
|
477
|
-
/**
|
|
478
|
-
* @throws If range is not available.
|
|
479
|
-
*/
|
|
480
|
-
async get(id, options = {}) {
|
|
481
|
-
const metadata = await this._getMeta(id);
|
|
482
|
-
if (!metadata) {
|
|
483
|
-
throw new Error("Blob not available");
|
|
484
|
-
}
|
|
485
|
-
const { offset = 0, length = metadata.length } = options;
|
|
486
|
-
if (offset + length > metadata.length) {
|
|
487
|
-
throw new Error("Invalid range");
|
|
488
|
-
}
|
|
489
|
-
if (metadata.state === BlobMeta2.State.FULLY_PRESENT) {
|
|
490
|
-
const file2 = this._getDataFile(id);
|
|
491
|
-
return file2.read(offset, length);
|
|
492
|
-
} else if (options.offset === void 0 && options.length === void 0) {
|
|
493
|
-
throw new Error("Blob not available");
|
|
494
|
-
}
|
|
495
|
-
const beginChunk = Math.floor(offset / metadata.chunkSize);
|
|
496
|
-
const endChunk = Math.ceil((offset + length) / metadata.chunkSize);
|
|
497
|
-
invariant3(metadata.bitfield, "Bitfield not present", {
|
|
498
|
-
F: __dxlog_file3,
|
|
499
|
-
L: 61,
|
|
500
|
-
S: this,
|
|
501
|
-
A: [
|
|
502
|
-
"metadata.bitfield",
|
|
503
|
-
"'Bitfield not present'"
|
|
504
|
-
]
|
|
505
|
-
});
|
|
506
|
-
invariant3(metadata.bitfield.length * 8 >= endChunk, "Invalid bitfield length", {
|
|
507
|
-
F: __dxlog_file3,
|
|
508
|
-
L: 62,
|
|
509
|
-
S: this,
|
|
510
|
-
A: [
|
|
511
|
-
"metadata.bitfield.length * 8 >= endChunk",
|
|
512
|
-
"'Invalid bitfield length'"
|
|
513
|
-
]
|
|
514
|
-
});
|
|
515
|
-
const present = BitField3.count(metadata.bitfield, beginChunk, endChunk) === endChunk - beginChunk;
|
|
516
|
-
if (!present) {
|
|
517
|
-
throw new Error("Blob not available");
|
|
518
|
-
}
|
|
519
|
-
const file = this._getDataFile(id);
|
|
520
|
-
return file.read(offset, length);
|
|
521
|
-
}
|
|
522
|
-
async list() {
|
|
523
|
-
const files = new Set((await this._directory.list()).map((f) => f.split("_")[0]));
|
|
524
|
-
const res = [];
|
|
525
|
-
for (const file of files) {
|
|
526
|
-
const id = PublicKey2.from(file).asUint8Array();
|
|
527
|
-
const meta = await this._getMeta(id);
|
|
528
|
-
if (meta) {
|
|
529
|
-
res.push(meta);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
return res;
|
|
533
|
-
}
|
|
534
|
-
async set(data) {
|
|
535
|
-
const id = new Uint8Array(await subtleCrypto.digest("SHA-256", data));
|
|
536
|
-
const bitfield = BitField3.ones(data.length / DEFAULT_CHUNK_SIZE);
|
|
537
|
-
const meta = {
|
|
538
|
-
id,
|
|
539
|
-
state: BlobMeta2.State.FULLY_PRESENT,
|
|
540
|
-
length: data.length,
|
|
541
|
-
chunkSize: DEFAULT_CHUNK_SIZE,
|
|
542
|
-
bitfield,
|
|
543
|
-
created: /* @__PURE__ */ new Date(),
|
|
544
|
-
updated: /* @__PURE__ */ new Date()
|
|
545
|
-
};
|
|
546
|
-
await this._getDataFile(id).write(0, arrayToBuffer(data));
|
|
547
|
-
await this._writeMeta(id, meta);
|
|
548
|
-
return meta;
|
|
549
|
-
}
|
|
550
|
-
// TODO(dmaretskyi): Optimize locking.
|
|
551
|
-
async setChunk(chunk) {
|
|
552
|
-
let meta = await this._getMeta(chunk.id);
|
|
553
|
-
if (!meta) {
|
|
554
|
-
invariant3(chunk.totalLength, "totalLength is not present", {
|
|
555
|
-
F: __dxlog_file3,
|
|
556
|
-
L: 124,
|
|
557
|
-
S: this,
|
|
558
|
-
A: [
|
|
559
|
-
"chunk.totalLength",
|
|
560
|
-
"'totalLength is not present'"
|
|
561
|
-
]
|
|
562
|
-
});
|
|
563
|
-
meta = {
|
|
564
|
-
id: chunk.id,
|
|
565
|
-
state: BlobMeta2.State.PARTIALLY_PRESENT,
|
|
566
|
-
length: chunk.totalLength,
|
|
567
|
-
chunkSize: chunk.chunkSize ?? DEFAULT_CHUNK_SIZE,
|
|
568
|
-
created: /* @__PURE__ */ new Date()
|
|
569
|
-
};
|
|
570
|
-
meta.bitfield = BitField3.zeros(meta.length / meta.chunkSize);
|
|
571
|
-
}
|
|
572
|
-
if (chunk.chunkSize && chunk.chunkSize !== meta.chunkSize) {
|
|
573
|
-
throw new Error("Invalid chunk size");
|
|
574
|
-
}
|
|
575
|
-
invariant3(meta.bitfield, "Bitfield not present", {
|
|
576
|
-
F: __dxlog_file3,
|
|
577
|
-
L: 139,
|
|
578
|
-
S: this,
|
|
579
|
-
A: [
|
|
580
|
-
"meta.bitfield",
|
|
581
|
-
"'Bitfield not present'"
|
|
582
|
-
]
|
|
583
|
-
});
|
|
584
|
-
invariant3(chunk.chunkOffset !== void 0, "chunkOffset is not present", {
|
|
585
|
-
F: __dxlog_file3,
|
|
586
|
-
L: 140,
|
|
587
|
-
S: this,
|
|
588
|
-
A: [
|
|
589
|
-
"chunk.chunkOffset !== undefined",
|
|
590
|
-
"'chunkOffset is not present'"
|
|
591
|
-
]
|
|
592
|
-
});
|
|
593
|
-
await this._getDataFile(chunk.id).write(chunk.chunkOffset, arrayToBuffer(chunk.payload));
|
|
594
|
-
BitField3.set(meta.bitfield, Math.floor(chunk.chunkOffset / meta.chunkSize), true);
|
|
595
|
-
if (BitField3.count(meta.bitfield, 0, meta.length) * meta.chunkSize >= meta.length) {
|
|
596
|
-
meta.state = BlobMeta2.State.FULLY_PRESENT;
|
|
597
|
-
}
|
|
598
|
-
meta.updated = /* @__PURE__ */ new Date();
|
|
599
|
-
await this._writeMeta(chunk.id, meta);
|
|
600
|
-
return meta;
|
|
601
|
-
}
|
|
602
|
-
async _writeMeta(id, meta) {
|
|
603
|
-
const encoded = arrayToBuffer(BlobMetaCodec.encode(meta));
|
|
604
|
-
const data = Buffer.alloc(encoded.length + 4);
|
|
605
|
-
data.writeUInt32LE(encoded.length, 0);
|
|
606
|
-
encoded.copy(data, 4);
|
|
607
|
-
await this._getMetaFile(id).write(0, data);
|
|
608
|
-
}
|
|
609
|
-
async _getMeta(id) {
|
|
610
|
-
const file = this._getMetaFile(id);
|
|
611
|
-
const size = (await file.stat()).size;
|
|
612
|
-
if (size === 0) {
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
const data = await file.read(0, size);
|
|
616
|
-
const protoSize = data.readUInt32LE(0);
|
|
617
|
-
return BlobMetaCodec.decode(data.subarray(4, protoSize + 4));
|
|
618
|
-
}
|
|
619
|
-
_getMetaFile(id) {
|
|
620
|
-
return this._directory.getOrCreateFile(path.join(arrayToBuffer(id).toString("hex"), "meta"));
|
|
621
|
-
}
|
|
622
|
-
_getDataFile(id) {
|
|
623
|
-
return this._directory.getOrCreateFile(path.join(arrayToBuffer(id).toString("hex"), "data"));
|
|
624
|
-
}
|
|
625
|
-
};
|
|
626
|
-
_ts_decorate3([
|
|
627
|
-
synchronized2
|
|
628
|
-
], BlobStore.prototype, "getMeta", null);
|
|
629
|
-
_ts_decorate3([
|
|
630
|
-
synchronized2
|
|
631
|
-
], BlobStore.prototype, "get", null);
|
|
632
|
-
_ts_decorate3([
|
|
633
|
-
synchronized2
|
|
634
|
-
], BlobStore.prototype, "list", null);
|
|
635
|
-
_ts_decorate3([
|
|
636
|
-
synchronized2
|
|
637
|
-
], BlobStore.prototype, "set", null);
|
|
638
|
-
_ts_decorate3([
|
|
639
|
-
synchronized2
|
|
640
|
-
], BlobStore.prototype, "setChunk", null);
|
|
641
|
-
export {
|
|
642
|
-
BlobStore,
|
|
643
|
-
BlobSync,
|
|
644
|
-
BlobSyncExtension,
|
|
645
|
-
DEFAULT_CHUNK_SIZE
|
|
646
|
-
};
|
|
647
|
-
//# sourceMappingURL=index.mjs.map
|