@comapeo/core 3.2.0 → 4.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 +0 -48
- package/dist/blob-api.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/mapeo-project.d.ts +51 -33
- package/dist/mapeo-project.d.ts.map +1 -1
- package/dist/schema/project.d.ts +15 -0
- package/dist/schema/project.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/drizzle/project/0002_cooing_princess_powerful.sql +1 -0
- package/drizzle/project/meta/0002_snapshot.json +1274 -0
- package/drizzle/project/meta/_journal.json +7 -0
- package/package.json +6 -4
- package/src/blob-api.js +0 -26
- package/src/mapeo-project.js +88 -45
- package/src/types.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@comapeo/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Offline p2p mapping library",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -112,12 +112,13 @@
|
|
|
112
112
|
"@comapeo/core2.0.1": "npm:@comapeo/core@2.0.1",
|
|
113
113
|
"@comapeo/ipc": "^2.1.0",
|
|
114
114
|
"@mapeo/default-config": "5.0.0",
|
|
115
|
-
"@mapeo/mock-data": "^
|
|
115
|
+
"@mapeo/mock-data": "^5.0.0",
|
|
116
116
|
"@sinonjs/fake-timers": "^10.0.2",
|
|
117
117
|
"@types/b4a": "^1.6.0",
|
|
118
118
|
"@types/bogon": "^1.0.2",
|
|
119
119
|
"@types/compact-encoding": "^2.15.0",
|
|
120
120
|
"@types/debug": "^4.1.8",
|
|
121
|
+
"@types/geojson": "^7946.0.16",
|
|
121
122
|
"@types/json-schema": "^7.0.11",
|
|
122
123
|
"@types/json-stable-stringify": "^1.0.36",
|
|
123
124
|
"@types/nanobench": "^3.0.0",
|
|
@@ -136,6 +137,7 @@
|
|
|
136
137
|
"drizzle-kit": "^0.20.14",
|
|
137
138
|
"eslint": "^8.57.0",
|
|
138
139
|
"execa": "^9.5.1",
|
|
140
|
+
"filter-obj": "^6.1.0",
|
|
139
141
|
"husky": "^8.0.0",
|
|
140
142
|
"iterpal": "^0.4.0",
|
|
141
143
|
"lint-staged": "^14.0.1",
|
|
@@ -160,7 +162,7 @@
|
|
|
160
162
|
},
|
|
161
163
|
"dependencies": {
|
|
162
164
|
"@comapeo/fallback-smp": "^1.0.0",
|
|
163
|
-
"@comapeo/schema": "
|
|
165
|
+
"@comapeo/schema": "2.0.0",
|
|
164
166
|
"@digidem/types": "^2.3.0",
|
|
165
167
|
"@fastify/error": "^3.4.1",
|
|
166
168
|
"@fastify/type-provider-typebox": "^4.1.0",
|
|
@@ -189,7 +191,7 @@
|
|
|
189
191
|
"json-stable-stringify": "^1.1.1",
|
|
190
192
|
"magic-bytes.js": "^1.10.0",
|
|
191
193
|
"map-obj": "^5.0.2",
|
|
192
|
-
"mime": "^4.0.
|
|
194
|
+
"mime": "^4.0.7",
|
|
193
195
|
"multi-core-indexer": "^1.0.0",
|
|
194
196
|
"p-defer": "^4.0.0",
|
|
195
197
|
"p-event": "^6.0.1",
|
package/src/blob-api.js
CHANGED
|
@@ -4,35 +4,9 @@ import { Transform, pipelinePromise as pipeline } from 'streamx'
|
|
|
4
4
|
import { createHash, randomBytes } from 'node:crypto'
|
|
5
5
|
/** @import { BlobId, BlobType } from './types.js' */
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* Location coordinate data. Based on [Expo's `LocationObjectCoords`][0].
|
|
9
|
-
* [0]: https://docs.expo.dev/versions/latest/sdk/location/#locationobjectcoords
|
|
10
|
-
*
|
|
11
|
-
* @typedef {object} LocationObjectCoords
|
|
12
|
-
* @prop {number | null} accuracy
|
|
13
|
-
* @prop {number | null} altitude
|
|
14
|
-
* @prop {number | null} altitudeAccuracy
|
|
15
|
-
* @prop {number | null} heading
|
|
16
|
-
* @prop {number} latitude
|
|
17
|
-
* @prop {number} longitude
|
|
18
|
-
* @prop {number | null} speed
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Location metadata for a blob. Based on [Expo's `LocationObject`][0].
|
|
23
|
-
* [0]: https://docs.expo.dev/versions/latest/sdk/location/#locationobject
|
|
24
|
-
*
|
|
25
|
-
* @typedef {object} LocationObject
|
|
26
|
-
* @prop {LocationObjectCoords} coords
|
|
27
|
-
* @prop {boolean} [mocked]
|
|
28
|
-
* @prop {number} timestamp
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
7
|
/**
|
|
32
8
|
* @typedef {object} Metadata
|
|
33
9
|
* @prop {string} mimeType
|
|
34
|
-
* @prop {number} timestamp
|
|
35
|
-
* @prop {LocationObject} [location]
|
|
36
10
|
*/
|
|
37
11
|
|
|
38
12
|
export class BlobApi {
|
package/src/mapeo-project.js
CHANGED
|
@@ -6,6 +6,7 @@ import { discoveryKey } from 'hypercore-crypto'
|
|
|
6
6
|
import { TypedEmitter } from 'tiny-typed-emitter'
|
|
7
7
|
import ZipArchive from 'zip-stream-promise'
|
|
8
8
|
import * as b4a from 'b4a'
|
|
9
|
+
import mime from 'mime/lite'
|
|
9
10
|
// @ts-expect-error
|
|
10
11
|
import { Readable, pipelinePromise } from 'streamx'
|
|
11
12
|
|
|
@@ -69,6 +70,10 @@ import { createWriteStream } from 'fs'
|
|
|
69
70
|
/** @typedef {Omit<ProjectSettingsValue, 'schemaName'>} EditableProjectSettings */
|
|
70
71
|
/** @typedef {ProjectSettingsValue['configMetadata']} ConfigMetadata */
|
|
71
72
|
/** @typedef {Map<string,Attachment>} SeenAttachments*/
|
|
73
|
+
/** @typedef {object} BlobRef
|
|
74
|
+
* @prop {string|undefined} mimeType
|
|
75
|
+
* @prop {BlobId} blobId
|
|
76
|
+
*/
|
|
72
77
|
|
|
73
78
|
const CORESTORE_STORAGE_FOLDER_NAME = 'corestore'
|
|
74
79
|
const INDEXER_STORAGE_FOLDER_NAME = 'indexer'
|
|
@@ -83,8 +88,6 @@ export const kClearDataIfLeft = Symbol('clear data if left project')
|
|
|
83
88
|
export const kSetIsArchiveDevice = Symbol('set isArchiveDevice')
|
|
84
89
|
export const kIsArchiveDevice = Symbol('isArchiveDevice (temp - test only)')
|
|
85
90
|
export const kGeoJSONFileName = Symbol('geoJSONFileName')
|
|
86
|
-
export const kExportGeoJSONStream = Symbol('exportGeoJSONStream')
|
|
87
|
-
export const kExportZipStream = Symbol('exportZipStream')
|
|
88
91
|
|
|
89
92
|
const EMPTY_PROJECT_SETTINGS = Object.freeze({})
|
|
90
93
|
|
|
@@ -786,40 +789,43 @@ export class MapeoProject extends TypedEmitter {
|
|
|
786
789
|
}
|
|
787
790
|
}
|
|
788
791
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
792
|
+
const metadataCoords = observation.metadata?.position?.coords
|
|
793
|
+
const altitude = metadataCoords?.altitude
|
|
794
|
+
|
|
795
|
+
/** @type {[number, number] | [number, number, number] | null} */
|
|
796
|
+
let coordinates = null
|
|
797
|
+
|
|
798
|
+
// Prioritize using the observation's `lat` and `lon` fields
|
|
799
|
+
if (typeof lat === 'number' && typeof lon === 'number') {
|
|
800
|
+
coordinates =
|
|
801
|
+
typeof altitude === 'number' ? [lon, lat, altitude] : [lon, lat]
|
|
802
|
+
} else {
|
|
803
|
+
// Fall back to using the observation metadata's position if possible
|
|
804
|
+
if (
|
|
805
|
+
typeof metadataCoords?.latitude === 'number' &&
|
|
806
|
+
typeof metadataCoords?.longitude === 'number'
|
|
807
|
+
) {
|
|
808
|
+
coordinates =
|
|
809
|
+
typeof altitude === 'number'
|
|
810
|
+
? [metadataCoords.longitude, metadataCoords.latitude, altitude]
|
|
811
|
+
: [metadataCoords.longitude, metadataCoords.latitude]
|
|
798
812
|
}
|
|
799
813
|
}
|
|
800
814
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
815
|
+
/** @type {import('geojson').Feature<import('geojson').Point | null>} */
|
|
816
|
+
const feature = {
|
|
817
|
+
type: 'Feature',
|
|
818
|
+
properties: observation,
|
|
819
|
+
geometry: coordinates
|
|
820
|
+
? {
|
|
821
|
+
type: 'Point',
|
|
822
|
+
coordinates,
|
|
823
|
+
}
|
|
824
|
+
: null,
|
|
804
825
|
}
|
|
805
|
-
const hasLatLon =
|
|
806
|
-
typeof longitude === 'number' && typeof latitude === 'number'
|
|
807
|
-
const geometry = hasLatLon
|
|
808
|
-
? {
|
|
809
|
-
type: 'Point',
|
|
810
|
-
coordinates,
|
|
811
|
-
}
|
|
812
|
-
: null
|
|
813
826
|
const comma = first ? '' : ','
|
|
814
827
|
first = false
|
|
815
|
-
yield b4a.from(
|
|
816
|
-
`${comma}\n ` +
|
|
817
|
-
JSON.stringify({
|
|
818
|
-
type: 'Feature',
|
|
819
|
-
properties: observation,
|
|
820
|
-
geometry,
|
|
821
|
-
})
|
|
822
|
-
)
|
|
828
|
+
yield b4a.from(`${comma}\n ` + JSON.stringify(feature))
|
|
823
829
|
}
|
|
824
830
|
}
|
|
825
831
|
|
|
@@ -941,7 +947,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
941
947
|
* @param {string} [options.lang]
|
|
942
948
|
* @returns {Readable<Buffer | Uint8Array>}
|
|
943
949
|
*/
|
|
944
|
-
|
|
950
|
+
#exportGeoJSONStream({
|
|
945
951
|
observations = true,
|
|
946
952
|
tracks = true,
|
|
947
953
|
lang,
|
|
@@ -1027,7 +1033,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
1027
1033
|
) {
|
|
1028
1034
|
const fileName = await this[kGeoJSONFileName](observations, tracks)
|
|
1029
1035
|
const filePath = path.join(exportFolder, fileName)
|
|
1030
|
-
const source = this
|
|
1036
|
+
const source = this.#exportGeoJSONStream({ observations, tracks, lang })
|
|
1031
1037
|
const sink = createWriteStream(filePath)
|
|
1032
1038
|
await pipelinePromise(source, sink)
|
|
1033
1039
|
|
|
@@ -1036,15 +1042,37 @@ export class MapeoProject extends TypedEmitter {
|
|
|
1036
1042
|
|
|
1037
1043
|
/**
|
|
1038
1044
|
* @param {Attachment} attachment
|
|
1039
|
-
* @returns {Promise<null |
|
|
1045
|
+
* @returns {Promise<null | BlobRef>}
|
|
1040
1046
|
*/
|
|
1041
|
-
async #
|
|
1047
|
+
async #tryGetAttachmentBlob(attachment) {
|
|
1042
1048
|
// Audio must not have variants
|
|
1043
1049
|
for (const variant of VARIANT_EXPORT_ORDER) {
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1050
|
+
try {
|
|
1051
|
+
const blobId = buildBlobId(attachment, variant)
|
|
1052
|
+
const entry = await this.#blobStore.entry(blobId)
|
|
1053
|
+
if (!entry) continue
|
|
1054
|
+
const metadata = entry.value.metadata
|
|
1055
|
+
if (!metadata || typeof metadata !== 'object') continue
|
|
1056
|
+
let mimeType = undefined
|
|
1057
|
+
if ('mimeType' in metadata) {
|
|
1058
|
+
if (typeof metadata.mimeType === 'string') {
|
|
1059
|
+
mimeType = metadata.mimeType
|
|
1060
|
+
} else {
|
|
1061
|
+
this.#l.log('Invalid type for mimeType in blob', blobId, entry)
|
|
1062
|
+
continue
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
return { blobId, mimeType }
|
|
1066
|
+
} catch (e) {
|
|
1067
|
+
if (!(e instanceof Error)) throw e
|
|
1068
|
+
this.#l.log(
|
|
1069
|
+
'Error loading blob id for attachment',
|
|
1070
|
+
attachment,
|
|
1071
|
+
variant,
|
|
1072
|
+
e.message
|
|
1073
|
+
)
|
|
1074
|
+
continue
|
|
1075
|
+
}
|
|
1048
1076
|
}
|
|
1049
1077
|
|
|
1050
1078
|
return null
|
|
@@ -1066,7 +1094,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
1066
1094
|
// GeoJSON
|
|
1067
1095
|
const geoJSONFileName = await this[kGeoJSONFileName](observations, tracks)
|
|
1068
1096
|
const seenAttachments = new Map()
|
|
1069
|
-
const geoJSONStream = this
|
|
1097
|
+
const geoJSONStream = this.#exportGeoJSONStream({
|
|
1070
1098
|
observations,
|
|
1071
1099
|
tracks,
|
|
1072
1100
|
lang,
|
|
@@ -1079,16 +1107,31 @@ export class MapeoProject extends TypedEmitter {
|
|
|
1079
1107
|
const missingAttachments = []
|
|
1080
1108
|
// Attachments
|
|
1081
1109
|
if (attachments) {
|
|
1082
|
-
const mediaFolder = this.#exportPrefix('Media') + '/'
|
|
1110
|
+
const mediaFolder = (await this.#exportPrefix('Media')) + '/'
|
|
1083
1111
|
for (const attachment of seenAttachments.values()) {
|
|
1084
|
-
const
|
|
1085
|
-
if (
|
|
1112
|
+
const ref = await this.#tryGetAttachmentBlob(attachment)
|
|
1113
|
+
if (ref === null) {
|
|
1086
1114
|
missingAttachments.push(attachment)
|
|
1087
1115
|
continue
|
|
1088
1116
|
}
|
|
1089
1117
|
|
|
1118
|
+
const { blobId, mimeType } = ref
|
|
1119
|
+
let extensionString = ''
|
|
1120
|
+
if (mimeType) {
|
|
1121
|
+
const extension = mime.getExtension(mimeType)
|
|
1122
|
+
if (extension) {
|
|
1123
|
+
extensionString = '.' + extension
|
|
1124
|
+
} else {
|
|
1125
|
+
this.#l.log('Got unknown mime type in attachment blob', attachment)
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1090
1129
|
const stream = this.#blobStore.createReadStream(blobId)
|
|
1091
|
-
const name =
|
|
1130
|
+
const name = path.posix.join(
|
|
1131
|
+
mediaFolder,
|
|
1132
|
+
blobId.variant,
|
|
1133
|
+
`${attachment.name}${extensionString}`
|
|
1134
|
+
)
|
|
1092
1135
|
|
|
1093
1136
|
// @ts-expect-error
|
|
1094
1137
|
await archive.entry(stream, { name })
|
|
@@ -1118,7 +1161,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
1118
1161
|
* @param {string} [options.lang]
|
|
1119
1162
|
* @returns {Readable<Buffer | Uint8Array>}
|
|
1120
1163
|
*/
|
|
1121
|
-
|
|
1164
|
+
#exportZipStream({
|
|
1122
1165
|
observations = true,
|
|
1123
1166
|
tracks = true,
|
|
1124
1167
|
attachments = true,
|
|
@@ -1153,7 +1196,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
1153
1196
|
) {
|
|
1154
1197
|
const fileName = await this.#zipFileName(observations, tracks)
|
|
1155
1198
|
const filePath = path.join(exportFolder, fileName)
|
|
1156
|
-
const source = this
|
|
1199
|
+
const source = this.#exportZipStream({
|
|
1157
1200
|
observations,
|
|
1158
1201
|
tracks,
|
|
1159
1202
|
attachments,
|
package/src/types.ts
CHANGED
|
@@ -162,7 +162,7 @@ export type BlobStoreEntriesStream = Readable & {
|
|
|
162
162
|
>
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
export type Attachment = Observation['attachments'][
|
|
165
|
+
export type Attachment = Observation['attachments'][number]
|
|
166
166
|
|
|
167
167
|
export type StringToTaggedUnion<T extends string> = {
|
|
168
168
|
[K in T]: {
|