@comapeo/core 2.0.1 → 2.2.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.
Files changed (114) hide show
  1. package/dist/blob-store/downloader.d.ts +43 -0
  2. package/dist/blob-store/downloader.d.ts.map +1 -0
  3. package/dist/blob-store/entries-stream.d.ts +13 -0
  4. package/dist/blob-store/entries-stream.d.ts.map +1 -0
  5. package/dist/blob-store/hyperdrive-index.d.ts +20 -0
  6. package/dist/blob-store/hyperdrive-index.d.ts.map +1 -0
  7. package/dist/blob-store/index.d.ts +34 -29
  8. package/dist/blob-store/index.d.ts.map +1 -1
  9. package/dist/blob-store/utils.d.ts +27 -0
  10. package/dist/blob-store/utils.d.ts.map +1 -0
  11. package/dist/constants.d.ts +2 -1
  12. package/dist/constants.d.ts.map +1 -1
  13. package/dist/core-manager/index.d.ts +11 -1
  14. package/dist/core-manager/index.d.ts.map +1 -1
  15. package/dist/core-ownership.d.ts.map +1 -1
  16. package/dist/datastore/index.d.ts +5 -4
  17. package/dist/datastore/index.d.ts.map +1 -1
  18. package/dist/datatype/index.d.ts +5 -1
  19. package/dist/discovery/local-discovery.d.ts.map +1 -1
  20. package/dist/errors.d.ts +6 -1
  21. package/dist/errors.d.ts.map +1 -1
  22. package/dist/fastify-plugins/blobs.d.ts.map +1 -1
  23. package/dist/fastify-plugins/maps.d.ts.map +1 -1
  24. package/dist/generated/extensions.d.ts +31 -0
  25. package/dist/generated/extensions.d.ts.map +1 -1
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/lib/drizzle-helpers.d.ts +6 -0
  29. package/dist/lib/drizzle-helpers.d.ts.map +1 -0
  30. package/dist/lib/error.d.ts +51 -0
  31. package/dist/lib/error.d.ts.map +1 -0
  32. package/dist/lib/get-own.d.ts +9 -0
  33. package/dist/lib/get-own.d.ts.map +1 -0
  34. package/dist/lib/is-hostname-ip-address.d.ts +17 -0
  35. package/dist/lib/is-hostname-ip-address.d.ts.map +1 -0
  36. package/dist/lib/ws-core-replicator.d.ts +11 -0
  37. package/dist/lib/ws-core-replicator.d.ts.map +1 -0
  38. package/dist/mapeo-manager.d.ts +18 -22
  39. package/dist/mapeo-manager.d.ts.map +1 -1
  40. package/dist/mapeo-project.d.ts +459 -26
  41. package/dist/mapeo-project.d.ts.map +1 -1
  42. package/dist/member-api.d.ts +44 -1
  43. package/dist/member-api.d.ts.map +1 -1
  44. package/dist/roles.d.ts.map +1 -1
  45. package/dist/schema/client.d.ts +17 -5
  46. package/dist/schema/client.d.ts.map +1 -1
  47. package/dist/schema/project.d.ts +212 -2
  48. package/dist/schema/project.d.ts.map +1 -1
  49. package/dist/sync/core-sync-state.d.ts +20 -15
  50. package/dist/sync/core-sync-state.d.ts.map +1 -1
  51. package/dist/sync/namespace-sync-state.d.ts +13 -1
  52. package/dist/sync/namespace-sync-state.d.ts.map +1 -1
  53. package/dist/sync/peer-sync-controller.d.ts +1 -1
  54. package/dist/sync/peer-sync-controller.d.ts.map +1 -1
  55. package/dist/sync/sync-api.d.ts +47 -2
  56. package/dist/sync/sync-api.d.ts.map +1 -1
  57. package/dist/sync/sync-state.d.ts +12 -0
  58. package/dist/sync/sync-state.d.ts.map +1 -1
  59. package/dist/translation-api.d.ts +2 -2
  60. package/dist/translation-api.d.ts.map +1 -1
  61. package/dist/types.d.ts +10 -2
  62. package/dist/types.d.ts.map +1 -1
  63. package/drizzle/client/0001_chubby_cargill.sql +12 -0
  64. package/drizzle/client/meta/0001_snapshot.json +208 -0
  65. package/drizzle/client/meta/_journal.json +7 -0
  66. package/drizzle/project/0001_medical_wendell_rand.sql +22 -0
  67. package/drizzle/project/meta/0001_snapshot.json +1267 -0
  68. package/drizzle/project/meta/_journal.json +7 -0
  69. package/package.json +14 -5
  70. package/src/blob-store/downloader.js +130 -0
  71. package/src/blob-store/entries-stream.js +81 -0
  72. package/src/blob-store/hyperdrive-index.js +122 -0
  73. package/src/blob-store/index.js +59 -117
  74. package/src/blob-store/utils.js +54 -0
  75. package/src/constants.js +4 -1
  76. package/src/core-manager/index.js +60 -3
  77. package/src/core-ownership.js +2 -4
  78. package/src/datastore/README.md +1 -2
  79. package/src/datastore/index.js +8 -8
  80. package/src/datatype/index.d.ts +5 -1
  81. package/src/datatype/index.js +22 -9
  82. package/src/discovery/local-discovery.js +2 -1
  83. package/src/errors.js +11 -2
  84. package/src/fastify-plugins/blobs.js +17 -1
  85. package/src/fastify-plugins/maps.js +2 -1
  86. package/src/generated/extensions.d.ts +31 -0
  87. package/src/generated/extensions.js +150 -0
  88. package/src/generated/extensions.ts +181 -0
  89. package/src/index.js +10 -0
  90. package/src/invite-api.js +1 -1
  91. package/src/lib/drizzle-helpers.js +79 -0
  92. package/src/lib/error.js +71 -0
  93. package/src/lib/get-own.js +10 -0
  94. package/src/lib/is-hostname-ip-address.js +26 -0
  95. package/src/lib/ws-core-replicator.js +47 -0
  96. package/src/mapeo-manager.js +74 -45
  97. package/src/mapeo-project.js +238 -58
  98. package/src/member-api.js +295 -2
  99. package/src/roles.js +38 -32
  100. package/src/schema/client.js +4 -3
  101. package/src/schema/project.js +7 -0
  102. package/src/sync/core-sync-state.js +39 -23
  103. package/src/sync/namespace-sync-state.js +22 -0
  104. package/src/sync/peer-sync-controller.js +1 -0
  105. package/src/sync/sync-api.js +197 -3
  106. package/src/sync/sync-state.js +18 -0
  107. package/src/translation-api.js +5 -9
  108. package/src/types.ts +12 -3
  109. package/dist/blob-store/live-download.d.ts +0 -107
  110. package/dist/blob-store/live-download.d.ts.map +0 -1
  111. package/dist/lib/timing-safe-equal.d.ts +0 -15
  112. package/dist/lib/timing-safe-equal.d.ts.map +0 -1
  113. package/src/blob-store/live-download.js +0 -373
  114. package/src/lib/timing-safe-equal.js +0 -34
@@ -1,4 +1,5 @@
1
1
  import { TypedEmitter } from 'tiny-typed-emitter'
2
+ import WebSocket from 'ws'
2
3
  import { SyncState } from './sync-state.js'
3
4
  import { PeerSyncController } from './peer-sync-controller.js'
4
5
  import { Logger } from '../logger.js'
@@ -8,15 +9,25 @@ import {
8
9
  PRESYNC_NAMESPACES,
9
10
  } from '../constants.js'
10
11
  import { ExhaustivenessError, assert, keyToId, noop } from '../utils.js'
12
+ import { getOwn } from '../lib/get-own.js'
13
+ import { wsCoreReplicator } from '../lib/ws-core-replicator.js'
11
14
  import { NO_ROLE_ID } from '../roles.js'
12
15
  /** @import { CoreOwnership as CoreOwnershipDoc } from '@comapeo/schema' */
16
+ /** @import * as http from 'node:http' */
13
17
  /** @import { CoreOwnership } from '../core-ownership.js' */
14
18
  /** @import { OpenedNoiseStream } from '../lib/noise-secret-stream-helpers.js' */
19
+ /** @import { BlobFilter, ReplicationStream } from '../types.js' */
15
20
 
16
21
  export const kHandleDiscoveryKey = Symbol('handle discovery key')
17
22
  export const kSyncState = Symbol('sync state')
18
23
  export const kRequestFullStop = Symbol('background')
19
24
  export const kRescindFullStopRequest = Symbol('foreground')
25
+ export const kWaitForInitialSyncWithPeer = Symbol(
26
+ 'wait for initial sync with peer'
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')
20
31
 
21
32
  /**
22
33
  * @typedef {'initial' | 'full'} SyncType
@@ -65,6 +76,7 @@ export class SyncApi extends TypedEmitter {
65
76
  /** @type {Map<string, PeerSyncController>} */
66
77
  #pscByPeerId = new Map()
67
78
  #wantsToSyncData = false
79
+ #wantsToConnectToServers = false
68
80
  #hasRequestedFullStop = false
69
81
  /** @type {SyncEnabledState} */
70
82
  #previousSyncEnabledState = 'none'
@@ -77,22 +89,41 @@ export class SyncApi extends TypedEmitter {
77
89
  /** @type {Map<import('protomux'), Set<Buffer>>} */
78
90
  #pendingDiscoveryKeys = new Map()
79
91
  #l
92
+ #getServerWebsocketUrls
93
+ #getReplicationStream
94
+ /** @type {Map<string, WebSocket>} */
95
+ #serverWebsockets = new Map()
96
+ /** @type {null | BlobFilter} */
97
+ #blobDownloadFilter = null
80
98
 
81
99
  /**
82
- *
83
100
  * @param {object} opts
84
101
  * @param {import('../core-manager/index.js').CoreManager} opts.coreManager
85
102
  * @param {CoreOwnership} opts.coreOwnership
86
103
  * @param {import('../roles.js').Roles} opts.roles
104
+ * @param {() => Promise<Iterable<string>>} opts.getServerWebsocketUrls
105
+ * @param {() => ReplicationStream} opts.getReplicationStream
106
+ * @param {null | BlobFilter} opts.blobDownloadFilter
87
107
  * @param {number} [opts.throttleMs]
88
108
  * @param {Logger} [opts.logger]
89
109
  */
90
- constructor({ coreManager, throttleMs = 200, roles, logger, coreOwnership }) {
110
+ constructor({
111
+ coreManager,
112
+ throttleMs = 200,
113
+ roles,
114
+ getServerWebsocketUrls,
115
+ getReplicationStream,
116
+ logger,
117
+ coreOwnership,
118
+ blobDownloadFilter,
119
+ }) {
91
120
  super()
92
121
  this.#l = Logger.create('syncApi', logger)
93
122
  this.#coreManager = coreManager
94
123
  this.#coreOwnership = coreOwnership
95
124
  this.#roles = roles
125
+ this.#getServerWebsocketUrls = getServerWebsocketUrls
126
+ this.#getReplicationStream = getReplicationStream
96
127
  this[kSyncState] = new SyncState({
97
128
  coreManager,
98
129
  throttleMs,
@@ -104,6 +135,8 @@ export class SyncApi extends TypedEmitter {
104
135
  this.#updateState(namespaceSyncState)
105
136
  })
106
137
 
138
+ this[kSetBlobDownloadFilter](blobDownloadFilter)
139
+
107
140
  this.#coreManager.creatorCore.on('peer-add', this.#handlePeerAdd)
108
141
  this.#coreManager.creatorCore.on('peer-remove', this.#handlePeerDisconnect)
109
142
 
@@ -123,6 +156,37 @@ export class SyncApi extends TypedEmitter {
123
156
  .catch(noop)
124
157
  }
125
158
 
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
+
126
190
  /** @type {import('../local-peers.js').LocalPeersEvents['discovery-key']} */
127
191
  [kHandleDiscoveryKey](discoveryKey, protomux) {
128
192
  const peerSyncController = this.#peerSyncControllers.get(protomux)
@@ -274,6 +338,74 @@ export class SyncApi extends TypedEmitter {
274
338
  this.emit('sync-state', this.#getState(namespaceSyncState))
275
339
  }
276
340
 
341
+ /**
342
+ * @returns {void}
343
+ */
344
+ connectServers() {
345
+ this.#wantsToConnectToServers = true
346
+
347
+ this.#getServerWebsocketUrls()
348
+ .then((urls) => {
349
+ const hasDisconnectedSinceWebsocketUrlsRequestFinished =
350
+ !this.#wantsToConnectToServers
351
+ if (hasDisconnectedSinceWebsocketUrlsRequestFinished) return
352
+
353
+ for (const url of urls) {
354
+ const existingWebsocket = this.#serverWebsockets.get(url)
355
+ if (
356
+ existingWebsocket &&
357
+ (existingWebsocket.readyState === WebSocket.OPEN ||
358
+ existingWebsocket.readyState === WebSocket.CONNECTING)
359
+ ) {
360
+ continue
361
+ }
362
+
363
+ const websocket = new WebSocket(url)
364
+
365
+ /** @param {Error} err */
366
+ const onWebsocketError = (err) => {
367
+ this.#l.log('Ignoring WebSocket error to %s: %o', url, err)
368
+ }
369
+ websocket.on('error', onWebsocketError)
370
+
371
+ /**
372
+ * @param {unknown} _req
373
+ * @param {http.IncomingMessage} res
374
+ */
375
+ const onWebsocketUnexpectedResponse = (_req, res) => {
376
+ this.#l.log(
377
+ 'Ignoring unexpected %d WebSocket response to %s',
378
+ res.statusCode,
379
+ url
380
+ )
381
+ }
382
+ websocket.on('unexpected-response', onWebsocketUnexpectedResponse)
383
+
384
+ const replicationStream = this.#getReplicationStream()
385
+ wsCoreReplicator(websocket, replicationStream)
386
+
387
+ this.#serverWebsockets.set(url, websocket)
388
+ websocket.once('close', () => {
389
+ websocket.off('error', onWebsocketError)
390
+ websocket.off('unexpected-response', onWebsocketUnexpectedResponse)
391
+ this.#serverWebsockets.delete(url)
392
+ })
393
+ }
394
+ })
395
+ .catch(noop)
396
+ }
397
+
398
+ /**
399
+ * @returns {void}
400
+ */
401
+ disconnectServers() {
402
+ for (const websocket of this.#serverWebsockets.values()) {
403
+ websocket.close()
404
+ }
405
+ this.#serverWebsockets.clear()
406
+ this.#wantsToConnectToServers = false
407
+ }
408
+
277
409
  /**
278
410
  * Start syncing data cores.
279
411
  *
@@ -348,6 +480,40 @@ export class SyncApi extends TypedEmitter {
348
480
  })
349
481
  }
350
482
 
483
+ /**
484
+ * @param {string} deviceId
485
+ * @param {AbortSignal} abortSignal
486
+ * @returns {Promise<void>}
487
+ */
488
+ async [kWaitForInitialSyncWithPeer](deviceId, abortSignal) {
489
+ abortSignal.throwIfAborted()
490
+
491
+ const state = this[kSyncState].getState()
492
+ if (isInitiallySyncedWithPeer(state, deviceId)) return
493
+
494
+ return new Promise((resolve, reject) => {
495
+ /** @param {import('./sync-state.js').State} state */
496
+ const onState = (state) => {
497
+ if (isInitiallySyncedWithPeer(state, deviceId)) {
498
+ cleanup()
499
+ resolve()
500
+ }
501
+ }
502
+ const onAbort = () => {
503
+ cleanup()
504
+ reject(abortSignal.reason)
505
+ }
506
+
507
+ const cleanup = () => {
508
+ this[kSyncState].off('state', onState)
509
+ abortSignal.removeEventListener('abort', onAbort)
510
+ }
511
+
512
+ this[kSyncState].on('state', onState)
513
+ abortSignal.addEventListener('abort', onAbort)
514
+ })
515
+ }
516
+
351
517
  #clearAutostopDataSyncTimeoutIfExists() {
352
518
  if (this.#autostopDataSyncTimeout) {
353
519
  clearTimeout(this.#autostopDataSyncTimeout)
@@ -363,7 +529,7 @@ export class SyncApi extends TypedEmitter {
363
529
  * will then handle validation of role records to ensure that the peer is
364
530
  * actually still part of the project.
365
531
  *
366
- * @param {{ protomux: import('protomux')<OpenedNoiseStream> }} peer
532
+ * @param {import('../types.js').HypercorePeer & { protomux: import('protomux')<OpenedNoiseStream> }} peer
367
533
  */
368
534
  #handlePeerAdd = (peer) => {
369
535
  const { protomux } = peer
@@ -374,6 +540,9 @@ export class SyncApi extends TypedEmitter {
374
540
  )
375
541
  return
376
542
  }
543
+ if (this.#blobDownloadFilter) {
544
+ this.#coreManager.sendDownloadIntents(this.#blobDownloadFilter, peer)
545
+ }
377
546
  const peerSyncController = new PeerSyncController({
378
547
  protomux,
379
548
  coreManager: this.#coreManager,
@@ -524,6 +693,31 @@ function isSynced(state, type, peerSyncControllers) {
524
693
  return true
525
694
  }
526
695
 
696
+ /**
697
+ * @param {import('./sync-state.js').State} state
698
+ * @param {string} peerId
699
+ */
700
+ function isInitiallySyncedWithPeer(state, peerId) {
701
+ for (const ns of PRESYNC_NAMESPACES) {
702
+ const remoteDeviceSyncState = getOwn(state[ns].remoteStates, peerId)
703
+ if (!remoteDeviceSyncState) return false
704
+
705
+ switch (remoteDeviceSyncState.status) {
706
+ case 'starting':
707
+ return false
708
+ case 'started':
709
+ case 'stopped': {
710
+ const { want, wanted } = remoteDeviceSyncState
711
+ if (want || wanted) return false
712
+ break
713
+ }
714
+ default:
715
+ throw new ExhaustivenessError(remoteDeviceSyncState.status)
716
+ }
717
+ }
718
+ return true
719
+ }
720
+
527
721
  /**
528
722
  * @param {import('./sync-state.js').State} namespaceSyncState
529
723
  * @param {Iterable<PeerSyncController>} peerSyncControllers
@@ -68,6 +68,24 @@ export class SyncState extends TypedEmitter {
68
68
  ])
69
69
  }
70
70
 
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
+
71
89
  #handleUpdate = () => {
72
90
  this.emit('state', this.getState())
73
91
  }
@@ -1,7 +1,7 @@
1
1
  import { and, sql } from 'drizzle-orm'
2
2
  import { kCreateWithDocId, kSelect } from './datatype/index.js'
3
3
  import { hashObject } from './utils.js'
4
- import { NotFoundError } from './errors.js'
4
+ import { nullIfNotFound } from './errors.js'
5
5
  import { omit } from './lib/omit.js'
6
6
  /** @import { Translation, TranslationValue } from '@comapeo/schema' */
7
7
  /** @import { SetOptional } from 'type-fest' */
@@ -50,15 +50,11 @@ export default class TranslationApi {
50
50
  async put(value) {
51
51
  const identifiers = omit(value, ['message'])
52
52
  const docId = hashObject(identifiers)
53
- try {
54
- const doc = await this.#dataType.getByDocId(docId)
53
+ const doc = await this.#dataType.getByDocId(docId).catch(nullIfNotFound)
54
+ if (doc) {
55
55
  return await this.#dataType.update(doc.versionId, value)
56
- } catch (e) {
57
- if (e instanceof NotFoundError) {
58
- return await this.#dataType[kCreateWithDocId](docId, value)
59
- } else {
60
- throw new Error(`Error on translation ${e}`)
61
- }
56
+ } else {
57
+ return await this.#dataType[kCreateWithDocId](docId, value)
62
58
  }
63
59
  }
64
60
 
package/src/types.ts CHANGED
@@ -14,6 +14,8 @@ import { Duplex } from 'streamx'
14
14
  import RandomAccessStorage from 'random-access-storage'
15
15
  import { DefaultListener, ListenerSignature } from 'tiny-typed-emitter'
16
16
  import type { NAMESPACES } from './constants.js'
17
+ import type { Readable } from 'stream'
18
+ import type { HyperdriveEntry } from 'hyperdrive'
17
19
 
18
20
  export type Namespace = (typeof NAMESPACES)[number]
19
21
 
@@ -41,12 +43,13 @@ export type BlobId = Simplify<
41
43
  }>
42
44
  >
43
45
 
44
- type ArrayAtLeastOne<T> = [T, ...T[]]
45
-
46
46
  export type BlobFilter = RequireAtLeastOne<{
47
- [KeyType in BlobType]: ArrayAtLeastOne<BlobVariant<KeyType>>
47
+ [KeyType in BlobType]: Array<BlobVariant<KeyType>>
48
48
  }>
49
49
 
50
+ /** Map of blob types to array of blob variants */
51
+ export type GenericBlobFilter = Record<string, string[]>
52
+
50
53
  export type MapeoDocMap = {
51
54
  [K in MapeoDoc['schemaName']]: Extract<MapeoDoc, { schemaName: K }>
52
55
  }
@@ -146,3 +149,9 @@ export type DefaultEmitterEvents<
146
149
  newListener: (event: keyof L, listener: L[keyof L]) => void
147
150
  removeListener: (event: keyof L, listener: L[keyof L]) => void
148
151
  }
152
+
153
+ export type BlobStoreEntriesStream = Readable & {
154
+ [Symbol.asyncIterator](): AsyncIterableIterator<
155
+ HyperdriveEntry & { driveId: string }
156
+ >
157
+ }
@@ -1,107 +0,0 @@
1
- /**
2
- * Reduce multiple states into one. Factored out for unit testing because I
3
- * don't trust my coding. Probably a smarter way to do this, but this works.
4
- *
5
- * @param {Iterable<{ state: BlobDownloadState | BlobDownloadStateError }>} liveDownloads
6
- * @param {{ signal?: AbortSignal }} options
7
- * @returns {BlobDownloadState | BlobDownloadStateError}
8
- */
9
- export function combineStates(liveDownloads: Iterable<{
10
- state: BlobDownloadState | BlobDownloadStateError;
11
- }>, { signal }?: {
12
- signal?: AbortSignal;
13
- }): BlobDownloadState | BlobDownloadStateError;
14
- /**
15
- * @typedef {object} BlobDownloadState
16
- * @property {number} haveCount The number of files already downloaded
17
- * @property {number} haveBytes The bytes already downloaded
18
- * @property {number} wantCount The number of files pending download
19
- * @property {number} wantBytes The bytes pending download
20
- * @property {null} error If status = 'error' then this will be an Error object
21
- * @property {'checking' | 'downloading' | 'downloaded' | 'aborted'} status
22
- */
23
- /** @typedef {Omit<BlobDownloadState, 'error' | 'status'> & { status: 'error', error: Error }} BlobDownloadStateError */
24
- /**
25
- * @typedef {object} BlobDownloadEvents
26
- * @property {(state: BlobDownloadState | BlobDownloadStateError ) => void} state Emitted with the current download state whenever it changes (not emitted during initial 'checking' status)
27
- */
28
- /**
29
- * LiveDownload class
30
- * @extends {TypedEmitter<BlobDownloadEvents>}
31
- */
32
- export class LiveDownload extends TypedEmitter<BlobDownloadEvents> {
33
- /**
34
- * Like drive.download() but 'live', and for multiple drives
35
- * @param {Iterable<import('hyperdrive')>} drives
36
- * @param {import('./index.js').InternalDriveEmitter} emitter
37
- * @param {object} options
38
- * @param {import('../types.js').BlobFilter} [options.filter] Filter blobs of specific types and/or sizes to download
39
- * @param {AbortSignal} [options.signal]
40
- */
41
- constructor(drives: Iterable<import("hyperdrive")>, emitter: import("./index.js").InternalDriveEmitter, { filter, signal }: {
42
- filter?: import("../types.js").BlobFilter | undefined;
43
- signal?: AbortSignal | undefined;
44
- });
45
- /**
46
- * @returns {BlobDownloadState | BlobDownloadStateError}
47
- */
48
- get state(): BlobDownloadState | BlobDownloadStateError;
49
- #private;
50
- }
51
- /**
52
- * LiveDownload class
53
- * @extends {TypedEmitter<BlobDownloadEvents>}
54
- */
55
- export class DriveLiveDownload extends TypedEmitter<BlobDownloadEvents> {
56
- /**
57
- * Like drive.download() but 'live',
58
- * @param {import('hyperdrive')} drive
59
- * @param {object} options
60
- * @param {import('../types.js').BlobFilter} [options.filter] Filter blobs of specific types and/or sizes to download
61
- * @param {AbortSignal} [options.signal]
62
- */
63
- constructor(drive: import("hyperdrive"), { filter, signal }?: {
64
- filter?: import("../types.js").BlobFilter | undefined;
65
- signal?: AbortSignal | undefined;
66
- });
67
- /**
68
- * @returns {BlobDownloadState | BlobDownloadStateError}
69
- */
70
- get state(): BlobDownloadState | BlobDownloadStateError;
71
- #private;
72
- }
73
- export type BlobDownloadState = {
74
- /**
75
- * The number of files already downloaded
76
- */
77
- haveCount: number;
78
- /**
79
- * The bytes already downloaded
80
- */
81
- haveBytes: number;
82
- /**
83
- * The number of files pending download
84
- */
85
- wantCount: number;
86
- /**
87
- * The bytes pending download
88
- */
89
- wantBytes: number;
90
- /**
91
- * If status = 'error' then this will be an Error object
92
- */
93
- error: null;
94
- status: "checking" | "downloading" | "downloaded" | "aborted";
95
- };
96
- export type BlobDownloadStateError = Omit<BlobDownloadState, "error" | "status"> & {
97
- status: "error";
98
- error: Error;
99
- };
100
- export type BlobDownloadEvents = {
101
- /**
102
- * Emitted with the current download state whenever it changes (not emitted during initial 'checking' status)
103
- */
104
- state: (state: BlobDownloadState | BlobDownloadStateError) => void;
105
- };
106
- import { TypedEmitter } from 'tiny-typed-emitter';
107
- //# sourceMappingURL=live-download.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"live-download.d.ts","sourceRoot":"","sources":["../../src/blob-store/live-download.js"],"names":[],"mappings":"AA+RA;;;;;;;GAOG;AACH,6CAJW,QAAQ,CAAC;IAAE,KAAK,EAAE,iBAAiB,GAAG,sBAAsB,CAAA;CAAE,CAAC,eAC/D;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GACtB,iBAAiB,GAAG,sBAAsB,CAqCtD;AApUD;;;;;;;;GAQG;AAEH,wHAAwH;AAExH;;;GAGG;AAEH;;;GAGG;AACH;IAKE;;;;;;;OAOG;IACH,oBANW,QAAQ,CAAC,OAAO,YAAY,CAAC,CAAC,WAC9B,OAAO,YAAY,EAAE,oBAAoB,sBAEjD;QAAmD,MAAM;QAC3B,MAAM;KAAC,EAiCvC;IAED;;OAEG;IACH,wDAEC;;CACF;AAED;;;GAGG;AACH;IAaE;;;;;;OAMG;IACH,mBALW,OAAO,YAAY,CAAC,uBAE5B;QAAmD,MAAM;QAC3B,MAAM;KAAC,EAmBvC;IAED;;OAEG;IACH,wDAyBC;;CAqIF;;;;;eArRa,MAAM;;;;eACN,MAAM;;;;eACN,MAAM;;;;eACN,MAAM;;;;WACN,IAAI;YACJ,UAAU,GAAG,aAAa,GAAG,YAAY,GAAG,SAAS;;qCAGrD,IAAI,CAAC,iBAAiB,EAAE,OAAO,GAAG,QAAQ,CAAC,GAAG;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE;;;;;WAI/E,CAAC,KAAK,EAAE,iBAAiB,GAAG,sBAAsB,KAAM,IAAI;;6BApB7C,oBAAoB"}
@@ -1,15 +0,0 @@
1
- /**
2
- * Compare two values in constant time.
3
- *
4
- * Useful when you want to avoid leaking data.
5
- *
6
- * Like `crypto.timingSafeEqual`, but works with strings and doesn't throw if
7
- * lengths differ.
8
- *
9
- * @template {string | NodeJS.ArrayBufferView} T
10
- * @param {T} a
11
- * @param {T} b
12
- * @returns {boolean}
13
- */
14
- export default function timingSafeEqual<T extends string | NodeJS.ArrayBufferView>(a: T, b: T): boolean;
15
- //# sourceMappingURL=timing-safe-equal.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"timing-safe-equal.d.ts","sourceRoot":"","sources":["../../src/lib/timing-safe-equal.js"],"names":[],"mappings":"AAaA;;;;;;;;;;;;GAYG;AACH,wCAL+C,CAAC,SAAlC,MAAM,GAAG,MAAM,CAAC,eAAgB,KACnC,CAAC,KACD,CAAC,GACC,OAAO,CASnB"}