@comapeo/core 4.3.0 → 5.0.0-next.3
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-store/downloader.d.ts +5 -2
- package/dist/blob-store/downloader.d.ts.map +1 -1
- package/dist/constants.d.ts +0 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/core-ownership.d.ts +2 -6
- package/dist/core-ownership.d.ts.map +1 -1
- package/dist/datatype/index.d.ts +30 -30
- package/dist/datatype/index.d.ts.map +1 -1
- package/dist/discovery/local-discovery.d.ts.map +1 -1
- package/dist/import-categories.d.ts +19 -0
- package/dist/import-categories.d.ts.map +1 -0
- package/dist/intl/iso639.d.ts +4 -0
- package/dist/intl/iso639.d.ts.map +1 -0
- package/dist/intl/parse-bcp-47.d.ts +22 -0
- package/dist/intl/parse-bcp-47.d.ts.map +1 -0
- package/dist/invite/invite-api.d.ts.map +1 -1
- package/dist/lib/drizzle-helpers.d.ts +19 -1
- package/dist/lib/drizzle-helpers.d.ts.map +1 -1
- package/dist/mapeo-manager.d.ts +15 -9
- package/dist/mapeo-manager.d.ts.map +1 -1
- package/dist/mapeo-project.d.ts +4969 -3017
- package/dist/mapeo-project.d.ts.map +1 -1
- package/dist/schema/client.d.ts +246 -232
- package/dist/schema/client.d.ts.map +1 -1
- package/dist/schema/comapeo-to-drizzle.d.ts +65 -0
- package/dist/schema/comapeo-to-drizzle.d.ts.map +1 -0
- package/dist/schema/json-schema-to-drizzle.d.ts +18 -0
- package/dist/schema/json-schema-to-drizzle.d.ts.map +1 -0
- package/dist/schema/project.d.ts +2711 -1835
- package/dist/schema/project.d.ts.map +1 -1
- package/dist/schema/types.d.ts +73 -66
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/translation-api.d.ts +112 -192
- package/dist/translation-api.d.ts.map +1 -1
- package/dist/types.d.ts +9 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +25 -3
- package/dist/utils.d.ts.map +1 -1
- package/drizzle/client/0004_glorious_shape.sql +1 -0
- package/drizzle/client/meta/0000_snapshot.json +13 -9
- package/drizzle/client/meta/0001_snapshot.json +13 -9
- package/drizzle/client/meta/0002_snapshot.json +13 -9
- package/drizzle/client/meta/0003_snapshot.json +13 -9
- package/drizzle/client/meta/0004_snapshot.json +239 -0
- package/drizzle/client/meta/_journal.json +7 -0
- package/drizzle/project/meta/0000_snapshot.json +43 -24
- package/drizzle/project/meta/0001_snapshot.json +47 -26
- package/drizzle/project/meta/0002_snapshot.json +47 -26
- package/package.json +17 -9
- package/src/constants.js +0 -3
- package/src/datatype/index.js +92 -39
- package/src/discovery/local-discovery.js +3 -2
- package/src/import-categories.js +368 -0
- package/src/index-writer/index.js +1 -1
- package/src/intl/iso639.js +8118 -0
- package/src/intl/parse-bcp-47.js +91 -0
- package/src/invite/invite-api.js +2 -0
- package/src/lib/drizzle-helpers.js +70 -18
- package/src/mapeo-manager.js +138 -88
- package/src/mapeo-project.js +72 -229
- package/src/roles.js +1 -1
- package/src/schema/client.js +22 -28
- package/src/schema/comapeo-to-drizzle.js +57 -0
- package/src/schema/{schema-to-drizzle.js → json-schema-to-drizzle.js} +25 -25
- package/src/schema/project.js +24 -37
- package/src/schema/types.ts +138 -99
- package/src/translation-api.js +65 -13
- package/src/types.ts +11 -20
- package/src/utils.js +37 -3
- package/dist/config-import.d.ts +0 -74
- package/dist/config-import.d.ts.map +0 -1
- package/dist/schema/schema-to-drizzle.d.ts +0 -20
- package/dist/schema/schema-to-drizzle.d.ts.map +0 -1
- package/dist/schema/utils.d.ts +0 -55
- package/dist/schema/utils.d.ts.map +0 -1
- package/src/config-import.js +0 -603
- package/src/schema/utils.js +0 -51
package/src/mapeo-project.js
CHANGED
|
@@ -2,7 +2,7 @@ import path from 'path'
|
|
|
2
2
|
import Database from 'better-sqlite3'
|
|
3
3
|
import { decodeBlockPrefix, decode, parseVersionId } from '@comapeo/schema'
|
|
4
4
|
import { drizzle } from 'drizzle-orm/better-sqlite3'
|
|
5
|
-
import { sql, count } from 'drizzle-orm'
|
|
5
|
+
import { sql, count, eq } from 'drizzle-orm'
|
|
6
6
|
import { discoveryKey } from 'hypercore-crypto'
|
|
7
7
|
import { TypedEmitter } from 'tiny-typed-emitter'
|
|
8
8
|
import ZipArchive from 'zip-stream-promise'
|
|
@@ -11,7 +11,7 @@ import mime from 'mime/lite'
|
|
|
11
11
|
// @ts-expect-error
|
|
12
12
|
import { Readable, pipelinePromise } from 'streamx'
|
|
13
13
|
|
|
14
|
-
import { NAMESPACES, NAMESPACE_SCHEMAS
|
|
14
|
+
import { NAMESPACES, NAMESPACE_SCHEMAS } from './constants.js'
|
|
15
15
|
import { CoreManager } from './core-manager/index.js'
|
|
16
16
|
import { DataStore } from './datastore/index.js'
|
|
17
17
|
import { DataType, kCreateWithDocId } from './datatype/index.js'
|
|
@@ -61,11 +61,12 @@ import {
|
|
|
61
61
|
} from './sync/sync-api.js'
|
|
62
62
|
import { Logger } from './logger.js'
|
|
63
63
|
import { IconApi } from './icon-api.js'
|
|
64
|
-
import {
|
|
64
|
+
import { importCategories } from './import-categories.js'
|
|
65
65
|
import TranslationApi from './translation-api.js'
|
|
66
66
|
import { NotFoundError, nullIfNotFound } from './errors.js'
|
|
67
67
|
import { WebSocket } from 'ws'
|
|
68
68
|
import { createWriteStream } from 'fs'
|
|
69
|
+
import ensureError from 'ensure-error'
|
|
69
70
|
/** @import { ProjectSettingsValue, Observation, Track } from '@comapeo/schema' */
|
|
70
71
|
/** @import { Attachment, CoreStorage, BlobFilter, BlobId, BlobStoreEntriesStream, KeyPair, Namespace, ReplicationStream, GenericBlobFilter, MapeoValueMap, MapeoDocMap } from './types.js' */
|
|
71
72
|
/** @typedef {Omit<ProjectSettingsValue, 'schemaName'>} EditableProjectSettings */
|
|
@@ -97,12 +98,12 @@ export const kBlobStore = Symbol('blobStore')
|
|
|
97
98
|
export const kProjectReplicate = Symbol('replicate project')
|
|
98
99
|
export const kDataTypes = Symbol('dataTypes')
|
|
99
100
|
export const kProjectLeave = Symbol('leave project')
|
|
100
|
-
export const
|
|
101
|
+
export const kClearData = Symbol('clear project data')
|
|
101
102
|
export const kSetIsArchiveDevice = Symbol('set isArchiveDevice')
|
|
102
103
|
export const kIsArchiveDevice = Symbol('isArchiveDevice (temp - test only)')
|
|
103
104
|
export const kGeoJSONFileName = Symbol('geoJSONFileName')
|
|
104
105
|
|
|
105
|
-
const EMPTY_PROJECT_SETTINGS = Object.freeze({})
|
|
106
|
+
const EMPTY_PROJECT_SETTINGS = Object.freeze({ sendStats: false })
|
|
106
107
|
|
|
107
108
|
/** @type BlobId['variant'][]*/
|
|
108
109
|
const VARIANT_EXPORT_ORDER = ['original', 'preview', 'thumbnail']
|
|
@@ -130,9 +131,10 @@ export class MapeoProject extends TypedEmitter {
|
|
|
130
131
|
#translationApi
|
|
131
132
|
#l
|
|
132
133
|
/** @type {Boolean} this avoids loading multiple configs in parallel */
|
|
133
|
-
#
|
|
134
|
+
#importingCategories
|
|
134
135
|
|
|
135
136
|
static EMPTY_PROJECT_SETTINGS = EMPTY_PROJECT_SETTINGS
|
|
137
|
+
#getFallbackProjectInfo
|
|
136
138
|
|
|
137
139
|
/**
|
|
138
140
|
* @param {Object} opts
|
|
@@ -149,6 +151,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
149
151
|
* @param {(url: string) => WebSocket} [opts.makeWebsocket]
|
|
150
152
|
* @param {import('./local-peers.js').LocalPeers} opts.localPeers
|
|
151
153
|
* @param {boolean} opts.isArchiveDevice Whether this device is an archive device
|
|
154
|
+
* @param {() => import('./schema/client.js').ProjectInfo | undefined} opts.getFallbackProjectInfo
|
|
152
155
|
* @param {Logger} [opts.logger]
|
|
153
156
|
*
|
|
154
157
|
*/
|
|
@@ -167,13 +170,15 @@ export class MapeoProject extends TypedEmitter {
|
|
|
167
170
|
localPeers,
|
|
168
171
|
logger,
|
|
169
172
|
isArchiveDevice,
|
|
173
|
+
getFallbackProjectInfo,
|
|
170
174
|
}) {
|
|
171
175
|
super()
|
|
172
176
|
|
|
173
177
|
this.#l = Logger.create('project', logger)
|
|
174
178
|
this.#deviceId = getDeviceId(keyManager)
|
|
175
179
|
this.#projectKey = projectKey
|
|
176
|
-
this.#
|
|
180
|
+
this.#importingCategories = false
|
|
181
|
+
this.#getFallbackProjectInfo = getFallbackProjectInfo
|
|
177
182
|
|
|
178
183
|
const getReplicationStream = this[kProjectReplicate].bind(this, true)
|
|
179
184
|
|
|
@@ -216,6 +221,11 @@ export class MapeoProject extends TypedEmitter {
|
|
|
216
221
|
|
|
217
222
|
if (reindex) {
|
|
218
223
|
for (const table of indexedTables) db.delete(table).run()
|
|
224
|
+
|
|
225
|
+
sharedDb
|
|
226
|
+
.delete(projectSettingsTable)
|
|
227
|
+
.where(eq(projectSettingsTable.docId, this.#projectId))
|
|
228
|
+
.run()
|
|
219
229
|
}
|
|
220
230
|
|
|
221
231
|
///////// 3. Setup random-access-storage functions
|
|
@@ -287,74 +297,78 @@ export class MapeoProject extends TypedEmitter {
|
|
|
287
297
|
|
|
288
298
|
/** @type {typeof TranslationApi.prototype.get} */
|
|
289
299
|
const getTranslations = (...args) => this.$translation.get(...args)
|
|
300
|
+
/** @type {(versionId: string) => Promise<string>} */
|
|
301
|
+
const getDeviceIdForVersionId = (...args) =>
|
|
302
|
+
this.$originalVersionIdToDeviceId(...args)
|
|
303
|
+
|
|
290
304
|
this.#dataTypes = {
|
|
291
305
|
observation: new DataType({
|
|
292
306
|
dataStore: this.#dataStores.data,
|
|
293
307
|
table: observationTable,
|
|
294
308
|
db,
|
|
295
|
-
|
|
309
|
+
getDeviceIdForVersionId,
|
|
296
310
|
}),
|
|
297
311
|
track: new DataType({
|
|
298
312
|
dataStore: this.#dataStores.data,
|
|
299
313
|
table: trackTable,
|
|
300
314
|
db,
|
|
301
|
-
|
|
315
|
+
getDeviceIdForVersionId,
|
|
302
316
|
}),
|
|
303
317
|
remoteDetectionAlert: new DataType({
|
|
304
318
|
dataStore: this.#dataStores.data,
|
|
305
319
|
table: remoteDetectionAlertTable,
|
|
306
320
|
db,
|
|
307
|
-
|
|
321
|
+
getDeviceIdForVersionId,
|
|
308
322
|
}),
|
|
309
323
|
preset: new DataType({
|
|
310
324
|
dataStore: this.#dataStores.config,
|
|
311
325
|
table: presetTable,
|
|
312
326
|
db,
|
|
313
327
|
getTranslations,
|
|
328
|
+
getDeviceIdForVersionId,
|
|
314
329
|
}),
|
|
315
330
|
field: new DataType({
|
|
316
331
|
dataStore: this.#dataStores.config,
|
|
317
332
|
table: fieldTable,
|
|
318
333
|
db,
|
|
319
334
|
getTranslations,
|
|
335
|
+
getDeviceIdForVersionId,
|
|
320
336
|
}),
|
|
321
337
|
projectSettings: new DataType({
|
|
322
338
|
dataStore: this.#dataStores.config,
|
|
323
339
|
table: projectSettingsTable,
|
|
324
340
|
db: sharedDb,
|
|
325
|
-
|
|
341
|
+
getDeviceIdForVersionId,
|
|
326
342
|
}),
|
|
327
343
|
coreOwnership: new DataType({
|
|
328
344
|
dataStore: this.#dataStores.auth,
|
|
329
345
|
table: coreOwnershipTable,
|
|
330
346
|
db,
|
|
331
|
-
|
|
347
|
+
getDeviceIdForVersionId: () => Promise.resolve(''),
|
|
332
348
|
}),
|
|
333
349
|
role: new DataType({
|
|
334
350
|
dataStore: this.#dataStores.auth,
|
|
335
351
|
table: roleTable,
|
|
336
352
|
db,
|
|
337
|
-
|
|
353
|
+
getDeviceIdForVersionId: () => Promise.resolve(''),
|
|
338
354
|
}),
|
|
339
355
|
deviceInfo: new DataType({
|
|
340
356
|
dataStore: this.#dataStores.config,
|
|
341
357
|
table: deviceInfoTable,
|
|
342
358
|
db,
|
|
343
|
-
|
|
359
|
+
getDeviceIdForVersionId: () => Promise.resolve(''),
|
|
344
360
|
}),
|
|
345
361
|
icon: new DataType({
|
|
346
362
|
dataStore: this.#dataStores.config,
|
|
347
363
|
table: iconTable,
|
|
348
364
|
db,
|
|
349
|
-
|
|
365
|
+
getDeviceIdForVersionId,
|
|
350
366
|
}),
|
|
351
367
|
translation: new DataType({
|
|
352
368
|
dataStore: this.#dataStores.config,
|
|
353
369
|
table: translationTable,
|
|
354
370
|
db,
|
|
355
|
-
|
|
356
|
-
throw new Error('Cannot get translation for translations')
|
|
357
|
-
},
|
|
371
|
+
getDeviceIdForVersionId,
|
|
358
372
|
}),
|
|
359
373
|
}
|
|
360
374
|
this.#identityKeypair = keyManager.getIdentityKeypair()
|
|
@@ -675,7 +689,12 @@ export class MapeoProject extends TypedEmitter {
|
|
|
675
689
|
await this.#dataTypes.projectSettings.getByDocId(this.#projectId)
|
|
676
690
|
)
|
|
677
691
|
} catch (e) {
|
|
678
|
-
|
|
692
|
+
// if (e instanceof Error && e.name !== 'NotFoundError') throw e
|
|
693
|
+
// If the project has not completed an initial sync, project settings will
|
|
694
|
+
// not be available, so use fallback project info which is set from the
|
|
695
|
+
// invite that was used to join the project.
|
|
696
|
+
const fallbackInfo = this.#getFallbackProjectInfo()
|
|
697
|
+
return fallbackInfo || EMPTY_PROJECT_SETTINGS
|
|
679
698
|
}
|
|
680
699
|
}
|
|
681
700
|
|
|
@@ -684,11 +703,9 @@ export class MapeoProject extends TypedEmitter {
|
|
|
684
703
|
*/
|
|
685
704
|
async $hasSyncedProjectSettings() {
|
|
686
705
|
try {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
return settings.createdAt !== UNIX_EPOCH_DATE
|
|
706
|
+
// Should error if we haven't synced before
|
|
707
|
+
await this.#dataTypes.projectSettings.getByDocId(this.#projectId)
|
|
708
|
+
return true
|
|
692
709
|
} catch (e) {
|
|
693
710
|
return false
|
|
694
711
|
}
|
|
@@ -706,6 +723,7 @@ export class MapeoProject extends TypedEmitter {
|
|
|
706
723
|
}
|
|
707
724
|
|
|
708
725
|
/**
|
|
726
|
+
* @deprecated
|
|
709
727
|
* @param {string} originalVersionId The `originalVersionId` from a document.
|
|
710
728
|
* @returns {Promise<string>} The device ID for this creator.
|
|
711
729
|
* @throws When device ID cannot be found.
|
|
@@ -1297,19 +1315,14 @@ export class MapeoProject extends TypedEmitter {
|
|
|
1297
1315
|
|
|
1298
1316
|
await this.#roles.assignRole(this.#deviceId, LEFT_ROLE_ID)
|
|
1299
1317
|
|
|
1300
|
-
await this[
|
|
1318
|
+
await this[kClearData]()
|
|
1301
1319
|
}
|
|
1302
1320
|
|
|
1303
1321
|
/**
|
|
1304
|
-
* Clear data
|
|
1322
|
+
* Clear synced data, but keep auth data and own data
|
|
1305
1323
|
* @returns {Promise<void>}
|
|
1306
1324
|
*/
|
|
1307
|
-
async [
|
|
1308
|
-
const role = await this.$getOwnRole()
|
|
1309
|
-
if (role.roleId !== LEFT_ROLE_ID) {
|
|
1310
|
-
return
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1325
|
+
async [kClearData]() {
|
|
1313
1326
|
const namespacesWithoutAuth =
|
|
1314
1327
|
/** @satisfies {Exclude<Namespace, 'auth'>[]} */ ([
|
|
1315
1328
|
'config',
|
|
@@ -1340,172 +1353,44 @@ export class MapeoProject extends TypedEmitter {
|
|
|
1340
1353
|
}
|
|
1341
1354
|
}
|
|
1342
1355
|
|
|
1343
|
-
/**
|
|
1344
|
-
*
|
|
1345
|
-
*
|
|
1356
|
+
/**
|
|
1357
|
+
* @deprecated
|
|
1358
|
+
* @param {object} opts
|
|
1359
|
+
* @param {string} opts.configPath
|
|
1360
|
+
* @returns {Promise<Error[]>}
|
|
1346
1361
|
*/
|
|
1347
1362
|
async importConfig({ configPath }) {
|
|
1363
|
+
try {
|
|
1364
|
+
await this.$importCategories({ filePath: configPath })
|
|
1365
|
+
return []
|
|
1366
|
+
} catch (e) {
|
|
1367
|
+
return [ensureError(e)]
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
/**
|
|
1372
|
+
* @param {object} opts
|
|
1373
|
+
* @param {string} opts.filePath
|
|
1374
|
+
* @returns {Promise<void>}
|
|
1375
|
+
*/
|
|
1376
|
+
async $importCategories({ filePath }) {
|
|
1348
1377
|
assert(
|
|
1349
|
-
!this.#
|
|
1350
|
-
'Cannot run multiple
|
|
1378
|
+
!this.#importingCategories,
|
|
1379
|
+
'Cannot run multiple category imports at the same time'
|
|
1351
1380
|
)
|
|
1352
|
-
this.#
|
|
1381
|
+
this.#importingCategories = true
|
|
1353
1382
|
|
|
1354
1383
|
try {
|
|
1355
|
-
|
|
1356
|
-
const presetsToDelete = await grabDocsToDelete(this.preset)
|
|
1357
|
-
const fieldsToDelete = await grabDocsToDelete(this.field)
|
|
1358
|
-
// delete only translations that refer to deleted fields and presets
|
|
1359
|
-
const translationsToDelete = await grabTranslationsToDelete({
|
|
1360
|
-
logger: this.#l,
|
|
1361
|
-
translation: this.$translation.dataType,
|
|
1362
|
-
preset: this.preset,
|
|
1363
|
-
field: this.field,
|
|
1364
|
-
})
|
|
1365
|
-
|
|
1366
|
-
const config = await readConfig(configPath)
|
|
1367
|
-
/** @type {Map<string, import('./icon-api.js').IconRef>} */
|
|
1368
|
-
const iconNameToRef = new Map()
|
|
1369
|
-
/** @type {Map<string, import('@comapeo/schema').PresetValue['fieldRefs'][1]>} */
|
|
1370
|
-
const fieldNameToRef = new Map()
|
|
1371
|
-
/** @type {Map<string,import('@comapeo/schema').TranslationValue['docRef']>} */
|
|
1372
|
-
const presetNameToRef = new Map()
|
|
1373
|
-
|
|
1374
|
-
// Do this in serial not parallel to avoid memory issues (avoid keeping all icon buffers in memory)
|
|
1375
|
-
for await (const icon of config.icons()) {
|
|
1376
|
-
const { docId, versionId } = await this.#iconApi.create(icon)
|
|
1377
|
-
iconNameToRef.set(icon.name, { docId, versionId })
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
// Ok to create fields and presets in parallel
|
|
1381
|
-
const fieldPromises = []
|
|
1382
|
-
for (const { name, value } of config.fields()) {
|
|
1383
|
-
fieldPromises.push(
|
|
1384
|
-
this.#dataTypes.field.create(value).then(({ docId, versionId }) => {
|
|
1385
|
-
fieldNameToRef.set(name, { docId, versionId })
|
|
1386
|
-
})
|
|
1387
|
-
)
|
|
1388
|
-
}
|
|
1389
|
-
await Promise.all(fieldPromises)
|
|
1390
|
-
|
|
1391
|
-
const presetsWithRefs = []
|
|
1392
|
-
for (const { fieldNames, iconName, value, name } of config.presets()) {
|
|
1393
|
-
const fieldRefs = fieldNames.map((fieldName) => {
|
|
1394
|
-
const fieldRef = fieldNameToRef.get(fieldName)
|
|
1395
|
-
if (!fieldRef) {
|
|
1396
|
-
throw new NotFoundError(
|
|
1397
|
-
`field ${fieldName} not found (referenced by preset ${value.name})})`
|
|
1398
|
-
)
|
|
1399
|
-
}
|
|
1400
|
-
return fieldRef
|
|
1401
|
-
})
|
|
1402
|
-
|
|
1403
|
-
if (!iconName) {
|
|
1404
|
-
throw new Error(`preset ${value.name} is missing an icon name`)
|
|
1405
|
-
}
|
|
1406
|
-
const iconRef = iconNameToRef.get(iconName)
|
|
1407
|
-
if (!iconRef) {
|
|
1408
|
-
throw new NotFoundError(
|
|
1409
|
-
`icon ${iconName} not found (referenced by preset ${value.name})`
|
|
1410
|
-
)
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
presetsWithRefs.push({
|
|
1414
|
-
preset: {
|
|
1415
|
-
...value,
|
|
1416
|
-
iconRef,
|
|
1417
|
-
fieldRefs,
|
|
1418
|
-
},
|
|
1419
|
-
name,
|
|
1420
|
-
})
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
const presetPromises = []
|
|
1424
|
-
for (const { preset, name } of presetsWithRefs) {
|
|
1425
|
-
presetPromises.push(
|
|
1426
|
-
this.preset.create(preset).then(({ docId, versionId }) => {
|
|
1427
|
-
presetNameToRef.set(name, { docId, versionId })
|
|
1428
|
-
})
|
|
1429
|
-
)
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
await Promise.all(presetPromises)
|
|
1433
|
-
|
|
1434
|
-
const translationPromises = []
|
|
1435
|
-
for (const { name, value } of config.translations()) {
|
|
1436
|
-
let docRef
|
|
1437
|
-
if (value.docRefType === 'field') {
|
|
1438
|
-
docRef = { ...fieldNameToRef.get(name) }
|
|
1439
|
-
} else if (value.docRefType === 'preset') {
|
|
1440
|
-
docRef = { ...presetNameToRef.get(name) }
|
|
1441
|
-
} else {
|
|
1442
|
-
throw new Error(`invalid docRefType ${value.docRefType}`)
|
|
1443
|
-
}
|
|
1444
|
-
if (docRef.docId && docRef.versionId) {
|
|
1445
|
-
translationPromises.push(
|
|
1446
|
-
this.$translation.put({
|
|
1447
|
-
...value,
|
|
1448
|
-
docRef: {
|
|
1449
|
-
docId: docRef.docId,
|
|
1450
|
-
versionId: docRef.versionId,
|
|
1451
|
-
},
|
|
1452
|
-
})
|
|
1453
|
-
)
|
|
1454
|
-
} else {
|
|
1455
|
-
throw new NotFoundError(
|
|
1456
|
-
`docRef for ${value.docRefType} with name ${name} not found`
|
|
1457
|
-
)
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
await Promise.all(translationPromises)
|
|
1461
|
-
|
|
1462
|
-
// close the zip handles after we know we won't be needing them anymore
|
|
1463
|
-
await config.close()
|
|
1464
|
-
const presetIds = [...presetNameToRef.values()].map((val) => val.docId)
|
|
1465
|
-
|
|
1466
|
-
await this.$setProjectSettings({
|
|
1467
|
-
defaultPresets: {
|
|
1468
|
-
point: presetIds,
|
|
1469
|
-
line: [],
|
|
1470
|
-
area: [],
|
|
1471
|
-
vertex: [],
|
|
1472
|
-
relation: [],
|
|
1473
|
-
},
|
|
1474
|
-
configMetadata: config.metadata,
|
|
1475
|
-
})
|
|
1476
|
-
|
|
1477
|
-
const deletePresetsPromise = Promise.all(
|
|
1478
|
-
presetsToDelete.map(async (docId) => {
|
|
1479
|
-
const { deleted } = await this.preset.getByDocId(docId)
|
|
1480
|
-
if (!deleted) await this.preset.delete(docId)
|
|
1481
|
-
})
|
|
1482
|
-
)
|
|
1483
|
-
const deleteFieldsPromise = Promise.all(
|
|
1484
|
-
fieldsToDelete.map(async (docId) => {
|
|
1485
|
-
const { deleted } = await this.field.getByDocId(docId)
|
|
1486
|
-
if (!deleted) await this.field.delete(docId)
|
|
1487
|
-
})
|
|
1488
|
-
)
|
|
1489
|
-
const deleteTranslationsPromise = Promise.all(
|
|
1490
|
-
[...translationsToDelete].map(async (docId) => {
|
|
1491
|
-
const { deleted } = await this.$translation.dataType.getByDocId(docId)
|
|
1492
|
-
if (!deleted) await this.$translation.dataType.delete(docId)
|
|
1493
|
-
})
|
|
1494
|
-
)
|
|
1495
|
-
await Promise.all([
|
|
1496
|
-
deletePresetsPromise,
|
|
1497
|
-
deleteFieldsPromise,
|
|
1498
|
-
deleteTranslationsPromise,
|
|
1499
|
-
])
|
|
1500
|
-
this.#loadingConfig = false
|
|
1501
|
-
return config.warnings
|
|
1384
|
+
await importCategories(this, { filePath, logger: this.#l })
|
|
1502
1385
|
} catch (e) {
|
|
1503
1386
|
this.#l.log('error loading config', e)
|
|
1504
|
-
|
|
1505
|
-
|
|
1387
|
+
throw e
|
|
1388
|
+
} finally {
|
|
1389
|
+
this.#importingCategories = false
|
|
1506
1390
|
}
|
|
1507
1391
|
}
|
|
1508
1392
|
}
|
|
1393
|
+
|
|
1509
1394
|
/**
|
|
1510
1395
|
* @param {import("@comapeo/schema").ProjectSettings & { forks: string[] }} projectDoc
|
|
1511
1396
|
* @returns {EditableProjectSettings}
|
|
@@ -1514,48 +1399,6 @@ function extractEditableProjectSettings(projectDoc) {
|
|
|
1514
1399
|
return omit(valueOf(projectDoc), ['schemaName'])
|
|
1515
1400
|
}
|
|
1516
1401
|
|
|
1517
|
-
/**
|
|
1518
|
-
@param {MapeoProject['field'] | MapeoProject['preset']} dataType
|
|
1519
|
-
@returns {Promise<String[]>}
|
|
1520
|
-
*/
|
|
1521
|
-
async function grabDocsToDelete(dataType) {
|
|
1522
|
-
const toDelete = []
|
|
1523
|
-
for (const { docId } of await dataType.getMany()) {
|
|
1524
|
-
toDelete.push(docId)
|
|
1525
|
-
}
|
|
1526
|
-
return toDelete
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
/**
|
|
1530
|
-
* @param {Object} opts
|
|
1531
|
-
* @param {Logger} opts.logger
|
|
1532
|
-
* @param {MapeoProject['$translation']['dataType']} opts.translation
|
|
1533
|
-
* @param {MapeoProject['preset']} opts.preset
|
|
1534
|
-
* @param {MapeoProject['field']} opts.field
|
|
1535
|
-
* @returns {Promise<Set<String>>}
|
|
1536
|
-
*/
|
|
1537
|
-
async function grabTranslationsToDelete(opts) {
|
|
1538
|
-
/** @type {Set<String>} */
|
|
1539
|
-
const toDelete = new Set()
|
|
1540
|
-
const translations = await opts.translation.getMany()
|
|
1541
|
-
await Promise.all(
|
|
1542
|
-
translations.map(async ({ docRefType, docRef, docId }) => {
|
|
1543
|
-
if (docRefType === 'field' || docRefType === 'preset') {
|
|
1544
|
-
let doc
|
|
1545
|
-
try {
|
|
1546
|
-
doc = await opts[docRefType].getByVersionId(docRef.versionId)
|
|
1547
|
-
} catch (e) {
|
|
1548
|
-
opts.logger.log(`referred ${docRef.versionId} is not found`)
|
|
1549
|
-
}
|
|
1550
|
-
if (doc) {
|
|
1551
|
-
toDelete.add(docId)
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
})
|
|
1555
|
-
)
|
|
1556
|
-
return toDelete
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
1402
|
/**
|
|
1560
1403
|
* Return a map of namespace -> core keypair
|
|
1561
1404
|
*
|
package/src/roles.js
CHANGED
package/src/schema/client.js
CHANGED
|
@@ -3,55 +3,49 @@
|
|
|
3
3
|
// device
|
|
4
4
|
import { blob, sqliteTable, text, int } from 'drizzle-orm/sqlite-core'
|
|
5
5
|
import { dereferencedDocSchemas as schemas } from '@comapeo/schema'
|
|
6
|
-
import {
|
|
7
|
-
|
|
6
|
+
import {
|
|
7
|
+
comapeoSchemaToDrizzleTable as toDrizzle,
|
|
8
|
+
backlinkTable,
|
|
9
|
+
} from './comapeo-to-drizzle.js'
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* @import { ProjectSettings } from '@comapeo/schema'
|
|
13
|
+
* @import { $Type } from 'drizzle-orm'
|
|
14
|
+
* @import { SQLiteTextJsonBuilder } from 'drizzle-orm/sqlite-core'
|
|
11
15
|
*
|
|
12
16
|
* @internal
|
|
13
|
-
* @typedef {Pick<ProjectSettings, 'name' | 'projectColor' | 'projectDescription'>} ProjectInfo
|
|
17
|
+
* @typedef {Pick<ProjectSettings, 'name' | 'projectColor' | 'projectDescription' | 'sendStats'>} ProjectInfo
|
|
14
18
|
*/
|
|
15
19
|
|
|
16
|
-
const projectInfoColumn =
|
|
17
|
-
/** @type {ReturnType<typeof import('drizzle-orm/sqlite-core').customType<{data: ProjectInfo}>>} */ (
|
|
18
|
-
customJson
|
|
19
|
-
)
|
|
20
|
-
|
|
21
20
|
/** @type {ProjectInfo} */
|
|
22
|
-
const PROJECT_INFO_DEFAULT_VALUE = {}
|
|
21
|
+
const PROJECT_INFO_DEFAULT_VALUE = { sendStats: false }
|
|
23
22
|
|
|
24
|
-
export const projectSettingsTable =
|
|
25
|
-
|
|
26
|
-
toColumns(schemas.projectSettings)
|
|
27
|
-
)
|
|
28
|
-
export const projectBacklinkTable = backlinkTable(projectSettingsTable)
|
|
23
|
+
export const projectSettingsTable = toDrizzle(schemas.projectSettings)
|
|
24
|
+
export const projectBacklinkTable = backlinkTable('projectSettings')
|
|
29
25
|
export const projectKeysTable = sqliteTable('projectKeys', {
|
|
30
26
|
projectId: text('projectId').notNull().primaryKey(),
|
|
31
27
|
projectPublicId: text('projectPublicId').notNull(),
|
|
32
|
-
projectInviteId: blob('projectInviteId').notNull(),
|
|
28
|
+
projectInviteId: blob('projectInviteId', { mode: 'buffer' }).notNull(),
|
|
33
29
|
keysCipher: blob('keysCipher', { mode: 'buffer' }).notNull(),
|
|
34
|
-
projectInfo:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
.notNull()
|
|
30
|
+
projectInfo:
|
|
31
|
+
/** @type {$Type<SQLiteTextJsonBuilder, ProjectInfo>} */
|
|
32
|
+
(text('projectInfo', { mode: 'json' }))
|
|
33
|
+
.default(PROJECT_INFO_DEFAULT_VALUE)
|
|
34
|
+
.notNull(),
|
|
35
|
+
hasLeftProject: int('hasLeftProject', { mode: 'boolean' })
|
|
36
|
+
.notNull()
|
|
37
|
+
.default(false),
|
|
41
38
|
})
|
|
42
39
|
|
|
43
40
|
/**
|
|
44
41
|
* @typedef {Omit<import('@comapeo/schema').DeviceInfoValue, 'schemaName'>} DeviceInfoParam
|
|
45
42
|
*/
|
|
46
43
|
|
|
47
|
-
const deviceInfoColumn =
|
|
48
|
-
/** @type {ReturnType<typeof import('drizzle-orm/sqlite-core').customType<{data: DeviceInfoParam }>>} */ (
|
|
49
|
-
customJson
|
|
50
|
-
)
|
|
51
|
-
|
|
52
44
|
// This table only ever has one row in it.
|
|
53
45
|
export const deviceSettingsTable = sqliteTable('deviceSettings', {
|
|
54
46
|
deviceId: text('deviceId').notNull().unique(),
|
|
55
|
-
deviceInfo:
|
|
47
|
+
deviceInfo:
|
|
48
|
+
/** @type {$Type<SQLiteTextJsonBuilder, DeviceInfoParam>} */
|
|
49
|
+
(text('deviceInfo', { mode: 'json' })),
|
|
56
50
|
isArchiveDevice: int('isArchiveDevice', { mode: 'boolean' }),
|
|
57
51
|
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { text, sqliteTable } from 'drizzle-orm/sqlite-core'
|
|
2
|
+
import { jsonSchemaToDrizzleSqliteTable } from './json-schema-to-drizzle.js'
|
|
3
|
+
|
|
4
|
+
export const BACKLINK_TABLE_POSTFIX = '_backlink'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @import { JsonSchemaToDrizzleSqliteTable } from './types.js'
|
|
8
|
+
* @import { SQLiteTextJsonBuilder } from 'drizzle-orm/sqlite-core'
|
|
9
|
+
* @import { $Type } from 'drizzle-orm'
|
|
10
|
+
* @import { MapeoDocMap } from '../types.js'
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {typeof import('@comapeo/schema').dereferencedDocSchemas} ComapeoSchemaMap
|
|
15
|
+
* @typedef {{ forks: $Type<SQLiteTextJsonBuilder, string[]> }} AdditionalColumns
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @template {ComapeoSchemaMap[keyof ComapeoSchemaMap]} TSchema
|
|
20
|
+
* @param {TSchema} schema
|
|
21
|
+
* @returns {JsonSchemaToDrizzleSqliteTable<
|
|
22
|
+
* MapeoDocMap[TSchema['properties']['schemaName']['const']],
|
|
23
|
+
* TSchema,
|
|
24
|
+
* TSchema['properties']['schemaName']['const'],
|
|
25
|
+
* AdditionalColumns,
|
|
26
|
+
* 'docId'
|
|
27
|
+
* >}
|
|
28
|
+
*/
|
|
29
|
+
export function comapeoSchemaToDrizzleTable(schema) {
|
|
30
|
+
return jsonSchemaToDrizzleSqliteTable(
|
|
31
|
+
schema.properties.schemaName.const,
|
|
32
|
+
schema,
|
|
33
|
+
{
|
|
34
|
+
additionalColumns: { forks: text('forks', { mode: 'json' }).notNull() },
|
|
35
|
+
primaryKey: 'docId',
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Table for storing backlinks, used for indexing. There needs to be one for
|
|
42
|
+
* each indexed document type, with a specific name `<datatype>_backlink`
|
|
43
|
+
*
|
|
44
|
+
* @param {import('@comapeo/schema').MapeoDoc['schemaName']} schemaName
|
|
45
|
+
*/
|
|
46
|
+
export function backlinkTable(schemaName) {
|
|
47
|
+
return sqliteTable(getBacklinkTableName(schemaName), {
|
|
48
|
+
versionId: text('versionId').notNull().primaryKey(),
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {string} tableName
|
|
54
|
+
*/
|
|
55
|
+
export function getBacklinkTableName(tableName) {
|
|
56
|
+
return tableName + BACKLINK_TABLE_POSTFIX
|
|
57
|
+
}
|