@comapeo/core 5.4.1 → 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.
- package/dist/blob-api.d.ts.map +1 -1
- package/dist/blob-store/downloader.d.ts.map +1 -1
- package/dist/blob-store/hyperdrive-index.d.ts.map +1 -1
- package/dist/blob-store/index.d.ts.map +1 -1
- package/dist/core-manager/bitfield-rle.d.ts.map +1 -1
- package/dist/core-manager/core-index.d.ts.map +1 -1
- package/dist/core-manager/index.d.ts +1 -2
- package/dist/core-manager/index.d.ts.map +1 -1
- package/dist/core-ownership.d.ts.map +1 -1
- package/dist/datastore/index.d.ts.map +1 -1
- package/dist/datatype/index.d.ts +7 -0
- package/dist/datatype/index.d.ts.map +1 -1
- package/dist/discovery/local-discovery.d.ts.map +1 -1
- package/dist/errors.d.ts +437 -35
- package/dist/errors.d.ts.map +1 -1
- package/dist/fastify-plugins/blobs.d.ts.map +1 -1
- package/dist/fastify-plugins/icons.d.ts.map +1 -1
- package/dist/fastify-plugins/maps.d.ts.map +1 -1
- package/dist/generated/extensions.d.ts +1 -1
- package/dist/generated/extensions.d.ts.map +1 -1
- package/dist/generated/rpc.d.ts +1 -0
- package/dist/generated/rpc.d.ts.map +1 -1
- package/dist/icon-api.d.ts +0 -1
- package/dist/icon-api.d.ts.map +1 -1
- package/dist/import-categories.d.ts.map +1 -1
- package/dist/index-writer/index.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/intl/parse-bcp-47.d.ts.map +1 -1
- package/dist/invite/invite-api.d.ts.map +1 -1
- package/dist/lib/drizzle-helpers.d.ts.map +1 -1
- package/dist/lib/hypercore-helpers.d.ts.map +1 -1
- package/dist/lib/key-by.d.ts.map +1 -1
- package/dist/local-peers.d.ts +0 -14
- package/dist/local-peers.d.ts.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/mapeo-manager.d.ts +2 -1
- package/dist/mapeo-manager.d.ts.map +1 -1
- package/dist/mapeo-project.d.ts +15 -8
- package/dist/mapeo-project.d.ts.map +1 -1
- package/dist/member-api.d.ts +42 -7
- package/dist/member-api.d.ts.map +1 -1
- package/dist/roles.d.ts.map +1 -1
- package/dist/schema/json-schema-to-drizzle.d.ts.map +1 -1
- package/dist/schema.d.ts +2 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/sync/core-sync-state.d.ts.map +1 -1
- package/dist/sync/peer-sync-controller.d.ts.map +1 -1
- package/dist/sync/sync-api.d.ts.map +1 -1
- package/dist/utils.d.ts +8 -10
- package/dist/utils.d.ts.map +1 -1
- package/package.json +18 -2
- package/src/blob-api.js +24 -4
- package/src/blob-store/downloader.js +7 -6
- package/src/blob-store/entries-stream.js +1 -1
- package/src/blob-store/hyperdrive-index.js +3 -5
- package/src/blob-store/index.js +15 -20
- package/src/core-manager/bitfield-rle.js +2 -1
- package/src/core-manager/core-index.js +2 -1
- package/src/core-manager/index.js +12 -13
- package/src/core-ownership.js +7 -3
- package/src/datastore/index.js +13 -9
- package/src/datatype/index.js +28 -5
- package/src/discovery/local-discovery.js +8 -7
- package/src/errors.js +530 -62
- package/src/fastify-controller.js +3 -3
- package/src/fastify-plugins/blobs.js +21 -14
- package/src/fastify-plugins/icons.js +18 -9
- package/src/fastify-plugins/maps.js +6 -5
- package/src/generated/extensions.d.ts +1 -1
- package/src/generated/extensions.js +5 -5
- package/src/generated/extensions.ts +6 -6
- package/src/generated/rpc.d.ts +1 -0
- package/src/generated/rpc.js +12 -1
- package/src/generated/rpc.ts +13 -0
- package/src/icon-api.js +15 -7
- package/src/import-categories.js +6 -7
- package/src/index-writer/index.js +3 -2
- package/src/index.js +1 -0
- package/src/intl/parse-bcp-47.js +2 -1
- package/src/invite/invite-api.js +26 -20
- package/src/lib/drizzle-helpers.js +54 -39
- package/src/lib/hypercore-helpers.js +4 -2
- package/src/lib/key-by.js +3 -1
- package/src/local-peers.js +39 -46
- package/src/logger.js +2 -1
- package/src/mapeo-manager.js +36 -23
- package/src/mapeo-project.js +96 -67
- package/src/member-api.js +177 -96
- package/src/roles.js +11 -10
- package/src/schema/json-schema-to-drizzle.js +13 -4
- package/src/schema.js +1 -0
- package/src/sync/core-sync-state.js +2 -1
- package/src/sync/peer-sync-controller.js +4 -3
- package/src/sync/sync-api.js +9 -9
- package/src/translation-api.js +2 -2
- package/src/utils.js +58 -43
- package/dist/lib/error.d.ts +0 -51
- package/dist/lib/error.d.ts.map +0 -1
- package/src/lib/error.js +0 -71
package/src/sync/sync-api.js
CHANGED
|
@@ -8,10 +8,11 @@ import {
|
|
|
8
8
|
NAMESPACES,
|
|
9
9
|
PRESYNC_NAMESPACES,
|
|
10
10
|
} from '../constants.js'
|
|
11
|
-
import {
|
|
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
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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)) {
|
package/src/translation-api.js
CHANGED
|
@@ -39,8 +39,8 @@ export default class TranslationApi {
|
|
|
39
39
|
.then((docs) => {
|
|
40
40
|
docs.map((doc) => this.index(doc))
|
|
41
41
|
})
|
|
42
|
-
.catch((
|
|
43
|
-
console.error(`error loading Translation cache: ${
|
|
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
|
-
|
|
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
|
|
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
|
|
@@ -267,7 +272,7 @@ export function typedEntries(obj) {
|
|
|
267
272
|
export function validateMapShareExtension(mapShare) {
|
|
268
273
|
const {
|
|
269
274
|
mapShareUrls,
|
|
270
|
-
|
|
275
|
+
receiverDeviceKey,
|
|
271
276
|
mapId,
|
|
272
277
|
mapName,
|
|
273
278
|
shareId,
|
|
@@ -279,51 +284,61 @@ export function validateMapShareExtension(mapShare) {
|
|
|
279
284
|
mapShareCreatedAt,
|
|
280
285
|
} = mapShare
|
|
281
286
|
|
|
282
|
-
if (!
|
|
283
|
-
throw new
|
|
287
|
+
if (!receiverDeviceKey.length) {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
336
|
+
throw new InvalidMapShareError('Max zoom must be between 0 and 22')
|
|
322
337
|
}
|
|
323
338
|
if (minzoom < 0 || minzoom > 22) {
|
|
324
|
-
throw new
|
|
339
|
+
throw new InvalidMapShareError('Min zoom must be between 0 and 22')
|
|
325
340
|
}
|
|
326
341
|
if (estimatedSizeBytes <= 0) {
|
|
327
|
-
throw new
|
|
342
|
+
throw new InvalidMapShareError('Map size bytes must greater than zero')
|
|
328
343
|
}
|
|
329
344
|
}
|
package/dist/lib/error.d.ts
DELETED
|
@@ -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
|
package/dist/lib/error.d.ts.map
DELETED
|
@@ -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
|
-
}
|