@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
@@ -5,7 +5,13 @@ import { Type as T } from '@sinclair/typebox'
5
5
 
6
6
  import { SUPPORTED_BLOB_VARIANTS } from '../blob-store/index.js'
7
7
  import { HEX_REGEX_32_BYTES, Z_BASE_32_REGEX_32_BYTES } from './constants.js'
8
- import { getErrorMessage } from '../lib/error.js'
8
+ import { ensureKnownError } from '../errors.js'
9
+ import {
10
+ BlobNotFoundError,
11
+ BlobStoreEntryNotFoundError,
12
+ UnsupportedVariantError,
13
+ } from '../errors.js'
14
+ import ensureError from 'ensure-error'
9
15
 
10
16
  /** @import { BlobId } from '../types.js' */
11
17
 
@@ -45,7 +51,7 @@ const PARAMS_JSON_SCHEMA = T.Object({
45
51
 
46
52
  /** @type {import('fastify').FastifyPluginAsync<import('fastify').RegisterOptions & BlobServerPluginOpts>} */
47
53
  async function blobServerPlugin(fastify, options) {
48
- if (!options.getBlobStore) throw new Error('Missing getBlobStore')
54
+ if (!options.getBlobStore) throw new TypeError('Missing getBlobStore')
49
55
 
50
56
  // We call register here so that the `prefix` option can work if desired
51
57
  // https://fastify.dev/docs/latest/Reference/Routes#route-prefixing-and-fastify-plugin
@@ -64,9 +70,10 @@ async function routes(fastify, options) {
64
70
 
65
71
  if (!isValidBlobId(blobId)) {
66
72
  reply.code(400)
67
- throw new Error(
68
- `Unsupported variant "${blobId.variant}" for ${blobId.type}`
69
- )
73
+ throw new UnsupportedVariantError({
74
+ variant: blobId.variant,
75
+ type: blobId.type,
76
+ })
70
77
  }
71
78
  const { driveId } = blobId
72
79
 
@@ -75,7 +82,7 @@ async function routes(fastify, options) {
75
82
  blobStore = await getBlobStore(projectPublicId)
76
83
  } catch (e) {
77
84
  reply.code(404)
78
- throw e
85
+ throw ensureKnownError(e)
79
86
  }
80
87
 
81
88
  let entry
@@ -83,12 +90,12 @@ async function routes(fastify, options) {
83
90
  entry = await blobStore.entry(blobId, { wait: false })
84
91
  } catch (e) {
85
92
  reply.code(404)
86
- throw e
93
+ throw ensureKnownError(e)
87
94
  }
88
95
 
89
96
  if (!entry) {
90
97
  reply.code(404)
91
- throw new Error('Entry not found')
98
+ throw new BlobStoreEntryNotFoundError()
92
99
  }
93
100
 
94
101
  const { metadata } = entry.value
@@ -98,19 +105,19 @@ async function routes(fastify, options) {
98
105
  blobStream = await blobStore.createReadStreamFromEntry(driveId, entry)
99
106
  } catch (e) {
100
107
  reply.code(404)
101
- throw e
108
+ throw ensureKnownError(e)
102
109
  }
103
110
 
104
111
  try {
105
112
  await pEvent(blobStream, 'readable', { rejectionEvents: ['error'] })
106
- } catch (err) {
113
+ } catch (e) {
107
114
  // This matches [how Hyperblobs checks if a blob is unavailable][0].
108
115
  // [0]: https://github.com/holepunchto/hyperblobs/blob/518088d2b828082fd70a276fa2c8848a2cf2a56b/index.js#L49
109
- if (getErrorMessage(err) === 'Block not available') {
116
+ if (ensureError(e).message === 'Block not available') {
110
117
  reply.code(404)
111
- throw new Error('Blob not found')
118
+ throw new BlobNotFoundError()
112
119
  } else {
113
- throw err
120
+ throw ensureKnownError(e)
114
121
  }
115
122
  }
116
123
 
@@ -130,7 +137,7 @@ async function routes(fastify, options) {
130
137
 
131
138
  if (!blobSlice) {
132
139
  reply.code(404)
133
- throw new Error('Blob not found')
140
+ throw new BlobNotFoundError()
134
141
  }
135
142
 
136
143
  const [guessedMime] = filetypemime(blobSlice)
@@ -4,7 +4,12 @@ import { docSchemas } from '@comapeo/schema'
4
4
 
5
5
  import { kGetIconBlob } from '../icon-api.js'
6
6
  import { HEX_REGEX_32_BYTES, Z_BASE_32_REGEX_32_BYTES } from './constants.js'
7
- import { ExhaustivenessError } from '../utils.js'
7
+ import {
8
+ ensureKnownError,
9
+ InvalidIconPixelDensityError,
10
+ InvalidIconSizeError,
11
+ ExhaustivenessError,
12
+ } from '../errors.js'
8
13
 
9
14
  export default fp(iconServerPlugin, {
10
15
  fastify: '4.x',
@@ -42,7 +47,7 @@ const PARAMS_JSON_SCHEMA = T.Object({
42
47
  case 'image/svg+xml':
43
48
  return T.Literal('svg')
44
49
  default:
45
- throw new ExhaustivenessError(mimeType)
50
+ throw new ExhaustivenessError({ value: mimeType })
46
51
  }
47
52
  })
48
53
  ),
@@ -56,7 +61,7 @@ const PARAMS_JSON_SCHEMA = T.Object({
56
61
 
57
62
  /** @type {import('fastify').FastifyPluginAsync<import('fastify').RegisterOptions & IconServerPluginOpts>} */
58
63
  async function iconServerPlugin(fastify, options) {
59
- if (!options.getProject) throw new Error('Missing getProject')
64
+ if (!options.getProject) throw new TypeError('Missing getProject')
60
65
  fastify.register(routes, options)
61
66
  }
62
67
 
@@ -95,9 +100,9 @@ async function routes(fastify, options) {
95
100
 
96
101
  res.header('Content-Type', mimeType)
97
102
  return res.send(icon)
98
- } catch (err) {
103
+ } catch (e) {
99
104
  res.code(404)
100
- throw err
105
+ throw ensureKnownError(e)
101
106
  }
102
107
  }
103
108
  )
@@ -143,16 +148,20 @@ function assertValidSize(value) {
143
148
  value
144
149
  )
145
150
  ) {
146
- throw new Error(`'${value}' is not a valid icon size`)
151
+ throw new InvalidIconSizeError({ value })
147
152
  }
148
153
  }
149
154
 
150
155
  /**
151
- * @param {unknown} value
156
+ * @param {number} value
152
157
  * @returns {asserts value is import('../icon-api.js').BitmapOpts['pixelDensity']}
153
158
  */
154
159
  function assertValidPixelDensity(value) {
155
- if (!VALID_PIXEL_DENSITIES.includes(/** @type {any} */ (value))) {
156
- throw new Error(`${value} is not a valid icon pixel density`)
160
+ if (
161
+ !VALID_PIXEL_DENSITIES.includes(
162
+ /** @type {import('../icon-api.js').BitmapOpts['pixelDensity']} */ (value)
163
+ )
164
+ ) {
165
+ throw new InvalidIconPixelDensityError({ density: value })
157
166
  }
158
167
  }
@@ -5,7 +5,8 @@ import { ReaderWatch, createServer } from 'styled-map-package'
5
5
 
6
6
  import { noop } from '../utils.js'
7
7
  import { NotFoundError, ENOENTError } from './utils.js'
8
- import { getErrorCode } from '../lib/error.js'
8
+ import { ensureKnownError, getErrorCode } from '../errors.js'
9
+ import { FailedToGetStyleError } from '../errors.js'
9
10
 
10
11
  /** @import { FastifyPluginAsync } from 'fastify' */
11
12
  /** @import { Stats } from 'node:fs' */
@@ -48,7 +49,7 @@ export async function plugin(fastify, opts) {
48
49
  }
49
50
 
50
51
  if (!response.ok) {
51
- throw new Error(`Failed to get style from ${customStyleJsonUrl.href}`)
52
+ throw new FailedToGetStyleError(customStyleJsonUrl)
52
53
  }
53
54
 
54
55
  /** @type {Stats | undefined} */
@@ -56,12 +57,12 @@ export async function plugin(fastify, opts) {
56
57
 
57
58
  try {
58
59
  stats = await fs.stat(customMapPath)
59
- } catch (err) {
60
- if (getErrorCode(err) === 'ENOENT') {
60
+ } catch (e) {
61
+ if (getErrorCode(e) === 'ENOENT') {
61
62
  throw new ENOENTError(customMapPath)
62
63
  }
63
64
 
64
- throw err
65
+ throw ensureKnownError(e)
65
66
  }
66
67
 
67
68
  const style = await response.json()
@@ -10,6 +10,7 @@ export interface Invite {
10
10
  projectColor?: string | undefined;
11
11
  projectDescription?: string | undefined;
12
12
  sendStats: boolean;
13
+ invitorWroteDeviceInfo: boolean;
13
14
  }
14
15
  export interface InviteCancel {
15
16
  inviteId: Buffer;
@@ -122,6 +122,7 @@ function createBaseInvite() {
122
122
  projectName: "",
123
123
  invitorName: "",
124
124
  sendStats: false,
125
+ invitorWroteDeviceInfo: false,
125
126
  };
126
127
  }
127
128
  export var Invite = {
@@ -154,6 +155,9 @@ export var Invite = {
154
155
  if (message.sendStats === true) {
155
156
  writer.uint32(72).bool(message.sendStats);
156
157
  }
158
+ if (message.invitorWroteDeviceInfo === true) {
159
+ writer.uint32(80).bool(message.invitorWroteDeviceInfo);
160
+ }
157
161
  return writer;
158
162
  },
159
163
  decode: function (input, length) {
@@ -217,6 +221,12 @@ export var Invite = {
217
221
  }
218
222
  message.sendStats = reader.bool();
219
223
  continue;
224
+ case 10:
225
+ if (tag !== 80) {
226
+ break;
227
+ }
228
+ message.invitorWroteDeviceInfo = reader.bool();
229
+ continue;
220
230
  }
221
231
  if ((tag & 7) === 4 || tag === 0) {
222
232
  break;
@@ -229,7 +239,7 @@ export var Invite = {
229
239
  return Invite.fromPartial(base !== null && base !== void 0 ? base : {});
230
240
  },
231
241
  fromPartial: function (object) {
232
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
242
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
233
243
  var message = createBaseInvite();
234
244
  message.inviteId = (_a = object.inviteId) !== null && _a !== void 0 ? _a : Buffer.alloc(0);
235
245
  message.projectInviteId = (_b = object.projectInviteId) !== null && _b !== void 0 ? _b : Buffer.alloc(0);
@@ -240,6 +250,7 @@ export var Invite = {
240
250
  message.projectColor = (_g = object.projectColor) !== null && _g !== void 0 ? _g : undefined;
241
251
  message.projectDescription = (_h = object.projectDescription) !== null && _h !== void 0 ? _h : undefined;
242
252
  message.sendStats = (_j = object.sendStats) !== null && _j !== void 0 ? _j : false;
253
+ message.invitorWroteDeviceInfo = (_k = object.invitorWroteDeviceInfo) !== null && _k !== void 0 ? _k : false;
243
254
  return message;
244
255
  },
245
256
  };
@@ -12,6 +12,7 @@ export interface Invite {
12
12
  projectColor?: string | undefined;
13
13
  projectDescription?: string | undefined;
14
14
  sendStats: boolean;
15
+ invitorWroteDeviceInfo: boolean;
15
16
  }
16
17
 
17
18
  export interface InviteCancel {
@@ -187,6 +188,7 @@ function createBaseInvite(): Invite {
187
188
  projectName: "",
188
189
  invitorName: "",
189
190
  sendStats: false,
191
+ invitorWroteDeviceInfo: false,
190
192
  };
191
193
  }
192
194
 
@@ -219,6 +221,9 @@ export const Invite = {
219
221
  if (message.sendStats === true) {
220
222
  writer.uint32(72).bool(message.sendStats);
221
223
  }
224
+ if (message.invitorWroteDeviceInfo === true) {
225
+ writer.uint32(80).bool(message.invitorWroteDeviceInfo);
226
+ }
222
227
  return writer;
223
228
  },
224
229
 
@@ -292,6 +297,13 @@ export const Invite = {
292
297
 
293
298
  message.sendStats = reader.bool();
294
299
  continue;
300
+ case 10:
301
+ if (tag !== 80) {
302
+ break;
303
+ }
304
+
305
+ message.invitorWroteDeviceInfo = reader.bool();
306
+ continue;
295
307
  }
296
308
  if ((tag & 7) === 4 || tag === 0) {
297
309
  break;
@@ -315,6 +327,7 @@ export const Invite = {
315
327
  message.projectColor = object.projectColor ?? undefined;
316
328
  message.projectDescription = object.projectDescription ?? undefined;
317
329
  message.sendStats = object.sendStats ?? false;
330
+ message.invitorWroteDeviceInfo = object.invitorWroteDeviceInfo ?? false;
318
331
  return message;
319
332
  },
320
333
  };
package/src/icon-api.js CHANGED
@@ -1,5 +1,13 @@
1
1
  /** @import { PresetValue, IconValue } from '@comapeo/schema' */
2
2
 
3
+ import {
4
+ EmptyIconPathError,
5
+ EmptyVariantsArrayError,
6
+ InvalidPixelDensityError,
7
+ NoVariantsExistError,
8
+ NoVariantsForMimeTypeError,
9
+ } from './errors.js'
10
+
3
11
  export const kGetIconBlob = Symbol('getIcon')
4
12
 
5
13
  /** @typedef {PresetValue['iconRef']} IconRef */
@@ -59,7 +67,7 @@ export class IconApi {
59
67
  */
60
68
  async create(icon) {
61
69
  if (icon.variants.length < 1) {
62
- throw new Error('empty variants array')
70
+ throw new EmptyVariantsArrayError()
63
71
  }
64
72
 
65
73
  const savedVariants = await Promise.all(
@@ -158,15 +166,13 @@ export function getBestVariant(variants, opts) {
158
166
  wantedPixelDensity = opts.pixelDensity
159
167
  }
160
168
  if (variants.length === 0) {
161
- throw new Error('No variants exist')
169
+ throw new NoVariantsExistError()
162
170
  }
163
171
 
164
172
  const matchingMime = variants.filter((v) => v.mimeType === wantedMimeType)
165
173
 
166
174
  if (matchingMime.length === 0) {
167
- throw new Error(
168
- `No variants with desired mime type ${wantedMimeType} exist`
169
- )
175
+ throw new NoVariantsForMimeTypeError({ wantedMimeType })
170
176
  }
171
177
  const wantedSizeNum = SIZE_AS_NUMERIC[wantedSize]
172
178
 
@@ -264,14 +270,16 @@ function determineSortValue(target, a, b) {
264
270
  */
265
271
  export function constructIconPath({ size, pixelDensity, iconId, extension }) {
266
272
  if (iconId.length === 0 || size.length === 0 || extension.length === 0) {
267
- throw new Error('iconId, size, and extension cannot be empty strings')
273
+ throw new EmptyIconPathError()
268
274
  }
269
275
 
270
276
  let result = `${iconId}/${size}`
271
277
 
272
278
  if (typeof pixelDensity === 'number') {
273
279
  if (pixelDensity < 1) {
274
- throw new Error('pixelDensity must be a positive number')
280
+ throw new InvalidPixelDensityError({
281
+ pixelDensity,
282
+ })
275
283
  }
276
284
  result += `@${pixelDensity}x`
277
285
  }
@@ -3,6 +3,7 @@ import { Reader } from 'comapeocat/reader.js'
3
3
  import { typedEntries } from './utils.js'
4
4
  import { parseBcp47 } from './intl/parse-bcp-47.js'
5
5
  import ensureError from 'ensure-error'
6
+ import { IconNotFoundError, KeyNotFoundError } from './errors.js'
6
7
 
7
8
  // The main reason for the concurrency limit is to avoid run-away memory usage.
8
9
  // The comapeocat Reader limits icon size to 2Mb (as-per the specification), and
@@ -75,7 +76,7 @@ export async function importCategories(project, { filePath, logger }) {
75
76
  const iconXml = await reader.getIcon(iconName)
76
77
  if (!iconXml) {
77
78
  // This should never happen because of the validate() call above
78
- throw new Error(`Icon ${iconName} not found in import file`)
79
+ throw new IconNotFoundError(iconName)
79
80
  }
80
81
  /** @type {Parameters<typeof project.$icons.create>[0]} */
81
82
  const icon = {
@@ -308,7 +309,7 @@ export async function importCategories(project, { filePath, logger }) {
308
309
  // flow statements (return, throw, break, continue) in the finally block
309
310
  // will "mask" any completion value of the try block or catch block)
310
311
  await reader.close().catch((e) => {
311
- logger.log('error closing import file reader', e)
312
+ logger.log('ERROR: closing import file reader', e)
312
313
  })
313
314
  }
314
315
  }
@@ -349,19 +350,17 @@ function appliesToToGeometry(appliesTo) {
349
350
  /**
350
351
  * Get a value from a Map, or throw an error if the key is not present
351
352
  *
352
- * @template K, V
353
+ * @template {string} K, V
353
354
  * @param {Map<K, V>} map
354
355
  * @param {K} key
355
- * @param {string | Error} [msgOrError]
356
356
  * @returns {V}
357
357
  * @throws {TypeError} if `map` is not a Map
358
358
  * @throws {Error} if `key` is not in `map` (with `msgOrError` as message or the default message)
359
359
  */
360
- function getOrThrow(map, key, msgOrError) {
360
+ function getOrThrow(map, key) {
361
361
  if (!(map instanceof Map)) throw new TypeError('map must be a Map')
362
362
  if (!map.has(key)) {
363
- if (msgOrError instanceof Error) throw msgOrError
364
- throw new Error(msgOrError ?? `key ${key} not found in map`)
363
+ throw new KeyNotFoundError(key)
365
364
  }
366
365
  return /** @type {V} */ (map.get(key))
367
366
  }
@@ -4,6 +4,7 @@ import { getTableConfig } from 'drizzle-orm/sqlite-core'
4
4
  import { getBacklinkTableName } from '../schema/comapeo-to-drizzle.js'
5
5
  import { discoveryKey } from 'hypercore-crypto'
6
6
  import { Logger } from '../logger.js'
7
+ import { UnknownSchemaError } from '../errors.js'
7
8
  /** @import { MapeoDoc, VersionIdObject } from '@comapeo/schema' */
8
9
  /** @import { MapeoDocTables } from '../datatype/index.js' */
9
10
 
@@ -76,7 +77,7 @@ export class IndexWriter {
76
77
  try {
77
78
  const version = { coreDiscoveryKey: discoveryKey(key), index }
78
79
  doc = this.#mapDoc(decode(block, version), version)
79
- } catch (e) {
80
+ } catch {
80
81
  this.#l.log('Could not decode entry %d of %h', index, key)
81
82
  // Unknown or invalid entry - silently ignore
82
83
  continue
@@ -119,7 +120,7 @@ export class IndexWriter {
119
120
  deleteSchema(schemaName) {
120
121
  const indexer = this.#indexers.get(schemaName)
121
122
  if (!indexer) {
122
- throw new Error(`IndexWriter doesn't know a schema named "${schemaName}"`)
123
+ throw new UnknownSchemaError({ schemaName })
123
124
  }
124
125
  indexer.deleteAll()
125
126
  }
package/src/index.js CHANGED
@@ -34,6 +34,7 @@ export { MapeoManager } from './mapeo-manager.js'
34
34
  /**
35
35
  * @namespace MemberApi
36
36
  * @typedef {import('./member-api.js').MemberInfo} MemberApi.MemberInfo
37
+ * @typedef {import('./member-api.js').ActiveMemberInfo} MemberApi.ActiveMemberInfo
37
38
  * @typedef {import('./roles.js').RoleId} MemberApi.RoleId
38
39
  * @typedef {import('./roles.js').RoleIdForNewInvite} MemberApi.RoleIdForNewInvite
39
40
  * @typedef {import('./roles.js').RoleIdAssignableToOthers} MemberApi.RoleIdAssignableToOthers
@@ -4,6 +4,7 @@ import { iso6391To6393, iso6393 } from './iso639.js'
4
4
  import { iso31661 } from 'iso-3166'
5
5
  import { iso31661Alpha3ToAlpha2 } from 'iso-3166'
6
6
  import { unM49 as unM49Array } from 'un-m49'
7
+ import { InvalidLanguageTagError } from '../errors.js'
7
8
 
8
9
  /**
9
10
  * Map of UN M.49 country codes to their corresponding ISO 3166-1 alpha-2 country codes.
@@ -82,7 +83,7 @@ export function parseBcp47(languageTag) {
82
83
  const normalized = bcp47Normalize(languageTag)
83
84
  const { language, region } = simpleParseBcp47(normalized)
84
85
  if (!language) {
85
- throw new Error(`Invalid BCP 47 language tag: ${languageTag}`)
86
+ throw new InvalidLanguageTagError({ languageTag })
86
87
  }
87
88
  return {
88
89
  language: normalizePrimaryLanguageSubtag(language),
@@ -1,6 +1,6 @@
1
1
  import { TypedEmitter } from 'tiny-typed-emitter'
2
2
  import { InviteResponse_Decision } from '../generated/rpc.js'
3
- import { assert, keyToId, noop } from '../utils.js'
3
+ import { keyToId, noop } from '../utils.js'
4
4
  import HashMap from '../lib/hashmap.js'
5
5
  import timingSafeEqual from 'string-timing-safe-equal'
6
6
  import { Logger } from '../logger.js'
@@ -10,6 +10,8 @@ import {
10
10
  NotFoundError,
11
11
  AlreadyJoinedError,
12
12
  InviteSendError,
13
+ ensureKnownError,
14
+ InviteNotFoundError,
13
15
  } from '../errors.js'
14
16
 
15
17
  /** @import { ProjectToAddDetails } from '../mapeo-manager.js' */
@@ -79,24 +81,24 @@ export class InviteApi extends TypedEmitter {
79
81
  this.rpc.on('invite', (...args) => {
80
82
  try {
81
83
  this.#handleNewInvite(...args)
82
- } catch (err) {
83
- console.error('Error handling invite', err)
84
+ } catch (e) {
85
+ this.#l.log('ERROR: could not handle invite', e)
84
86
  }
85
87
  })
86
88
 
87
89
  this.rpc.on('invite-cancel', (_peerId, inviteCancel) => {
88
90
  try {
89
91
  this.#handleInviteCancel(inviteCancel)
90
- } catch (err) {
91
- console.error('Error handling invite cancel', err)
92
+ } catch (e) {
93
+ this.#l.log('ERROR: could not handle invite cancel', e)
92
94
  }
93
95
  })
94
96
 
95
97
  this.rpc.on('got-project-details', (peerId, projectJoinDetails) => {
96
98
  try {
97
99
  this.#handleGotProjectDetails(peerId, projectJoinDetails)
98
- } catch (err) {
99
- console.error('Error handling got-project-details', err)
100
+ } catch (e) {
101
+ this.#l.log('ERROR: could not handle got-project-details', e)
100
102
  }
101
103
  })
102
104
  }
@@ -127,6 +129,7 @@ export class InviteApi extends TypedEmitter {
127
129
  projectColor,
128
130
  projectDescription,
129
131
  sendStats,
132
+ invitorWroteDeviceInfo,
130
133
  } = inviteRpcMessage
131
134
  const invite = { ...inviteRpcMessage, receivedAt: Date.now() }
132
135
 
@@ -167,6 +170,7 @@ export class InviteApi extends TypedEmitter {
167
170
  projectColor,
168
171
  projectDescription,
169
172
  sendStats,
173
+ invitorWroteDeviceInfo,
170
174
  })
171
175
  }),
172
176
  },
@@ -202,7 +206,9 @@ export class InviteApi extends TypedEmitter {
202
206
  this.#l.log('Received invite cancel for invite ID %h', inviteId)
203
207
 
204
208
  const invite = this.#invites.get(inviteId)
205
- assert(!!invite, `Cannot find invite ${inviteId.toString('hex')}`)
209
+ if (!invite) {
210
+ throw new InviteNotFoundError({ inviteId: inviteId.toString('hex') })
211
+ }
206
212
 
207
213
  // TODO: Move this logging to the state machine
208
214
  const state = invite.actor.getSnapshot()
@@ -292,10 +298,9 @@ export class InviteApi extends TypedEmitter {
292
298
  const inviteId = Buffer.from(inviteIdString, 'hex')
293
299
 
294
300
  const invite = this.#invites.get(inviteId)
295
- assert(
296
- !!invite,
297
- new NotFoundError(`Cannot find invite ${inviteIdString}`)
298
- )
301
+ if (!invite) {
302
+ throw new InviteNotFoundError({ inviteId: inviteId.toString('hex') })
303
+ }
299
304
  assertCanSend(invite.actor, { type: 'ACCEPT_INVITE' })
300
305
 
301
306
  this.#l.log('Accepting invite %h', inviteId)
@@ -321,9 +326,9 @@ export class InviteApi extends TypedEmitter {
321
326
  }
322
327
 
323
328
  return projectPublicId
324
- } catch (err) {
325
- this.#l.log('ERROR: Unable to accept invite', err)
326
- throw err
329
+ } catch (e) {
330
+ this.#l.log('ERROR: Unable to accept invite', e)
331
+ throw ensureKnownError(e)
327
332
  }
328
333
  }
329
334
 
@@ -335,7 +340,9 @@ export class InviteApi extends TypedEmitter {
335
340
  const inviteId = Buffer.from(inviteIdString, 'hex')
336
341
 
337
342
  const invite = this.#invites.get(inviteId)
338
- assert(!!invite, `Cannot find invite ${inviteIdString}`)
343
+ if (!invite) {
344
+ throw new InviteNotFoundError({ inviteId: inviteId.toString('hex') })
345
+ }
339
346
  assertCanSend(invite.actor, { type: 'REJECT_INVITE' })
340
347
 
341
348
  this.#l.log('Rejecting invite %h', inviteId)
@@ -381,14 +388,13 @@ function toInvite(internal, snapshot, invitorDeviceId) {
381
388
  */
382
389
  function assertCanSend(actor, eventType) {
383
390
  const state = actor.getSnapshot()
384
- assert(
385
- state.can(eventType),
386
- new InviteSendError(
391
+ if (!state.can(eventType)) {
392
+ throw new InviteSendError(
387
393
  `Cannot send ${JSON.stringify(eventType)} in state ${toStateString(
388
394
  state.value
389
395
  )}`
390
396
  )
391
- )
397
+ }
392
398
  }
393
399
 
394
400
  /**