@comapeo/core 5.5.0 → 6.0.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 (94) hide show
  1. package/dist/blob-api.d.ts.map +1 -1
  2. package/dist/blob-store/downloader.d.ts.map +1 -1
  3. package/dist/blob-store/hyperdrive-index.d.ts.map +1 -1
  4. package/dist/blob-store/index.d.ts.map +1 -1
  5. package/dist/core-manager/bitfield-rle.d.ts.map +1 -1
  6. package/dist/core-manager/core-index.d.ts.map +1 -1
  7. package/dist/core-manager/index.d.ts.map +1 -1
  8. package/dist/core-ownership.d.ts.map +1 -1
  9. package/dist/datastore/index.d.ts.map +1 -1
  10. package/dist/datatype/index.d.ts +7 -0
  11. package/dist/datatype/index.d.ts.map +1 -1
  12. package/dist/discovery/local-discovery.d.ts.map +1 -1
  13. package/dist/errors.d.ts +437 -35
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/fastify-plugins/blobs.d.ts.map +1 -1
  16. package/dist/fastify-plugins/icons.d.ts.map +1 -1
  17. package/dist/fastify-plugins/maps.d.ts.map +1 -1
  18. package/dist/generated/rpc.d.ts +1 -0
  19. package/dist/generated/rpc.d.ts.map +1 -1
  20. package/dist/icon-api.d.ts +0 -1
  21. package/dist/icon-api.d.ts.map +1 -1
  22. package/dist/import-categories.d.ts.map +1 -1
  23. package/dist/index-writer/index.d.ts.map +1 -1
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/intl/parse-bcp-47.d.ts.map +1 -1
  27. package/dist/invite/invite-api.d.ts.map +1 -1
  28. package/dist/lib/drizzle-helpers.d.ts.map +1 -1
  29. package/dist/lib/hypercore-helpers.d.ts.map +1 -1
  30. package/dist/lib/key-by.d.ts.map +1 -1
  31. package/dist/local-peers.d.ts +0 -14
  32. package/dist/local-peers.d.ts.map +1 -1
  33. package/dist/logger.d.ts.map +1 -1
  34. package/dist/mapeo-manager.d.ts +2 -1
  35. package/dist/mapeo-manager.d.ts.map +1 -1
  36. package/dist/mapeo-project.d.ts +1 -3
  37. package/dist/mapeo-project.d.ts.map +1 -1
  38. package/dist/member-api.d.ts +42 -7
  39. package/dist/member-api.d.ts.map +1 -1
  40. package/dist/roles.d.ts.map +1 -1
  41. package/dist/schema/json-schema-to-drizzle.d.ts.map +1 -1
  42. package/dist/schema.d.ts +2 -0
  43. package/dist/schema.d.ts.map +1 -0
  44. package/dist/sync/core-sync-state.d.ts.map +1 -1
  45. package/dist/sync/peer-sync-controller.d.ts.map +1 -1
  46. package/dist/sync/sync-api.d.ts.map +1 -1
  47. package/dist/utils.d.ts +8 -10
  48. package/dist/utils.d.ts.map +1 -1
  49. package/package.json +18 -2
  50. package/src/blob-api.js +24 -4
  51. package/src/blob-store/downloader.js +7 -6
  52. package/src/blob-store/entries-stream.js +1 -1
  53. package/src/blob-store/hyperdrive-index.js +3 -5
  54. package/src/blob-store/index.js +15 -20
  55. package/src/core-manager/bitfield-rle.js +2 -1
  56. package/src/core-manager/core-index.js +2 -1
  57. package/src/core-manager/index.js +9 -10
  58. package/src/core-ownership.js +7 -3
  59. package/src/datastore/index.js +13 -9
  60. package/src/datatype/index.js +28 -5
  61. package/src/discovery/local-discovery.js +8 -7
  62. package/src/errors.js +530 -62
  63. package/src/fastify-controller.js +3 -3
  64. package/src/fastify-plugins/blobs.js +21 -14
  65. package/src/fastify-plugins/icons.js +18 -9
  66. package/src/fastify-plugins/maps.js +6 -5
  67. package/src/generated/rpc.d.ts +1 -0
  68. package/src/generated/rpc.js +12 -1
  69. package/src/generated/rpc.ts +13 -0
  70. package/src/icon-api.js +15 -7
  71. package/src/import-categories.js +6 -7
  72. package/src/index-writer/index.js +3 -2
  73. package/src/index.js +1 -0
  74. package/src/intl/parse-bcp-47.js +2 -1
  75. package/src/invite/invite-api.js +26 -20
  76. package/src/lib/drizzle-helpers.js +54 -39
  77. package/src/lib/hypercore-helpers.js +4 -2
  78. package/src/lib/key-by.js +3 -1
  79. package/src/local-peers.js +39 -46
  80. package/src/logger.js +2 -1
  81. package/src/mapeo-manager.js +36 -23
  82. package/src/mapeo-project.js +68 -61
  83. package/src/member-api.js +177 -96
  84. package/src/roles.js +11 -10
  85. package/src/schema/json-schema-to-drizzle.js +13 -4
  86. package/src/schema.js +1 -0
  87. package/src/sync/core-sync-state.js +2 -1
  88. package/src/sync/peer-sync-controller.js +4 -3
  89. package/src/sync/sync-api.js +9 -9
  90. package/src/translation-api.js +2 -2
  91. package/src/utils.js +56 -41
  92. package/dist/lib/error.d.ts +0 -51
  93. package/dist/lib/error.d.ts.map +0 -1
  94. package/src/lib/error.js +0 -71
@@ -8,10 +8,11 @@ import {
8
8
  NAMESPACES,
9
9
  PRESYNC_NAMESPACES,
10
10
  } from '../constants.js'
11
- import { ExhaustivenessError, assert, keyToId, noop } from '../utils.js'
11
+ import { keyToId, noop } from '../utils.js'
12
12
  import { getOwn } from '../lib/get-own.js'
13
13
  import { wsCoreReplicator } from '../lib/ws-core-replicator.js'
14
14
  import { NO_ROLE_ID } from '../roles.js'
15
+ import { AutoStopTimeoutError, ExhaustivenessError } from '../errors.js'
15
16
  /** @import { CoreOwnership as CoreOwnershipDoc } from '@comapeo/schema' */
16
17
  /** @import * as http from 'node:http' */
17
18
  /** @import { CoreOwnership } from '../core-ownership.js' */
@@ -229,7 +230,7 @@ export class SyncApi extends TypedEmitter {
229
230
  remoteDeviceSyncState,
230
231
  }
231
232
  default:
232
- throw new ExhaustivenessError(this.#previousSyncEnabledState)
233
+ throw new ExhaustivenessError({ value: this.#previousSyncEnabledState })
233
234
  }
234
235
  }
235
236
 
@@ -619,7 +620,7 @@ export class SyncApi extends TypedEmitter {
619
620
  coreOwnershipDocId
620
621
  )
621
622
  await this.#validateRoleAndAddCoresForPeer(coreOwnershipDoc)
622
- } catch (_) {
623
+ } catch {
623
624
  // Ignore, we'll add these when the role is added
624
625
  }
625
626
  })()
@@ -655,10 +656,9 @@ export class SyncApi extends TypedEmitter {
655
656
  */
656
657
  function assertAutostopDataSyncAfterIsValid(ms) {
657
658
  if (ms === null) return
658
- assert(
659
- ms > 0 && ms <= 2 ** 31 - 1 && Number.isSafeInteger(ms),
660
- 'auto-stop timeout must be Infinity or a positive integer between 0 and the largest 32-bit signed integer'
661
- )
659
+ if (!(ms > 0 && ms <= 2 ** 31 - 1 && Number.isSafeInteger(ms))) {
660
+ throw new AutoStopTimeoutError()
661
+ }
662
662
  }
663
663
 
664
664
  /**
@@ -701,7 +701,7 @@ function isInitiallySyncedWithPeer(state, peerId) {
701
701
  break
702
702
  }
703
703
  default:
704
- throw new ExhaustivenessError(remoteDeviceSyncState.status)
704
+ throw new ExhaustivenessError({ value: remoteDeviceSyncState.status })
705
705
  }
706
706
  }
707
707
  return true
@@ -740,7 +740,7 @@ function getRemoteDevicesSyncState(namespaceSyncState, peerSyncControllers) {
740
740
  isSyncEnabled = true
741
741
  break
742
742
  default:
743
- throw new ExhaustivenessError(peerNamespaceState.status)
743
+ throw new ExhaustivenessError({ value: peerNamespaceState.status })
744
744
  }
745
745
 
746
746
  if (!Object.hasOwn(result, peerId)) {
@@ -39,8 +39,8 @@ export default class TranslationApi {
39
39
  .then((docs) => {
40
40
  docs.map((doc) => this.index(doc))
41
41
  })
42
- .catch((err) => {
43
- console.error(`error loading Translation cache: ${err}`)
42
+ .catch((e) => {
43
+ console.error(`error loading Translation cache: ${e}`)
44
44
  })
45
45
  }
46
46
 
package/src/utils.js CHANGED
@@ -3,13 +3,23 @@ import { keyToPublicId } from '@mapeo/crypto'
3
3
  import { createHash } from 'node:crypto'
4
4
  import stableStringify from 'json-stable-stringify'
5
5
  import { omit } from './lib/omit.js'
6
+ import {
7
+ UnsupportedAttachmentTypeError,
8
+ TimeoutError,
9
+ ensureKnownError,
10
+ InvalidMapShareError,
11
+ } from './errors.js'
12
+ import pTimeout, { TimeoutError as pTimeoutError } from 'p-timeout'
6
13
 
7
14
  /** @import { MapShareExtension } from './generated/extensions.js' */
8
15
  /** @import {Attachment, BlobId} from "./types.js" */
9
16
 
10
17
  const PROJECT_INVITE_ID_SALT = Buffer.from('mapeo project invite id', 'ascii')
11
18
 
12
- export const MAX_BOUNDS = [-180, -85.051129, 180, 85.051129]
19
+ // Slightly larger than spherical mercator bounds for a square map - rounds up
20
+ // from 85.051129 to allow for floating point errors in the bounds of some maps
21
+ // in userspace.
22
+ export const MAX_BOUNDS = [-180, -85.05113, 180, 85.05113]
13
23
 
14
24
  /**
15
25
  *
@@ -24,33 +34,11 @@ export function keyToId(key) {
24
34
  return key.toString('hex')
25
35
  }
26
36
 
27
- export class ExhaustivenessError extends Error {
28
- /** @param {never} value */
29
- constructor(value) {
30
- super(`Exhaustiveness check failed. ${value} should be impossible`)
31
- this.name = 'ExhaustivenessError'
32
- }
33
- }
34
-
35
37
  /**
36
38
  * @returns {void}
37
39
  */
38
40
  export function noop() {}
39
41
 
40
- /**
41
- * @param {unknown} condition
42
- * @param {string | Error} messageOrError
43
- * @returns {asserts condition}
44
- */
45
- export function assert(condition, messageOrError) {
46
- if (condition) return
47
- if (typeof messageOrError === 'string') {
48
- throw new Error(messageOrError)
49
- } else {
50
- throw messageOrError
51
- }
52
- }
53
-
54
42
  /**
55
43
  * Return a function that itself returns whether a value is part of the set.
56
44
  *
@@ -224,7 +212,7 @@ export function buildBlobId(attachment, requestedVariant) {
224
212
  attachment.type !== 'audio' &&
225
213
  attachment.type !== 'video'
226
214
  ) {
227
- throw new Error(`Cannot fetch URL for attachment type "${attachment.type}"`)
215
+ throw new UnsupportedAttachmentTypeError(attachment.type)
228
216
  }
229
217
 
230
218
  if (attachment.type === 'photo') {
@@ -257,6 +245,23 @@ export function typedEntries(obj) {
257
245
  return /** @type {import('type-fest').Entries<T>} */ (Object.entries(obj))
258
246
  }
259
247
 
248
+ /**
249
+ * @template T
250
+ * @param {Promise<T>} promise
251
+ * @param {Parameters<typeof pTimeout<T>>[1]} opts
252
+ * @returns {Promise<T>}
253
+ */
254
+ export async function timeoutPromise(promise, opts) {
255
+ try {
256
+ return await pTimeout(promise, opts)
257
+ } catch (e) {
258
+ if (e instanceof pTimeoutError) {
259
+ throw new TimeoutError()
260
+ }
261
+ throw ensureKnownError(e)
262
+ }
263
+ }
264
+
260
265
  /**
261
266
  * Validate map share extension messages to check that all their parameters make sense
262
267
  * Does not validate device ID or device name
@@ -280,50 +285,60 @@ export function validateMapShareExtension(mapShare) {
280
285
  } = mapShare
281
286
 
282
287
  if (!receiverDeviceKey.length) {
283
- throw new Error('Receiver Device ID must not be empty')
288
+ throw new InvalidMapShareError('Receiver Device ID must not be empty')
289
+ }
290
+ if (!mapId.length) throw new InvalidMapShareError('Map ID must not be empty')
291
+ if (!shareId.length) {
292
+ throw new InvalidMapShareError('Share ID must not be empty')
293
+ }
294
+ if (!mapName.length) {
295
+ throw new InvalidMapShareError('Map Name must not be empty')
296
+ }
297
+ if (!mapShareUrls.length) {
298
+ throw new InvalidMapShareError('Map share URLs must not be empty')
284
299
  }
285
- if (!mapId.length) throw new Error('Map ID must not be empty')
286
- if (!shareId.length) throw new Error('Share ID must not be empty')
287
- if (!mapName.length) throw new Error('Map Name must not be empty')
288
- if (!mapShareUrls.length) throw new Error('Map share URLs must not be empty')
289
300
  if (!mapShareUrls.every((url) => URL.canParse(url))) {
290
- throw new Error('Map share URLs must be valid URLs')
301
+ throw new InvalidMapShareError('Map share URLs must be valid URLs')
302
+ }
303
+ if (!mapCreatedAt) throw new InvalidMapShareError('mapCreatedAt must be set')
304
+ if (!mapShareCreatedAt) {
305
+ throw new InvalidMapShareError('mapShareCreatedAt must be set')
291
306
  }
292
- if (!mapCreatedAt) throw new Error('mapCreatedAt must be set')
293
- if (!mapShareCreatedAt) throw new Error('mapShareCreatedAt must be set')
294
307
  if (bounds.length !== 4) {
295
- throw new Error('Bounds must be bounding box with 4 values')
308
+ throw new InvalidMapShareError('Bounds must be bounding box with 4 values')
296
309
  }
297
310
  if (bounds[0] < MAX_BOUNDS[0]) {
298
- throw new Error(
311
+ throw new InvalidMapShareError(
299
312
  `Bounds at ${0} must be within max of spherical mercator projection ${MAX_BOUNDS}`
300
313
  )
301
314
  }
302
315
  if (bounds[1] < MAX_BOUNDS[1]) {
303
- throw new Error(
316
+ throw new InvalidMapShareError(
304
317
  `Bounds at ${1} must be within max of spherical mercator projection ${MAX_BOUNDS}`
305
318
  )
306
319
  }
307
320
  if (bounds[2] > MAX_BOUNDS[2]) {
308
- throw new Error(
321
+ throw new InvalidMapShareError(
309
322
  `Bounds at ${2} must be within max of spherical mercator projection ${MAX_BOUNDS}`
310
323
  )
311
324
  }
312
325
  if (bounds[3] > MAX_BOUNDS[3]) {
313
- throw new Error(
326
+ throw new InvalidMapShareError(
314
327
  `Bounds at ${3} must be within max of spherical mercator projection ${MAX_BOUNDS}`
315
328
  )
316
329
  }
317
330
  if (maxzoom < minzoom) {
318
- throw new Error('Max zoom must be greater than or equal to min zoom')
331
+ throw new InvalidMapShareError(
332
+ 'Max zoom must be greater than or equal to min zoom'
333
+ )
319
334
  }
320
335
  if (maxzoom < 0 || maxzoom > 22) {
321
- throw new Error('Max zoom must be between 0 and 22')
336
+ throw new InvalidMapShareError('Max zoom must be between 0 and 22')
322
337
  }
323
338
  if (minzoom < 0 || minzoom > 22) {
324
- throw new Error('Min zoom must be between 0 and 22')
339
+ throw new InvalidMapShareError('Min zoom must be between 0 and 22')
325
340
  }
326
341
  if (estimatedSizeBytes <= 0) {
327
- throw new Error('Map size bytes must greater than zero')
342
+ throw new InvalidMapShareError('Map size bytes must greater than zero')
328
343
  }
329
344
  }
@@ -1,51 +0,0 @@
1
- /**
2
- * If the argument is an `Error` instance, return its `code` property if it is a string.
3
- * Otherwise, returns `undefined`.
4
- *
5
- * @param {unknown} maybeError
6
- * @returns {undefined | string}
7
- * @example
8
- * try {
9
- * // do something
10
- * } catch (err) {
11
- * console.error(getErrorCode(err))
12
- * }
13
- */
14
- export function getErrorCode(maybeError: unknown): undefined | string;
15
- /**
16
- * Get the error message from an object if possible.
17
- * Otherwise, stringify the argument.
18
- *
19
- * @param {unknown} maybeError
20
- * @returns {string}
21
- * @example
22
- * try {
23
- * // do something
24
- * } catch (err) {
25
- * console.error(getErrorMessage(err))
26
- * }
27
- */
28
- export function getErrorMessage(maybeError: unknown): string;
29
- /**
30
- * Create an `Error` with a `code` property.
31
- *
32
- * @example
33
- * const err = new ErrorWithCode('INVALID_DATA', 'data was invalid')
34
- * err.message
35
- * // => 'data was invalid'
36
- * err.code
37
- * // => 'INVALID_DATA'
38
- */
39
- export class ErrorWithCode extends Error {
40
- /**
41
- * @param {string} code
42
- * @param {string} message
43
- * @param {object} [options]
44
- * @param {unknown} [options.cause]
45
- */
46
- constructor(code: string, message: string, options?: {
47
- cause?: unknown;
48
- } | undefined);
49
- /** @readonly */ readonly code: string;
50
- }
51
- //# sourceMappingURL=error.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/lib/error.js"],"names":[],"mappings":"AAuBA;;;;;;;;;;;;GAYG;AACH,yCATW,OAAO,GACL,SAAS,GAAG,MAAM,CAiB9B;AAED;;;;;;;;;;;;GAYG;AACH,4CATW,OAAO,GACL,MAAM,CAkBlB;AAtED;;;;;;;;;GASG;AACH;IACE;;;;;OAKG;IACH,kBALW,MAAM,WACN,MAAM;gBAEN,OAAO;mBAKjB;IADC,gBAAgB,CAAC,sBAAgB;CAEpC"}
package/src/lib/error.js DELETED
@@ -1,71 +0,0 @@
1
- /**
2
- * Create an `Error` with a `code` property.
3
- *
4
- * @example
5
- * const err = new ErrorWithCode('INVALID_DATA', 'data was invalid')
6
- * err.message
7
- * // => 'data was invalid'
8
- * err.code
9
- * // => 'INVALID_DATA'
10
- */
11
- export class ErrorWithCode extends Error {
12
- /**
13
- * @param {string} code
14
- * @param {string} message
15
- * @param {object} [options]
16
- * @param {unknown} [options.cause]
17
- */
18
- constructor(code, message, options) {
19
- super(message, options)
20
- /** @readonly */ this.code = code
21
- }
22
- }
23
-
24
- /**
25
- * If the argument is an `Error` instance, return its `code` property if it is a string.
26
- * Otherwise, returns `undefined`.
27
- *
28
- * @param {unknown} maybeError
29
- * @returns {undefined | string}
30
- * @example
31
- * try {
32
- * // do something
33
- * } catch (err) {
34
- * console.error(getErrorCode(err))
35
- * }
36
- */
37
- export function getErrorCode(maybeError) {
38
- if (
39
- maybeError instanceof Error &&
40
- 'code' in maybeError &&
41
- typeof maybeError.code === 'string'
42
- ) {
43
- return maybeError.code
44
- }
45
- return undefined
46
- }
47
-
48
- /**
49
- * Get the error message from an object if possible.
50
- * Otherwise, stringify the argument.
51
- *
52
- * @param {unknown} maybeError
53
- * @returns {string}
54
- * @example
55
- * try {
56
- * // do something
57
- * } catch (err) {
58
- * console.error(getErrorMessage(err))
59
- * }
60
- */
61
- export function getErrorMessage(maybeError) {
62
- if (maybeError && typeof maybeError === 'object' && 'message' in maybeError) {
63
- try {
64
- const { message } = maybeError
65
- if (typeof message === 'string') return message
66
- } catch (_err) {
67
- // Ignored
68
- }
69
- }
70
- return 'unknown error'
71
- }