@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.
- 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.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/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 +1 -3
- 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 +9 -10
- 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/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 +68 -61
- 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 +56 -41
- 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/mapeo-project.js
CHANGED
|
@@ -3,7 +3,6 @@ import Database from 'better-sqlite3'
|
|
|
3
3
|
import { decodeBlockPrefix, decode, parseVersionId } from '@comapeo/schema'
|
|
4
4
|
import { drizzle } from 'drizzle-orm/better-sqlite3'
|
|
5
5
|
import { sql, count, eq } from 'drizzle-orm'
|
|
6
|
-
import { discoveryKey } from 'hypercore-crypto'
|
|
7
6
|
import { TypedEmitter } from 'tiny-typed-emitter'
|
|
8
7
|
import ZipArchive from 'zip-stream-promise'
|
|
9
8
|
import * as b4a from 'b4a'
|
|
@@ -14,7 +13,11 @@ import { Readable, pipelinePromise } from 'streamx'
|
|
|
14
13
|
import { NAMESPACES, NAMESPACE_SCHEMAS } from './constants.js'
|
|
15
14
|
import { CoreManager } from './core-manager/index.js'
|
|
16
15
|
import { DataStore } from './datastore/index.js'
|
|
17
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
DataType,
|
|
18
|
+
kCreateOrUpdateWithDocId,
|
|
19
|
+
kCreateWithDocId,
|
|
20
|
+
} from './datatype/index.js'
|
|
18
21
|
import { BlobStore } from './blob-store/index.js'
|
|
19
22
|
import { BlobApi } from './blob-api.js'
|
|
20
23
|
import { IndexWriter } from './index-writer/index.js'
|
|
@@ -43,9 +46,7 @@ import {
|
|
|
43
46
|
INACTIVE_MEMBER_ROLE_IDS,
|
|
44
47
|
} from './roles.js'
|
|
45
48
|
import {
|
|
46
|
-
assert,
|
|
47
49
|
buildBlobId,
|
|
48
|
-
ExhaustivenessError,
|
|
49
50
|
getDeviceId,
|
|
50
51
|
projectKeyToId,
|
|
51
52
|
projectKeyToPublicId,
|
|
@@ -64,9 +65,21 @@ import { Logger } from './logger.js'
|
|
|
64
65
|
import { IconApi } from './icon-api.js'
|
|
65
66
|
import { importCategories } from './import-categories.js'
|
|
66
67
|
import TranslationApi from './translation-api.js'
|
|
67
|
-
import {
|
|
68
|
+
import {
|
|
69
|
+
CategoryFileNotFoundError,
|
|
70
|
+
ensureKnownError,
|
|
71
|
+
getErrorCode,
|
|
72
|
+
NotFoundError,
|
|
73
|
+
ExhaustivenessError,
|
|
74
|
+
nullIfNotFound,
|
|
75
|
+
GeoJSONExportError,
|
|
76
|
+
InvalidMapShareError,
|
|
77
|
+
MultipleCategoryImportsError,
|
|
78
|
+
UnexpectedDocSchemaError,
|
|
79
|
+
} from './errors.js'
|
|
68
80
|
import { WebSocket } from 'ws'
|
|
69
|
-
import
|
|
81
|
+
import fs from 'node:fs'
|
|
82
|
+
|
|
70
83
|
import ensureError from 'ensure-error'
|
|
71
84
|
/** @import { MapShareExtension } from './generated/extensions.js' */
|
|
72
85
|
/** @import { ProjectSettingsValue, Observation, Track } from '@comapeo/schema' */
|
|
@@ -235,7 +248,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
235
248
|
reindex = true
|
|
236
249
|
break
|
|
237
250
|
default:
|
|
238
|
-
throw new ExhaustivenessError(migrationResult)
|
|
251
|
+
throw new ExhaustivenessError({ value: migrationResult })
|
|
239
252
|
}
|
|
240
253
|
|
|
241
254
|
const indexedTables = [
|
|
@@ -292,8 +305,6 @@ export class MapeoProject extends TypedEmitter {
|
|
|
292
305
|
switch (doc.schemaName) {
|
|
293
306
|
case 'coreOwnership':
|
|
294
307
|
return mapAndValidateCoreOwnership(doc, version)
|
|
295
|
-
case 'deviceInfo':
|
|
296
|
-
return mapAndValidateDeviceInfo(doc, version)
|
|
297
308
|
default:
|
|
298
309
|
return doc
|
|
299
310
|
}
|
|
@@ -449,12 +460,12 @@ export class MapeoProject extends TypedEmitter {
|
|
|
449
460
|
logger: this.#l,
|
|
450
461
|
})
|
|
451
462
|
|
|
452
|
-
this.#blobStore.on('error', (
|
|
463
|
+
this.#blobStore.on('error', (e) => {
|
|
453
464
|
// Ignore hypercore inflight request cancellation
|
|
454
|
-
if (ensureError(
|
|
465
|
+
if (ensureError(e).message.includes('REQUEST_CANCELLED')) return
|
|
455
466
|
// TODO: Handle this error in some way - this error will come from an
|
|
456
467
|
// unexpected error with background blob downloads
|
|
457
|
-
console.error('BlobStore error',
|
|
468
|
+
console.error('BlobStore error', e)
|
|
458
469
|
})
|
|
459
470
|
|
|
460
471
|
this.$blobs = new BlobApi({
|
|
@@ -658,7 +669,13 @@ export class MapeoProject extends TypedEmitter {
|
|
|
658
669
|
index: entry.index,
|
|
659
670
|
})
|
|
660
671
|
|
|
661
|
-
|
|
672
|
+
if (doc.schemaName !== 'translation') {
|
|
673
|
+
throw new UnexpectedDocSchemaError({
|
|
674
|
+
gotSchema: doc.schemaName,
|
|
675
|
+
expectedSchema: 'translation',
|
|
676
|
+
})
|
|
677
|
+
}
|
|
678
|
+
|
|
662
679
|
this.#translationApi.index(doc)
|
|
663
680
|
otherEntries.push(entry)
|
|
664
681
|
} else {
|
|
@@ -744,7 +761,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
744
761
|
return extractEditableProjectSettings(
|
|
745
762
|
await this.#dataTypes.projectSettings.getByDocId(this.#projectId)
|
|
746
763
|
)
|
|
747
|
-
} catch
|
|
764
|
+
} catch {
|
|
748
765
|
// if (e instanceof Error && e.name !== 'NotFoundError') throw e
|
|
749
766
|
// If the project has not completed an initial sync, project settings will
|
|
750
767
|
// not be available, so use fallback project info which is set from the
|
|
@@ -762,7 +779,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
762
779
|
// Should error if we haven't synced before
|
|
763
780
|
await this.#dataTypes.projectSettings.getByDocId(this.#projectId)
|
|
764
781
|
return true
|
|
765
|
-
} catch
|
|
782
|
+
} catch {
|
|
766
783
|
return false
|
|
767
784
|
}
|
|
768
785
|
}
|
|
@@ -800,7 +817,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
800
817
|
const sender = await this.$member.getById(senderDeviceId)
|
|
801
818
|
|
|
802
819
|
if (INACTIVE_MEMBER_ROLE_IDS.includes(sender.role.roleId)) {
|
|
803
|
-
throw new
|
|
820
|
+
throw new InvalidMapShareError(
|
|
804
821
|
`Map Share Sender is not an active member of the project (role: ${sender.role.name})`
|
|
805
822
|
)
|
|
806
823
|
}
|
|
@@ -875,15 +892,10 @@ export class MapeoProject extends TypedEmitter {
|
|
|
875
892
|
|
|
876
893
|
/**
|
|
877
894
|
* @param {Pick<import('@comapeo/schema').DeviceInfoValue, 'name' | 'deviceType' | 'selfHostedServerDetails'>} value
|
|
878
|
-
* @returns {Promise<import('@comapeo/schema').DeviceInfo>}
|
|
879
895
|
*/
|
|
880
896
|
async [kSetOwnDeviceInfo](value) {
|
|
881
897
|
const { deviceInfo } = this.#dataTypes
|
|
882
898
|
|
|
883
|
-
const configCoreId = this.#coreManager
|
|
884
|
-
.getWriterCore('config')
|
|
885
|
-
.key.toString('hex')
|
|
886
|
-
|
|
887
899
|
const doc = {
|
|
888
900
|
name: value.name,
|
|
889
901
|
deviceType: value.deviceType,
|
|
@@ -891,13 +903,15 @@ export class MapeoProject extends TypedEmitter {
|
|
|
891
903
|
schemaName: /** @type {const} */ ('deviceInfo'),
|
|
892
904
|
}
|
|
893
905
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
.
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
906
|
+
// TODO: Remove configCore once we know everyone has deviceId
|
|
907
|
+
const configCoreId = this.#coreManager
|
|
908
|
+
.getWriterCore('config')
|
|
909
|
+
.key.toString('hex')
|
|
910
|
+
|
|
911
|
+
const docIds = [this.deviceId, configCoreId]
|
|
912
|
+
|
|
913
|
+
for (const docId of docIds) {
|
|
914
|
+
await deviceInfo[kCreateOrUpdateWithDocId](docId, doc)
|
|
901
915
|
}
|
|
902
916
|
}
|
|
903
917
|
|
|
@@ -1205,10 +1219,16 @@ export class MapeoProject extends TypedEmitter {
|
|
|
1205
1219
|
const fileName = await this[kGeoJSONFileName](observations, tracks)
|
|
1206
1220
|
const filePath = path.join(exportFolder, fileName)
|
|
1207
1221
|
const source = this.#exportGeoJSONStream({ observations, tracks, lang })
|
|
1208
|
-
const sink = createWriteStream(filePath)
|
|
1209
|
-
await pipelinePromise(source, sink)
|
|
1210
1222
|
|
|
1211
|
-
|
|
1223
|
+
const sink = fs.createWriteStream(filePath)
|
|
1224
|
+
|
|
1225
|
+
try {
|
|
1226
|
+
await pipelinePromise(source, sink)
|
|
1227
|
+
|
|
1228
|
+
return filePath
|
|
1229
|
+
} catch (e) {
|
|
1230
|
+
throw new GeoJSONExportError({ cause: e })
|
|
1231
|
+
}
|
|
1212
1232
|
}
|
|
1213
1233
|
|
|
1214
1234
|
/**
|
|
@@ -1242,12 +1262,11 @@ export class MapeoProject extends TypedEmitter {
|
|
|
1242
1262
|
}
|
|
1243
1263
|
return { blobId, mimeType }
|
|
1244
1264
|
} catch (e) {
|
|
1245
|
-
if (!(e instanceof Error)) throw e
|
|
1246
1265
|
this.#l.log(
|
|
1247
1266
|
'Error loading blob id for attachment',
|
|
1248
1267
|
attachment,
|
|
1249
1268
|
variant,
|
|
1250
|
-
e.message
|
|
1269
|
+
ensureError(e).message
|
|
1251
1270
|
)
|
|
1252
1271
|
continue
|
|
1253
1272
|
}
|
|
@@ -1350,7 +1369,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
1350
1369
|
tracks,
|
|
1351
1370
|
attachments,
|
|
1352
1371
|
lang,
|
|
1353
|
-
}).catch((e) => archive.emit('error', e))
|
|
1372
|
+
}).catch((e) => archive.emit('error', ensureError(e)))
|
|
1354
1373
|
|
|
1355
1374
|
// @ts-expect-error
|
|
1356
1375
|
return archive
|
|
@@ -1378,10 +1397,14 @@ export class MapeoProject extends TypedEmitter {
|
|
|
1378
1397
|
attachments,
|
|
1379
1398
|
lang,
|
|
1380
1399
|
})
|
|
1381
|
-
const sink = createWriteStream(filePath)
|
|
1382
|
-
|
|
1400
|
+
const sink = fs.createWriteStream(filePath)
|
|
1401
|
+
try {
|
|
1402
|
+
await pipelinePromise(source, sink)
|
|
1383
1403
|
|
|
1384
|
-
|
|
1404
|
+
return filePath
|
|
1405
|
+
} catch (e) {
|
|
1406
|
+
throw new GeoJSONExportError({ cause: e })
|
|
1407
|
+
}
|
|
1385
1408
|
}
|
|
1386
1409
|
|
|
1387
1410
|
async [kProjectLeave]() {
|
|
@@ -1450,17 +1473,19 @@ export class MapeoProject extends TypedEmitter {
|
|
|
1450
1473
|
* @returns {Promise<void>}
|
|
1451
1474
|
*/
|
|
1452
1475
|
async $importCategories({ filePath }) {
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
)
|
|
1476
|
+
if (this.#importingCategories) {
|
|
1477
|
+
throw new MultipleCategoryImportsError()
|
|
1478
|
+
}
|
|
1457
1479
|
this.#importingCategories = true
|
|
1458
1480
|
|
|
1459
1481
|
try {
|
|
1460
1482
|
await importCategories(this, { filePath, logger: this.#l })
|
|
1461
1483
|
} catch (e) {
|
|
1462
|
-
|
|
1463
|
-
|
|
1484
|
+
if (getErrorCode(e) === 'ENOENT') {
|
|
1485
|
+
throw new CategoryFileNotFoundError({ filePath })
|
|
1486
|
+
}
|
|
1487
|
+
this.#l.log('ERROR: could not load config', e)
|
|
1488
|
+
throw ensureKnownError(e)
|
|
1464
1489
|
} finally {
|
|
1465
1490
|
this.#importingCategories = false
|
|
1466
1491
|
}
|
|
@@ -1525,24 +1550,6 @@ function getCoreKeypairs({ projectKey, projectSecretKey, keyManager }) {
|
|
|
1525
1550
|
}
|
|
1526
1551
|
|
|
1527
1552
|
/**
|
|
1528
|
-
* Validate that a deviceInfo record is written by the device that is it about,
|
|
1529
|
-
* e.g. version.coreKey should equal docId
|
|
1530
|
-
*
|
|
1531
|
-
* @param {import('@comapeo/schema').DeviceInfo} doc
|
|
1532
|
-
* @param {import('@comapeo/schema').VersionIdObject} version
|
|
1533
|
-
* @returns {import('@comapeo/schema').DeviceInfo}
|
|
1534
|
-
*/
|
|
1535
|
-
function mapAndValidateDeviceInfo(doc, { coreDiscoveryKey }) {
|
|
1536
|
-
if (!coreDiscoveryKey.equals(discoveryKey(Buffer.from(doc.docId, 'hex')))) {
|
|
1537
|
-
throw new Error(
|
|
1538
|
-
'Invalid deviceInfo record, cannot write deviceInfo for another device'
|
|
1539
|
-
)
|
|
1540
|
-
}
|
|
1541
|
-
return doc
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
/**
|
|
1545
|
-
*
|
|
1546
1553
|
* @param {string} baseUrl
|
|
1547
1554
|
* @param {string} projectPublicId
|
|
1548
1555
|
* @returns {string}
|