@comapeo/core 4.4.0 → 5.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-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/datatype/index.d.ts +1 -1
- 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 +4968 -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 +111 -189
- package/dist/translation-api.d.ts.map +1 -1
- package/dist/utils.d.ts +10 -0
- 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 +16 -8
- package/src/constants.js +0 -3
- package/src/datatype/index.js +8 -5
- package/src/discovery/local-discovery.js +3 -2
- package/src/import-categories.js +364 -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 +56 -218
- 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 +64 -12
- package/src/utils.js +13 -0
- 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
|
|
@@ -679,7 +689,12 @@ export class MapeoProject extends TypedEmitter {
|
|
|
679
689
|
await this.#dataTypes.projectSettings.getByDocId(this.#projectId)
|
|
680
690
|
)
|
|
681
691
|
} catch (e) {
|
|
682
|
-
|
|
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
|
|
683
698
|
}
|
|
684
699
|
}
|
|
685
700
|
|
|
@@ -688,11 +703,9 @@ export class MapeoProject extends TypedEmitter {
|
|
|
688
703
|
*/
|
|
689
704
|
async $hasSyncedProjectSettings() {
|
|
690
705
|
try {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
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
|
|
696
709
|
} catch (e) {
|
|
697
710
|
return false
|
|
698
711
|
}
|
|
@@ -1302,19 +1315,14 @@ export class MapeoProject extends TypedEmitter {
|
|
|
1302
1315
|
|
|
1303
1316
|
await this.#roles.assignRole(this.#deviceId, LEFT_ROLE_ID)
|
|
1304
1317
|
|
|
1305
|
-
await this[
|
|
1318
|
+
await this[kClearData]()
|
|
1306
1319
|
}
|
|
1307
1320
|
|
|
1308
1321
|
/**
|
|
1309
|
-
* Clear data
|
|
1322
|
+
* Clear synced data, but keep auth data and own data
|
|
1310
1323
|
* @returns {Promise<void>}
|
|
1311
1324
|
*/
|
|
1312
|
-
async [
|
|
1313
|
-
const role = await this.$getOwnRole()
|
|
1314
|
-
if (role.roleId !== LEFT_ROLE_ID) {
|
|
1315
|
-
return
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1325
|
+
async [kClearData]() {
|
|
1318
1326
|
const namespacesWithoutAuth =
|
|
1319
1327
|
/** @satisfies {Exclude<Namespace, 'auth'>[]} */ ([
|
|
1320
1328
|
'config',
|
|
@@ -1345,172 +1353,44 @@ export class MapeoProject extends TypedEmitter {
|
|
|
1345
1353
|
}
|
|
1346
1354
|
}
|
|
1347
1355
|
|
|
1348
|
-
/**
|
|
1349
|
-
*
|
|
1350
|
-
*
|
|
1356
|
+
/**
|
|
1357
|
+
* @deprecated
|
|
1358
|
+
* @param {object} opts
|
|
1359
|
+
* @param {string} opts.configPath
|
|
1360
|
+
* @returns {Promise<Error[]>}
|
|
1351
1361
|
*/
|
|
1352
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 }) {
|
|
1353
1377
|
assert(
|
|
1354
|
-
!this.#
|
|
1355
|
-
'Cannot run multiple
|
|
1378
|
+
!this.#importingCategories,
|
|
1379
|
+
'Cannot run multiple category imports at the same time'
|
|
1356
1380
|
)
|
|
1357
|
-
this.#
|
|
1381
|
+
this.#importingCategories = true
|
|
1358
1382
|
|
|
1359
1383
|
try {
|
|
1360
|
-
|
|
1361
|
-
const presetsToDelete = await grabDocsToDelete(this.preset)
|
|
1362
|
-
const fieldsToDelete = await grabDocsToDelete(this.field)
|
|
1363
|
-
// delete only translations that refer to deleted fields and presets
|
|
1364
|
-
const translationsToDelete = await grabTranslationsToDelete({
|
|
1365
|
-
logger: this.#l,
|
|
1366
|
-
translation: this.$translation.dataType,
|
|
1367
|
-
preset: this.preset,
|
|
1368
|
-
field: this.field,
|
|
1369
|
-
})
|
|
1370
|
-
|
|
1371
|
-
const config = await readConfig(configPath)
|
|
1372
|
-
/** @type {Map<string, import('./icon-api.js').IconRef>} */
|
|
1373
|
-
const iconNameToRef = new Map()
|
|
1374
|
-
/** @type {Map<string, import('@comapeo/schema').PresetValue['fieldRefs'][1]>} */
|
|
1375
|
-
const fieldNameToRef = new Map()
|
|
1376
|
-
/** @type {Map<string,import('@comapeo/schema').TranslationValue['docRef']>} */
|
|
1377
|
-
const presetNameToRef = new Map()
|
|
1378
|
-
|
|
1379
|
-
// Do this in serial not parallel to avoid memory issues (avoid keeping all icon buffers in memory)
|
|
1380
|
-
for await (const icon of config.icons()) {
|
|
1381
|
-
const { docId, versionId } = await this.#iconApi.create(icon)
|
|
1382
|
-
iconNameToRef.set(icon.name, { docId, versionId })
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
// Ok to create fields and presets in parallel
|
|
1386
|
-
const fieldPromises = []
|
|
1387
|
-
for (const { name, value } of config.fields()) {
|
|
1388
|
-
fieldPromises.push(
|
|
1389
|
-
this.#dataTypes.field.create(value).then(({ docId, versionId }) => {
|
|
1390
|
-
fieldNameToRef.set(name, { docId, versionId })
|
|
1391
|
-
})
|
|
1392
|
-
)
|
|
1393
|
-
}
|
|
1394
|
-
await Promise.all(fieldPromises)
|
|
1395
|
-
|
|
1396
|
-
const presetsWithRefs = []
|
|
1397
|
-
for (const { fieldNames, iconName, value, name } of config.presets()) {
|
|
1398
|
-
const fieldRefs = fieldNames.map((fieldName) => {
|
|
1399
|
-
const fieldRef = fieldNameToRef.get(fieldName)
|
|
1400
|
-
if (!fieldRef) {
|
|
1401
|
-
throw new NotFoundError(
|
|
1402
|
-
`field ${fieldName} not found (referenced by preset ${value.name})})`
|
|
1403
|
-
)
|
|
1404
|
-
}
|
|
1405
|
-
return fieldRef
|
|
1406
|
-
})
|
|
1407
|
-
|
|
1408
|
-
if (!iconName) {
|
|
1409
|
-
throw new Error(`preset ${value.name} is missing an icon name`)
|
|
1410
|
-
}
|
|
1411
|
-
const iconRef = iconNameToRef.get(iconName)
|
|
1412
|
-
if (!iconRef) {
|
|
1413
|
-
throw new NotFoundError(
|
|
1414
|
-
`icon ${iconName} not found (referenced by preset ${value.name})`
|
|
1415
|
-
)
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
presetsWithRefs.push({
|
|
1419
|
-
preset: {
|
|
1420
|
-
...value,
|
|
1421
|
-
iconRef,
|
|
1422
|
-
fieldRefs,
|
|
1423
|
-
},
|
|
1424
|
-
name,
|
|
1425
|
-
})
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
const presetPromises = []
|
|
1429
|
-
for (const { preset, name } of presetsWithRefs) {
|
|
1430
|
-
presetPromises.push(
|
|
1431
|
-
this.preset.create(preset).then(({ docId, versionId }) => {
|
|
1432
|
-
presetNameToRef.set(name, { docId, versionId })
|
|
1433
|
-
})
|
|
1434
|
-
)
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
await Promise.all(presetPromises)
|
|
1438
|
-
|
|
1439
|
-
const translationPromises = []
|
|
1440
|
-
for (const { name, value } of config.translations()) {
|
|
1441
|
-
let docRef
|
|
1442
|
-
if (value.docRefType === 'field') {
|
|
1443
|
-
docRef = { ...fieldNameToRef.get(name) }
|
|
1444
|
-
} else if (value.docRefType === 'preset') {
|
|
1445
|
-
docRef = { ...presetNameToRef.get(name) }
|
|
1446
|
-
} else {
|
|
1447
|
-
throw new Error(`invalid docRefType ${value.docRefType}`)
|
|
1448
|
-
}
|
|
1449
|
-
if (docRef.docId && docRef.versionId) {
|
|
1450
|
-
translationPromises.push(
|
|
1451
|
-
this.$translation.put({
|
|
1452
|
-
...value,
|
|
1453
|
-
docRef: {
|
|
1454
|
-
docId: docRef.docId,
|
|
1455
|
-
versionId: docRef.versionId,
|
|
1456
|
-
},
|
|
1457
|
-
})
|
|
1458
|
-
)
|
|
1459
|
-
} else {
|
|
1460
|
-
throw new NotFoundError(
|
|
1461
|
-
`docRef for ${value.docRefType} with name ${name} not found`
|
|
1462
|
-
)
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
await Promise.all(translationPromises)
|
|
1466
|
-
|
|
1467
|
-
// close the zip handles after we know we won't be needing them anymore
|
|
1468
|
-
await config.close()
|
|
1469
|
-
const presetIds = [...presetNameToRef.values()].map((val) => val.docId)
|
|
1470
|
-
|
|
1471
|
-
await this.$setProjectSettings({
|
|
1472
|
-
defaultPresets: {
|
|
1473
|
-
point: presetIds,
|
|
1474
|
-
line: [],
|
|
1475
|
-
area: [],
|
|
1476
|
-
vertex: [],
|
|
1477
|
-
relation: [],
|
|
1478
|
-
},
|
|
1479
|
-
configMetadata: config.metadata,
|
|
1480
|
-
})
|
|
1481
|
-
|
|
1482
|
-
const deletePresetsPromise = Promise.all(
|
|
1483
|
-
presetsToDelete.map(async (docId) => {
|
|
1484
|
-
const { deleted } = await this.preset.getByDocId(docId)
|
|
1485
|
-
if (!deleted) await this.preset.delete(docId)
|
|
1486
|
-
})
|
|
1487
|
-
)
|
|
1488
|
-
const deleteFieldsPromise = Promise.all(
|
|
1489
|
-
fieldsToDelete.map(async (docId) => {
|
|
1490
|
-
const { deleted } = await this.field.getByDocId(docId)
|
|
1491
|
-
if (!deleted) await this.field.delete(docId)
|
|
1492
|
-
})
|
|
1493
|
-
)
|
|
1494
|
-
const deleteTranslationsPromise = Promise.all(
|
|
1495
|
-
[...translationsToDelete].map(async (docId) => {
|
|
1496
|
-
const { deleted } = await this.$translation.dataType.getByDocId(docId)
|
|
1497
|
-
if (!deleted) await this.$translation.dataType.delete(docId)
|
|
1498
|
-
})
|
|
1499
|
-
)
|
|
1500
|
-
await Promise.all([
|
|
1501
|
-
deletePresetsPromise,
|
|
1502
|
-
deleteFieldsPromise,
|
|
1503
|
-
deleteTranslationsPromise,
|
|
1504
|
-
])
|
|
1505
|
-
this.#loadingConfig = false
|
|
1506
|
-
return config.warnings
|
|
1384
|
+
await importCategories(this, { filePath, logger: this.#l })
|
|
1507
1385
|
} catch (e) {
|
|
1508
1386
|
this.#l.log('error loading config', e)
|
|
1509
|
-
|
|
1510
|
-
|
|
1387
|
+
throw e
|
|
1388
|
+
} finally {
|
|
1389
|
+
this.#importingCategories = false
|
|
1511
1390
|
}
|
|
1512
1391
|
}
|
|
1513
1392
|
}
|
|
1393
|
+
|
|
1514
1394
|
/**
|
|
1515
1395
|
* @param {import("@comapeo/schema").ProjectSettings & { forks: string[] }} projectDoc
|
|
1516
1396
|
* @returns {EditableProjectSettings}
|
|
@@ -1519,48 +1399,6 @@ function extractEditableProjectSettings(projectDoc) {
|
|
|
1519
1399
|
return omit(valueOf(projectDoc), ['schemaName'])
|
|
1520
1400
|
}
|
|
1521
1401
|
|
|
1522
|
-
/**
|
|
1523
|
-
@param {MapeoProject['field'] | MapeoProject['preset']} dataType
|
|
1524
|
-
@returns {Promise<String[]>}
|
|
1525
|
-
*/
|
|
1526
|
-
async function grabDocsToDelete(dataType) {
|
|
1527
|
-
const toDelete = []
|
|
1528
|
-
for (const { docId } of await dataType.getMany()) {
|
|
1529
|
-
toDelete.push(docId)
|
|
1530
|
-
}
|
|
1531
|
-
return toDelete
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
/**
|
|
1535
|
-
* @param {Object} opts
|
|
1536
|
-
* @param {Logger} opts.logger
|
|
1537
|
-
* @param {MapeoProject['$translation']['dataType']} opts.translation
|
|
1538
|
-
* @param {MapeoProject['preset']} opts.preset
|
|
1539
|
-
* @param {MapeoProject['field']} opts.field
|
|
1540
|
-
* @returns {Promise<Set<String>>}
|
|
1541
|
-
*/
|
|
1542
|
-
async function grabTranslationsToDelete(opts) {
|
|
1543
|
-
/** @type {Set<String>} */
|
|
1544
|
-
const toDelete = new Set()
|
|
1545
|
-
const translations = await opts.translation.getMany()
|
|
1546
|
-
await Promise.all(
|
|
1547
|
-
translations.map(async ({ docRefType, docRef, docId }) => {
|
|
1548
|
-
if (docRefType === 'field' || docRefType === 'preset') {
|
|
1549
|
-
let doc
|
|
1550
|
-
try {
|
|
1551
|
-
doc = await opts[docRefType].getByVersionId(docRef.versionId)
|
|
1552
|
-
} catch (e) {
|
|
1553
|
-
opts.logger.log(`referred ${docRef.versionId} is not found`)
|
|
1554
|
-
}
|
|
1555
|
-
if (doc) {
|
|
1556
|
-
toDelete.add(docId)
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
})
|
|
1560
|
-
)
|
|
1561
|
-
return toDelete
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
1402
|
/**
|
|
1565
1403
|
* Return a map of namespace -> core keypair
|
|
1566
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
|
+
}
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { text, integer, real } from 'drizzle-orm/sqlite-core'
|
|
1
|
+
import { text, integer, real, sqliteTable } from 'drizzle-orm/sqlite-core'
|
|
2
2
|
import { ExhaustivenessError } from '../utils.js'
|
|
3
|
-
import { customJson } from './utils.js'
|
|
4
|
-
/** @import { MapeoDoc } from '@comapeo/schema' */
|
|
5
|
-
/** @import { MapeoDocMap } from '../types.js' */
|
|
6
3
|
|
|
7
4
|
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@
|
|
15
|
-
|
|
16
|
-
@
|
|
17
|
-
@
|
|
18
|
-
@
|
|
19
|
-
@returns {import('./types.js').SchemaToDrizzleColumns<TSchema, TObjectType>}
|
|
5
|
+
* @template {{ [ K in keyof TSchema['properties'] ]?: any }} TObjectType
|
|
6
|
+
* @template {import('./types.js').JSONSchema7Object} TSchema
|
|
7
|
+
* @template {string} TTableName
|
|
8
|
+
* @template {Record<string, import('drizzle-orm').ColumnBuilderBase>} TColumnsMap
|
|
9
|
+
* @template {keyof TSchema['properties']} TPrimaryKey
|
|
10
|
+
* @param {TTableName} tableName
|
|
11
|
+
* @param {TSchema} schema
|
|
12
|
+
* @param {object} [opts]
|
|
13
|
+
* @param {TColumnsMap} [opts.additionalColumns]
|
|
14
|
+
* @param {TPrimaryKey} [opts.primaryKey] - Column name to use as primary key, if not specified in schema
|
|
15
|
+
* @returns {import('./types.js').JsonSchemaToDrizzleSqliteTable<TObjectType, TSchema, TTableName, TColumnsMap, TPrimaryKey>}
|
|
20
16
|
*/
|
|
21
|
-
export function
|
|
17
|
+
export function jsonSchemaToDrizzleSqliteTable(
|
|
18
|
+
tableName,
|
|
19
|
+
schema,
|
|
20
|
+
{ additionalColumns, primaryKey } = {}
|
|
21
|
+
) {
|
|
22
22
|
if (schema.type !== 'object' || !schema.properties) {
|
|
23
23
|
throw new Error('Cannot process JSONSchema as SQL table')
|
|
24
24
|
}
|
|
@@ -46,14 +46,11 @@ export function jsonSchemaToDrizzleColumns(schema) {
|
|
|
46
46
|
? /** @type {[typeof value.const]} */ ([value.const])
|
|
47
47
|
: undefined
|
|
48
48
|
columns[key] = text(key, { enum: enumValue })
|
|
49
|
-
if (key === 'docId') {
|
|
50
|
-
columns[key] = columns[key].primaryKey()
|
|
51
|
-
}
|
|
52
49
|
break
|
|
53
50
|
}
|
|
54
51
|
case 'array':
|
|
55
52
|
case 'object':
|
|
56
|
-
columns[key] =
|
|
53
|
+
columns[key] = text(key, { mode: 'json' })
|
|
57
54
|
break
|
|
58
55
|
case 'null':
|
|
59
56
|
// Skip handling this right now
|
|
@@ -69,10 +66,13 @@ export function jsonSchemaToDrizzleColumns(schema) {
|
|
|
69
66
|
columns[key] = columns[key].default(defaultValue)
|
|
70
67
|
}
|
|
71
68
|
}
|
|
69
|
+
if (key === primaryKey) {
|
|
70
|
+
columns[key] = columns[key].primaryKey()
|
|
71
|
+
}
|
|
72
72
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
return /** @type {any} */ (
|
|
74
|
+
sqliteTable(tableName, { ...columns, ...additionalColumns })
|
|
75
|
+
)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/**
|
|
@@ -85,7 +85,7 @@ function getDefault(value) {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
* @param {import('./types.js').
|
|
88
|
+
* @param {import('./types.js').JSONSchema7Object} schema
|
|
89
89
|
* @param {string} key
|
|
90
90
|
* @returns {boolean}
|
|
91
91
|
*/
|