@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
|
@@ -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
|
*/
|
package/src/schema/project.js
CHANGED
|
@@ -3,47 +3,34 @@
|
|
|
3
3
|
import { blob, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
|
4
4
|
import { dereferencedDocSchemas as schemas } from '@comapeo/schema'
|
|
5
5
|
import { NAMESPACES } from '../constants.js'
|
|
6
|
-
import {
|
|
7
|
-
|
|
6
|
+
import {
|
|
7
|
+
comapeoSchemaToDrizzleTable as toDrizzle,
|
|
8
|
+
backlinkTable,
|
|
9
|
+
} from './comapeo-to-drizzle.js'
|
|
8
10
|
|
|
9
|
-
export const translationTable =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
)
|
|
13
|
-
export const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
)
|
|
17
|
-
export const
|
|
18
|
-
export const
|
|
19
|
-
'remoteDetectionAlert',
|
|
20
|
-
toColumns(schemas.remoteDetectionAlert)
|
|
21
|
-
)
|
|
22
|
-
export const presetTable = sqliteTable('preset', toColumns(schemas.preset))
|
|
23
|
-
export const fieldTable = sqliteTable('field', toColumns(schemas.field))
|
|
24
|
-
export const coreOwnershipTable = sqliteTable(
|
|
25
|
-
'coreOwnership',
|
|
26
|
-
toColumns(schemas.coreOwnership)
|
|
27
|
-
)
|
|
28
|
-
export const roleTable = sqliteTable('role', toColumns(schemas.role))
|
|
29
|
-
export const deviceInfoTable = sqliteTable(
|
|
30
|
-
'deviceInfo',
|
|
31
|
-
toColumns(schemas.deviceInfo)
|
|
32
|
-
)
|
|
33
|
-
export const iconTable = sqliteTable('icon', toColumns(schemas.icon))
|
|
11
|
+
export const translationTable = toDrizzle(schemas.translation)
|
|
12
|
+
export const observationTable = toDrizzle(schemas.observation)
|
|
13
|
+
export const trackTable = toDrizzle(schemas.track)
|
|
14
|
+
export const remoteDetectionAlertTable = toDrizzle(schemas.remoteDetectionAlert)
|
|
15
|
+
export const presetTable = toDrizzle(schemas.preset)
|
|
16
|
+
export const fieldTable = toDrizzle(schemas.field)
|
|
17
|
+
export const coreOwnershipTable = toDrizzle(schemas.coreOwnership)
|
|
18
|
+
export const roleTable = toDrizzle(schemas.role)
|
|
19
|
+
export const deviceInfoTable = toDrizzle(schemas.deviceInfo)
|
|
20
|
+
export const iconTable = toDrizzle(schemas.icon)
|
|
34
21
|
|
|
35
|
-
export const translationBacklinkTable = backlinkTable(
|
|
36
|
-
export const observationBacklinkTable = backlinkTable(
|
|
37
|
-
export const trackBacklinkTable = backlinkTable(
|
|
22
|
+
export const translationBacklinkTable = backlinkTable('translation')
|
|
23
|
+
export const observationBacklinkTable = backlinkTable('observation')
|
|
24
|
+
export const trackBacklinkTable = backlinkTable('track')
|
|
38
25
|
export const remoteDetectionAlertBacklinkTable = backlinkTable(
|
|
39
|
-
|
|
26
|
+
'remoteDetectionAlert'
|
|
40
27
|
)
|
|
41
|
-
export const presetBacklinkTable = backlinkTable(
|
|
42
|
-
export const fieldBacklinkTable = backlinkTable(
|
|
43
|
-
export const coreOwnershipBacklinkTable = backlinkTable(
|
|
44
|
-
export const roleBacklinkTable = backlinkTable(
|
|
45
|
-
export const deviceInfoBacklinkTable = backlinkTable(
|
|
46
|
-
export const iconBacklinkTable = backlinkTable(
|
|
28
|
+
export const presetBacklinkTable = backlinkTable('preset')
|
|
29
|
+
export const fieldBacklinkTable = backlinkTable('field')
|
|
30
|
+
export const coreOwnershipBacklinkTable = backlinkTable('coreOwnership')
|
|
31
|
+
export const roleBacklinkTable = backlinkTable('role')
|
|
32
|
+
export const deviceInfoBacklinkTable = backlinkTable('deviceInfo')
|
|
33
|
+
export const iconBacklinkTable = backlinkTable('icon')
|
|
47
34
|
|
|
48
35
|
export const coresTable = sqliteTable('cores', {
|
|
49
36
|
publicKey: blob('publicKey', { mode: 'buffer' }).notNull(),
|
package/src/schema/types.ts
CHANGED
|
@@ -3,101 +3,86 @@ import {
|
|
|
3
3
|
JSONSchema7 as JSONSchema7Writable,
|
|
4
4
|
JSONSchema7Type,
|
|
5
5
|
} from 'json-schema'
|
|
6
|
+
import type {
|
|
7
|
+
SQLiteBooleanBuilder,
|
|
8
|
+
SQLiteIntegerBuilder,
|
|
9
|
+
SQLiteRealBuilder,
|
|
10
|
+
SQLiteTableWithColumns,
|
|
11
|
+
SQLiteTextBuilder,
|
|
12
|
+
SQLiteTextJsonBuilder,
|
|
13
|
+
} from 'drizzle-orm/sqlite-core'
|
|
14
|
+
import type {
|
|
15
|
+
$Type,
|
|
16
|
+
BuildColumns,
|
|
17
|
+
ColumnBuilderBase,
|
|
18
|
+
HasDefault,
|
|
19
|
+
IsPrimaryKey,
|
|
20
|
+
NotNull,
|
|
21
|
+
} from 'drizzle-orm'
|
|
6
22
|
|
|
7
|
-
/** Convert optional properties to nullable */
|
|
8
|
-
export type OptionalToNull<T extends {}> = {
|
|
9
|
-
[K in keyof T]-?: undefined extends T[K] ? T[K] | null : T[K]
|
|
10
|
-
}
|
|
11
23
|
/** Convert a readonly array/object to writeable */
|
|
12
24
|
type Writable<T> = { -readonly [P in keyof T]: T[P] }
|
|
13
|
-
/** Type returned by text(columnName, { enum: [] }) */
|
|
14
|
-
type TextBuilder<
|
|
15
|
-
TName extends string,
|
|
16
|
-
TEnum extends readonly [string, ...string[]],
|
|
17
|
-
TNotNull extends boolean,
|
|
18
|
-
THasDefault extends boolean
|
|
19
|
-
> = import('drizzle-orm/sqlite-core').SQLiteTextBuilder<{
|
|
20
|
-
name: TName
|
|
21
|
-
data: Writable<TEnum>[number]
|
|
22
|
-
driverParam: string
|
|
23
|
-
columnType: 'SQLiteText'
|
|
24
|
-
dataType: 'string'
|
|
25
|
-
enumValues: Writable<TEnum>
|
|
26
|
-
notNull: TNotNull
|
|
27
|
-
hasDefault: THasDefault
|
|
28
|
-
}>
|
|
29
|
-
|
|
30
|
-
/** Type returned by integer(columnName, { mode: 'boolean' }) */
|
|
31
|
-
type BooleanBuilder<
|
|
32
|
-
TName extends string,
|
|
33
|
-
TNotNull extends boolean,
|
|
34
|
-
THasDefault extends boolean
|
|
35
|
-
> = import('drizzle-orm/sqlite-core').SQLiteBooleanBuilder<{
|
|
36
|
-
name: TName
|
|
37
|
-
data: boolean
|
|
38
|
-
driverParam: number
|
|
39
|
-
columnType: 'SQLiteBoolean'
|
|
40
|
-
dataType: 'boolean'
|
|
41
|
-
notNull: TNotNull
|
|
42
|
-
hasDefault: THasDefault
|
|
43
|
-
enumValues: undefined
|
|
44
|
-
}>
|
|
45
|
-
|
|
46
|
-
/** Type returned by real(columnName) */
|
|
47
|
-
type RealBuilder<
|
|
48
|
-
TName extends string,
|
|
49
|
-
TNotNull extends boolean,
|
|
50
|
-
THasDefault extends boolean
|
|
51
|
-
> = import('drizzle-orm/sqlite-core').SQLiteRealBuilder<{
|
|
52
|
-
name: TName
|
|
53
|
-
data: number
|
|
54
|
-
driverParam: number
|
|
55
|
-
columnType: 'SQLiteReal'
|
|
56
|
-
dataType: 'number'
|
|
57
|
-
notNull: TNotNull
|
|
58
|
-
hasDefault: THasDefault
|
|
59
|
-
enumValues: undefined
|
|
60
|
-
}>
|
|
61
|
-
|
|
62
|
-
/** Type returned by integer(columnName) */
|
|
63
|
-
type IntegerBuilder<
|
|
64
|
-
TName extends string,
|
|
65
|
-
TNotNull extends boolean,
|
|
66
|
-
THasDefault extends boolean
|
|
67
|
-
> = import('drizzle-orm/sqlite-core').SQLiteIntegerBuilder<{
|
|
68
|
-
name: TName
|
|
69
|
-
data: number
|
|
70
|
-
driverParam: number
|
|
71
|
-
columnType: 'SQLiteInteger'
|
|
72
|
-
dataType: 'number'
|
|
73
|
-
notNull: TNotNull
|
|
74
|
-
hasDefault: THasDefault
|
|
75
|
-
enumValues: undefined
|
|
76
|
-
}>
|
|
77
|
-
|
|
78
|
-
/** Type returned by the `customJson` custom type */
|
|
79
|
-
type JsonBuilder<
|
|
80
|
-
TName extends string,
|
|
81
|
-
TData extends unknown,
|
|
82
|
-
TNotNull extends boolean,
|
|
83
|
-
THasDefault extends boolean
|
|
84
|
-
> = import('drizzle-orm/sqlite-core').SQLiteCustomColumnBuilder<{
|
|
85
|
-
name: TName
|
|
86
|
-
data: TData
|
|
87
|
-
dataType: 'custom'
|
|
88
|
-
driverParam: string
|
|
89
|
-
columnType: 'SQLiteCustomColumn'
|
|
90
|
-
notNull: TNotNull
|
|
91
|
-
hasDefault: THasDefault
|
|
92
|
-
enumValues: undefined
|
|
93
|
-
}>
|
|
94
25
|
|
|
95
26
|
export type JSONSchema7 = ReadonlyDeep<JSONSchema7Writable>
|
|
96
27
|
type JsonSchema7Properties = { readonly [K: string]: JSONSchema7 }
|
|
97
|
-
export type
|
|
28
|
+
export type JSONSchema7Object = Omit<JSONSchema7, 'properties' | 'type'> & {
|
|
29
|
+
readonly type: 'object'
|
|
98
30
|
readonly properties: JsonSchema7Properties
|
|
99
31
|
}
|
|
100
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Create a Drizzle SQLite table definition from a JSONSchema object. All
|
|
35
|
+
* top-level properties map to SQLite columns, with `required` properties marked
|
|
36
|
+
* as `NOT NULL`, and JSONSchema `default` will map to SQLite defaults.
|
|
37
|
+
*
|
|
38
|
+
* Any properties that are of type `object` or `array` in the JSONSchema will be
|
|
39
|
+
* mapped to a text field, which drizzle will parse and stringify. Types for
|
|
40
|
+
* `object` and `array` properties will be derived from `TObjectType`.
|
|
41
|
+
*/
|
|
42
|
+
export type JsonSchemaToDrizzleSqliteTable<
|
|
43
|
+
/** Typescript type for the object defined in the JSONSchema */
|
|
44
|
+
TObjectType extends { [K in keyof TSchema['properties']]?: any },
|
|
45
|
+
/** The JSONSchema object schema */
|
|
46
|
+
TSchema extends JSONSchema7Object,
|
|
47
|
+
/** Name of the table to create */
|
|
48
|
+
TTableName extends string,
|
|
49
|
+
/** Additional columns to add to the table definition (e.g. not defined in JSONSchema ) */
|
|
50
|
+
TColumnsMap extends Record<string, ColumnBuilderBase> = {},
|
|
51
|
+
/** Name of the property to use as primary key */
|
|
52
|
+
TPrimaryKey extends keyof TSchema['properties'] | undefined = undefined
|
|
53
|
+
> = SQLiteTableWithColumns<{
|
|
54
|
+
name: TTableName
|
|
55
|
+
schema: undefined
|
|
56
|
+
columns: BuildColumns<
|
|
57
|
+
TTableName,
|
|
58
|
+
JsonSchemaToDrizzleColumns<TObjectType, TSchema, TPrimaryKey> & TColumnsMap,
|
|
59
|
+
'sqlite'
|
|
60
|
+
>
|
|
61
|
+
dialect: 'sqlite'
|
|
62
|
+
}>
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Convert a JSONSchema Object Schema to a Drizzle Columns map (e.g. parameter
|
|
66
|
+
* for `sqliteTable()`). All top-level properties map to SQLite columns, with
|
|
67
|
+
* `required` properties marked as `NOT NULL`, and JSONSchema `default` will map
|
|
68
|
+
* to SQLite defaults.
|
|
69
|
+
*
|
|
70
|
+
* Any properties that are of type `object` or `array` in the JSONSchema will be
|
|
71
|
+
* mapped to a text field, which drizzle will parse and stringify. Types for
|
|
72
|
+
* `object` and `array` properties will be derived from `TObjectType`.
|
|
73
|
+
*/
|
|
74
|
+
type JsonSchemaToDrizzleColumns<
|
|
75
|
+
TObjectType extends { [K in keyof TSchema['properties']]?: any },
|
|
76
|
+
TSchema extends JSONSchema7Object,
|
|
77
|
+
TPrimaryKey extends keyof TSchema['properties'] | undefined = undefined
|
|
78
|
+
> = AddJSONSchemaDefaults<
|
|
79
|
+
TSchema,
|
|
80
|
+
AddJSONSchemaRequired<
|
|
81
|
+
TSchema,
|
|
82
|
+
SchemaToDrizzleColumnsBase<TSchema, TObjectType>
|
|
83
|
+
>
|
|
84
|
+
>
|
|
85
|
+
|
|
101
86
|
/** Get the type of a JSONSchema string: array of constants for an enum,
|
|
102
87
|
otherwise string[]. Strangeness is to convert it into the format expected by
|
|
103
88
|
drizzle, which results in the correct type for the field from SQLite */
|
|
@@ -111,13 +96,12 @@ type Enum<
|
|
|
111
96
|
: [string, ...string[]]
|
|
112
97
|
|
|
113
98
|
/** True if JSONSchema object has a default */
|
|
114
|
-
type
|
|
115
|
-
? true
|
|
116
|
-
: false
|
|
99
|
+
type HasJSONSchemaDefault<T extends JSONSchema7> =
|
|
100
|
+
T['default'] extends JSONSchema7Type ? true : false
|
|
117
101
|
|
|
118
102
|
/** True if JSONSchema value is required */
|
|
119
|
-
type
|
|
120
|
-
T extends
|
|
103
|
+
type IsJSONSchemaRequired<
|
|
104
|
+
T extends JSONSchema7Object,
|
|
121
105
|
U extends string,
|
|
122
106
|
V extends JSONSchema7['required'] = T['required']
|
|
123
107
|
> = V extends readonly any[] ? Includes<V, U> : false
|
|
@@ -131,23 +115,78 @@ type IsRequired<
|
|
|
131
115
|
* stringify. Types for parsed JSON will be derived from MapeoDoc types.
|
|
132
116
|
*/
|
|
133
117
|
export type SchemaToDrizzleColumns<
|
|
134
|
-
|
|
118
|
+
TSchema extends JSONSchema7Object,
|
|
119
|
+
/** This is the type matching the JSONSchema */
|
|
120
|
+
TObjectType extends { [K in keyof TSchema['properties']]?: any },
|
|
121
|
+
TPrimaryKey extends keyof TSchema['properties'] | undefined = undefined
|
|
122
|
+
> = AddPrimaryKey<
|
|
123
|
+
AddJSONSchemaDefaults<
|
|
124
|
+
TSchema,
|
|
125
|
+
AddJSONSchemaRequired<
|
|
126
|
+
TSchema,
|
|
127
|
+
SchemaToDrizzleColumnsBase<TSchema, TObjectType>
|
|
128
|
+
>
|
|
129
|
+
>,
|
|
130
|
+
TPrimaryKey
|
|
131
|
+
>
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Add `HasDefault` to columns if the JSONSchema has a default for that property
|
|
135
|
+
*/
|
|
136
|
+
type AddJSONSchemaDefaults<
|
|
137
|
+
TJSONSchema extends JSONSchema7Object,
|
|
138
|
+
TColumns extends Record<string, ColumnBuilderBase>,
|
|
139
|
+
U extends JsonSchema7Properties = TJSONSchema['properties']
|
|
140
|
+
> = {
|
|
141
|
+
[K in keyof TColumns]: K extends keyof U
|
|
142
|
+
? HasJSONSchemaDefault<U[K]> extends true
|
|
143
|
+
? HasDefault<TColumns[K]>
|
|
144
|
+
: TColumns[K]
|
|
145
|
+
: TColumns[K]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Mark columns as NotNull if they are required in the JSONSchema
|
|
150
|
+
*/
|
|
151
|
+
type AddJSONSchemaRequired<
|
|
152
|
+
TJSONSchema extends JSONSchema7Object,
|
|
153
|
+
TColumns extends Record<string, ColumnBuilderBase>
|
|
154
|
+
> = {
|
|
155
|
+
[K in keyof TColumns]: K extends string
|
|
156
|
+
? IsJSONSchemaRequired<TJSONSchema, K> extends true
|
|
157
|
+
? NotNull<TColumns[K]>
|
|
158
|
+
: TColumns[K]
|
|
159
|
+
: TColumns[K]
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
type AddPrimaryKey<
|
|
163
|
+
TColumns extends Record<string, ColumnBuilderBase>,
|
|
164
|
+
TKey extends keyof TColumns | undefined
|
|
165
|
+
> = TKey extends string
|
|
166
|
+
? Omit<TColumns, TKey> & { [K in TKey]: IsPrimaryKey<TColumns[TKey]> }
|
|
167
|
+
: TColumns
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Map JSONSchema object properties to Drizzle column types.
|
|
171
|
+
*/
|
|
172
|
+
type SchemaToDrizzleColumnsBase<
|
|
173
|
+
TSchema extends JSONSchema7Object,
|
|
135
174
|
TObjectType extends { [K in keyof U]?: any },
|
|
136
|
-
U extends JsonSchema7Properties =
|
|
175
|
+
U extends JsonSchema7Properties = TSchema['properties']
|
|
137
176
|
> = {
|
|
138
177
|
[K in keyof U]: K extends string
|
|
139
178
|
? U[K]['type'] extends 'string'
|
|
140
|
-
?
|
|
179
|
+
? SQLiteTextBuilder<Enum<U[K]>>
|
|
141
180
|
: U[K]['type'] extends 'boolean'
|
|
142
|
-
?
|
|
181
|
+
? SQLiteBooleanBuilder
|
|
143
182
|
: U[K]['type'] extends 'number'
|
|
144
|
-
?
|
|
183
|
+
? SQLiteRealBuilder
|
|
145
184
|
: U[K]['type'] extends 'integer'
|
|
146
|
-
?
|
|
185
|
+
? SQLiteIntegerBuilder
|
|
147
186
|
: U[K]['type'] extends 'array' | 'object'
|
|
148
|
-
?
|
|
187
|
+
? $Type<SQLiteTextJsonBuilder, TObjectType[K]>
|
|
149
188
|
: never
|
|
150
189
|
: never
|
|
151
|
-
} & { forks:
|
|
190
|
+
} & { forks: $Type<SQLiteTextJsonBuilder, string[]> }
|
|
152
191
|
|
|
153
192
|
export type NonEmptyArray<T> = [T, ...T[]]
|
package/src/translation-api.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { and, sql } from 'drizzle-orm'
|
|
1
|
+
import { and, eq, inArray, sql } from 'drizzle-orm'
|
|
2
2
|
import { kCreateWithDocId, kSelect } from './datatype/index.js'
|
|
3
3
|
import { deNullify, hashObject } from './utils.js'
|
|
4
4
|
import { nullIfNotFound } from './errors.js'
|
|
5
5
|
import { omit } from './lib/omit.js'
|
|
6
|
-
|
|
6
|
+
import { iso6391To6393, iso6393To6391 } from './intl/iso639.js'
|
|
7
|
+
import { translationTable } from './schema/project.js'
|
|
8
|
+
/** @import { MapeoDoc, Translation, TranslationValue } from '@comapeo/schema' */
|
|
7
9
|
/** @import { SetOptional } from 'type-fest' */
|
|
8
10
|
|
|
9
11
|
export const ktranslatedLanguageCodeToSchemaNames = Symbol(
|
|
@@ -14,6 +16,9 @@ export default class TranslationApi {
|
|
|
14
16
|
* TranslationValue['languageCode'],
|
|
15
17
|
* Set<import('@comapeo/schema/dist/types.js').SchemaName>>} */
|
|
16
18
|
#translatedLanguageCodeToSchemaNames = new Map()
|
|
19
|
+
// A bug in previous versions meant that translations were stored with ISO
|
|
20
|
+
// 639-1 codes, so we need to handle backwards compatibility for that case.
|
|
21
|
+
#hasLegacyIso6391Translations = false
|
|
17
22
|
#dataType
|
|
18
23
|
#indexPromise
|
|
19
24
|
|
|
@@ -35,7 +40,7 @@ export default class TranslationApi {
|
|
|
35
40
|
docs.map((doc) => this.index(doc))
|
|
36
41
|
})
|
|
37
42
|
.catch((err) => {
|
|
38
|
-
|
|
43
|
+
console.error(`error loading Translation cache: ${err}`)
|
|
39
44
|
})
|
|
40
45
|
}
|
|
41
46
|
|
|
@@ -69,19 +74,48 @@ export default class TranslationApi {
|
|
|
69
74
|
async get(value) {
|
|
70
75
|
await this.ready()
|
|
71
76
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
// Allow this API to accept both ISO 639-1 and ISO 639-3 codes for languageCode
|
|
78
|
+
const normalizedLanguageCode =
|
|
79
|
+
value.languageCode.length === 2
|
|
80
|
+
? iso6391To6393.get(value.languageCode)
|
|
81
|
+
: value.languageCode
|
|
82
|
+
if (!normalizedLanguageCode) return [] // invalid language code
|
|
83
|
+
|
|
84
|
+
const languageCodesToQuery = [normalizedLanguageCode]
|
|
85
|
+
|
|
86
|
+
// A bug in previous versions meant that translations could be stored with
|
|
87
|
+
// ISO 639-1 codes, so we need to query for both in this case by looking up
|
|
88
|
+
// the ISO 639-1 code for the langauge, and then checking whether there are
|
|
89
|
+
// translations for that language (looking up in our in-memory index saves
|
|
90
|
+
// an extra sqlite query when unnecessary)
|
|
91
|
+
if (this.#hasLegacyIso6391Translations) {
|
|
92
|
+
const iso6391LanguageCode =
|
|
93
|
+
value.languageCode.length === 2
|
|
94
|
+
? value.languageCode
|
|
95
|
+
: iso6393To6391.get(value.languageCode)
|
|
96
|
+
const isTranslationStoredWithIso6391Code =
|
|
97
|
+
iso6391LanguageCode &&
|
|
98
|
+
this.#isTranslated(
|
|
99
|
+
/** @type {MapeoDoc['schemaName']} */
|
|
100
|
+
(value.docRefType),
|
|
101
|
+
iso6391LanguageCode
|
|
79
102
|
)
|
|
103
|
+
if (isTranslationStoredWithIso6391Code) {
|
|
104
|
+
languageCodesToQuery.push(iso6391LanguageCode)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const docTypeIsTranslatedToLanguage =
|
|
109
|
+
this.#isTranslated(
|
|
110
|
+
/** @type {MapeoDoc['schemaName']} */
|
|
111
|
+
(value.docRefType),
|
|
112
|
+
normalizedLanguageCode
|
|
113
|
+
) || languageCodesToQuery.length > 1
|
|
80
114
|
if (!docTypeIsTranslatedToLanguage) return []
|
|
81
115
|
|
|
82
116
|
const filters = [
|
|
83
|
-
|
|
84
|
-
|
|
117
|
+
eq(translationTable.docRefType, value.docRefType),
|
|
118
|
+
inArray(translationTable.languageCode, languageCodesToQuery),
|
|
85
119
|
sql`json_extract(docRef, '$.docId') = ${value.docRef.docId}`,
|
|
86
120
|
]
|
|
87
121
|
|
|
@@ -94,7 +128,9 @@ export default class TranslationApi {
|
|
|
94
128
|
filters.push(sql`propertyRef = ${value.propertyRef}`)
|
|
95
129
|
}
|
|
96
130
|
if (value.regionCode) {
|
|
97
|
-
|
|
131
|
+
// Use COLLATE NOCASE for case-insensitive matching because in previous
|
|
132
|
+
// versions we did not normalize regionCode to uppercase.
|
|
133
|
+
filters.push(sql`regionCode = ${value.regionCode} COLLATE NOCASE`)
|
|
98
134
|
}
|
|
99
135
|
|
|
100
136
|
return (await this.#dataType[kSelect]())
|
|
@@ -118,6 +154,9 @@ export default class TranslationApi {
|
|
|
118
154
|
translatedSchemas
|
|
119
155
|
)
|
|
120
156
|
}
|
|
157
|
+
if (doc.languageCode.length === 2) {
|
|
158
|
+
this.#hasLegacyIso6391Translations = true
|
|
159
|
+
}
|
|
121
160
|
translatedSchemas.add(
|
|
122
161
|
/** @type {import('@comapeo/schema/dist/types.js').SchemaName} */ (
|
|
123
162
|
doc.docRefType
|
|
@@ -125,6 +164,19 @@ export default class TranslationApi {
|
|
|
125
164
|
)
|
|
126
165
|
}
|
|
127
166
|
|
|
167
|
+
/**
|
|
168
|
+
* @param {MapeoDoc['schemaName']} docType
|
|
169
|
+
* @param {string} languageCode
|
|
170
|
+
* @returns {boolean}
|
|
171
|
+
*/
|
|
172
|
+
#isTranslated(docType, languageCode) {
|
|
173
|
+
return (
|
|
174
|
+
this.#translatedLanguageCodeToSchemaNames
|
|
175
|
+
.get(languageCode)
|
|
176
|
+
?.has(docType) || false
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
128
180
|
// This should only be used by tests.
|
|
129
181
|
get [ktranslatedLanguageCodeToSchemaNames]() {
|
|
130
182
|
return this.#translatedLanguageCodeToSchemaNames
|
package/src/types.ts
CHANGED
|
@@ -74,25 +74,6 @@ export type CoreOwnershipWithSignaturesValue = Omit<
|
|
|
74
74
|
Exclude<keyof MapeoCommon, 'schemaName'>
|
|
75
75
|
>
|
|
76
76
|
|
|
77
|
-
type NullToOptional<T> = SetOptional<T, NullKeys<T>>
|
|
78
|
-
type RemoveNull<T> = {
|
|
79
|
-
[K in keyof T]: Exclude<T[K], null>
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
type NullKeys<Base> = NonNullable<
|
|
83
|
-
// Wrap in `NonNullable` to strip away the `undefined` type from the produced union.
|
|
84
|
-
{
|
|
85
|
-
// Map through all the keys of the given base type.
|
|
86
|
-
[Key in keyof Base]: null extends Base[Key] // Pick only keys with types extending the given `Condition` type.
|
|
87
|
-
? // Retain this key since the condition passes.
|
|
88
|
-
Key
|
|
89
|
-
: // Discard this key since the condition fails.
|
|
90
|
-
never
|
|
91
|
-
|
|
92
|
-
// Convert the produced object into a union type of the keys which passed the conditional test.
|
|
93
|
-
}[keyof Base]
|
|
94
|
-
>
|
|
95
|
-
|
|
96
77
|
/**
|
|
97
78
|
* Replace an object's `Buffer` values with `string`s. Useful for serialization.
|
|
98
79
|
*/
|
|
@@ -106,7 +87,17 @@ export type MapBuffers<T> = {
|
|
|
106
87
|
* top-level optional props set to `null`) to the original types in
|
|
107
88
|
* @comapeo/schema
|
|
108
89
|
*/
|
|
109
|
-
export type NullableToOptional<T> = Simplify<
|
|
90
|
+
export type NullableToOptional<T> = Simplify<
|
|
91
|
+
{
|
|
92
|
+
[K in keyof T as null extends T[K] ? K : never]?: Exclude<T[K], null>
|
|
93
|
+
} & {
|
|
94
|
+
[K in keyof T as null extends T[K] ? never : K]: T[K]
|
|
95
|
+
}
|
|
96
|
+
>
|
|
97
|
+
export type OptionalToNullable<T> = Simplify<{
|
|
98
|
+
[K in keyof T]-?: T[K] | (undefined extends T[K] ? null : never)
|
|
99
|
+
}>
|
|
100
|
+
|
|
110
101
|
export type KeyPair = {
|
|
111
102
|
publicKey: PublicKey
|
|
112
103
|
secretKey: SecretKey
|
package/src/utils.js
CHANGED
|
@@ -87,7 +87,6 @@ export function isDefined(value) {
|
|
|
87
87
|
* @param {T} obj
|
|
88
88
|
* @returns {import('./types.js').NullableToOptional<T>}
|
|
89
89
|
*/
|
|
90
|
-
|
|
91
90
|
export function deNullify(obj) {
|
|
92
91
|
/** @type {Record<string, any>} */
|
|
93
92
|
const objNoNulls = {}
|
|
@@ -98,9 +97,29 @@ export function deNullify(obj) {
|
|
|
98
97
|
}
|
|
99
98
|
|
|
100
99
|
/**
|
|
101
|
-
*
|
|
100
|
+
* __Mutating__
|
|
101
|
+
* When reading from SQLite, any optional properties are set to `null`. This
|
|
102
|
+
* converts `null` back to `undefined` to match the input types (e.g. the types
|
|
103
|
+
* defined in @comapeo/schema)
|
|
104
|
+
* @template {{}} T
|
|
105
|
+
* @param {T} obj
|
|
106
|
+
* @returns {import('./types.js').NullableToOptional<T>}
|
|
107
|
+
*/
|
|
108
|
+
export function mutatingDeNullify(obj) {
|
|
109
|
+
for (const key of Object.keys(obj)) {
|
|
110
|
+
// @ts-expect-error
|
|
111
|
+
if (obj[key] === null) {
|
|
112
|
+
// @ts-expect-error
|
|
113
|
+
obj[key] = undefined
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return /** @type {import('./types.js').NullableToOptional<T>} */ (obj)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @template {import('@comapeo/schema').MapeoDoc & { forks?: string[], createdBy?: string, updatedBy?: string }} T
|
|
102
121
|
* @param {T} doc
|
|
103
|
-
* @returns {Omit<T, 'docId' | 'versionId' | 'originalVersionId' | 'links' | 'forks' | 'createdAt' | 'updatedAt' | 'deleted'>}
|
|
122
|
+
* @returns {Omit<T, 'docId' | 'versionId' | 'originalVersionId' | 'links' | 'forks' | 'createdAt' | 'updatedAt' | 'createdBy' | 'updatedBy' | 'deleted' >}
|
|
104
123
|
*/
|
|
105
124
|
export function valueOf(doc) {
|
|
106
125
|
return omit(doc, [
|
|
@@ -111,6 +130,8 @@ export function valueOf(doc) {
|
|
|
111
130
|
'forks',
|
|
112
131
|
'createdAt',
|
|
113
132
|
'updatedAt',
|
|
133
|
+
'createdBy',
|
|
134
|
+
'updatedBy',
|
|
114
135
|
'deleted',
|
|
115
136
|
])
|
|
116
137
|
}
|
|
@@ -219,3 +240,16 @@ export function buildBlobId(attachment, requestedVariant) {
|
|
|
219
240
|
driveId: attachment.driveDiscoveryId,
|
|
220
241
|
}
|
|
221
242
|
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get typed entries from an object. Use this only on objects that you are
|
|
246
|
+
* certain have no extra properties - TS does not check for extra properties on
|
|
247
|
+
* an object, which is why Object.entries is untyped by default.
|
|
248
|
+
*
|
|
249
|
+
* @template {Record<string, unknown>} T
|
|
250
|
+
* @param {T} obj - The object to get entries from (must _not_ have extra properties)
|
|
251
|
+
* @returns {import('type-fest').Entries<T>}
|
|
252
|
+
*/
|
|
253
|
+
export function typedEntries(obj) {
|
|
254
|
+
return /** @type {import('type-fest').Entries<T>} */ (Object.entries(obj))
|
|
255
|
+
}
|