Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add lang option to DataType read operations #473

Merged
merged 46 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
e83160b
feat: Add `lang` option to DataType read operations
gmaclennan Feb 12, 2024
2f037a3
update typescript defs
gmaclennan Feb 12, 2024
e51c28e
add getMany support
gmaclennan Feb 12, 2024
7badd5d
initial implementation of `TranslationApi`
Mar 11, 2024
4fe87b8
* add @types/json-stable-strinfify
Mar 11, 2024
1b196f8
* remove `message` from object when hashing
Mar 12, 2024
76c3798
improve `get()` by ignoring not translated languages, improve typing
Mar 12, 2024
c8b68f0
* make `index` private, run it on `put`
Mar 12, 2024
377c057
revert `index` as private method, update tests
Mar 13, 2024
f37a1bc
Merge branch 'main' of github.com:digidem/mapeo-core-next into feat/t…
Mar 13, 2024
a3ac3bd
Merge branch 'main' of github.com:digidem/mapeo-core-next into feat/t…
Apr 3, 2024
040b856
update magic-bytes manually
Apr 3, 2024
458984b
add e2e/translation-api.js and expose translation api in mapeo project
Apr 4, 2024
f44d24f
* add translationTable to index writer
Apr 16, 2024
e69aaec
rever changes to `.index` method (better to accept doc than block)
Apr 16, 2024
4faf727
Merge branch 'main' of github.com:digidem/mapeo-core-next into feat/t…
Apr 16, 2024
325accc
first e2e tests
Apr 17, 2024
805c296
Merge branch 'main' of github.com:digidem/mapeo-core-next into feat/t…
Apr 17, 2024
7adfe0b
revert regionCode fallback (since its handled in an upper layer)
Apr 18, 2024
a77009b
Merge branch 'main' of github.com:digidem/mapeo-core-next into feat/t…
Apr 18, 2024
3bac196
use default config for translationApi e2e tests, test with a bunch of
Apr 18, 2024
2b30ed7
add translation fixtures
Apr 18, 2024
d6e99d1
add check of expected translation
Apr 18, 2024
aee74a2
improve test messages
Apr 18, 2024
693e42e
add assertion of matching preset docId with translation docIdRef
Apr 18, 2024
9b39afb
add tests and fixture for fields
Apr 18, 2024
bc44b8c
Merge branch 'main' of github.com:digidem/mapeo-core-next into feat/r…
Apr 22, 2024
de2c4ef
Merge branch 'feat/translationApi' of github.com:digidem/mapeo-core-n…
Apr 22, 2024
53b5222
Integrate actual translation-api in DataType
Apr 22, 2024
9621b89
Merge branch 'main' of github.com:digidem/mapeo-core-next into feat/r…
Apr 26, 2024
24f89c1
add type to translation
Apr 26, 2024
141ad21
Promise.all translation on getMany
Apr 29, 2024
dc6a7de
datatype clean, type translationApi in tests
Apr 29, 2024
fce62fc
add fallback when not matching `regionCode`, test that
May 2, 2024
1c8bd78
Merge branch 'main' of github.com:digidem/mapeo-core-next into feat/r…
May 2, 2024
8c45d22
re-add misterious dissapearing //ts-expect error on datatype
May 2, 2024
333ba41
Apply suggestions from code review
tomasciccola May 3, 2024
e2102d4
Merge branch 'main' of github.com:digidem/mapeo-core-next into feat/r…
May 7, 2024
ec14a35
revert removal of translation object clone in datatype
May 7, 2024
1dca4b1
check if `fieldRef` is string before setting the property
May 7, 2024
2743ed8
Fix "use before define" issue with DataType & TranslationApi (#604)
EvanHahn May 7, 2024
507685f
Merge branch 'feat/record-translations-opts-lang' of github.com:digid…
May 7, 2024
5c9ee75
add e2e tests
May 7, 2024
a51f709
add e2e tests for presets
May 7, 2024
1670644
chore: fix type errors in translation api e2e test (#619)
EvanHahn May 7, 2024
d4e06cf
Merge branch 'main' into feat/record-translations-opts-lang
EvanHahn May 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 75 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,14 @@
"@mapeo/sqlite-indexer": "1.0.0-alpha.8",
"@sinclair/typebox": "^0.29.6",
"b4a": "^1.6.3",
"bcp-47": "^2.1.0",
"better-sqlite3": "^8.7.0",
"big-sparse-array": "^1.0.3",
"bogon": "^1.1.0",
"compact-encoding": "^2.12.0",
"corestore": "^6.8.4",
"debug": "^4.3.4",
"dot-prop": "^8.0.2",
"drizzle-orm": "^0.30.8",
"fastify": ">= 4",
"fastify-plugin": "^4.5.1",
Expand Down
11 changes: 9 additions & 2 deletions src/datatype/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { SQLiteSelectBase } from 'drizzle-orm/sqlite-core'
import { RunResult } from 'better-sqlite3'
import type Hypercore from 'hypercore'
import { TypedEmitter } from 'tiny-typed-emitter'
import TranslationApi from '../translation-api.js'

type MapeoDocTableName = `${MapeoDoc['schemaName']}Table`
type GetMapeoDocTables<T> = T[keyof T & MapeoDocTableName]
Expand Down Expand Up @@ -52,11 +53,13 @@ export class DataType<
table,
getPermissions,
db,
getTranslations,
}: {
table: TTable
dataStore: TDataStore
db: import('drizzle-orm/better-sqlite3').BetterSQLite3Database
getPermissions?: () => any
getTranslations: TranslationApi['get']
})

get [kTable](): TTable
Expand All @@ -79,12 +82,16 @@ export class DataType<
>
>(value: T): Promise<TDoc & { forks: string[] }>

getByDocId(docId: string): Promise<TDoc & { forks: string[] }>
getByDocId(
docId: string,
opts?: { lang?: string }
): Promise<TDoc & { forks: string[] }>

getByVersionId(versionId: string): Promise<TDoc>
getByVersionId(versionId: string, opts?: { lang?: string }): Promise<TDoc>

getMany(opts?: {
includeDeleted?: boolean
lang?: string
}): Promise<Array<TDoc & { forks: string[] }>>

update<
Expand Down
72 changes: 62 additions & 10 deletions src/datatype/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { noop, deNullify } from '../utils.js'
import { NotFoundError } from '../errors.js'
import crypto from 'hypercore-crypto'
import { TypedEmitter } from 'tiny-typed-emitter'
import { parse as parseBCP47 } from 'bcp-47'
import { setProperty, getProperty } from 'dot-prop'

/**
* @typedef {import('@mapeo/schema').MapeoDoc} MapeoDoc
Expand Down Expand Up @@ -70,22 +72,25 @@ export class DataType extends TypedEmitter {
#schemaName
#sql
#db
#getTranslations

/**
*
* @param {object} opts
* @param {TTable} opts.table
* @param {TDataStore} opts.dataStore
* @param {import('drizzle-orm/better-sqlite3').BetterSQLite3Database} opts.db
* @param {import('../translation-api.js').default['get']} opts.getTranslations
* @param {() => any} [opts.getPermissions]
*/
constructor({ dataStore, table, getPermissions, db }) {
constructor({ dataStore, table, getPermissions, db, getTranslations }) {
super()
this.#dataStore = dataStore
this.#table = table
this.#schemaName = /** @type {TSchemaName} */ (getTableConfig(table).name)
this.#getPermissions = getPermissions
this.#db = db
this.#getTranslations = getTranslations
this.#sql = {
getByDocId: db
.select()
Expand Down Expand Up @@ -172,26 +177,73 @@ export class DataType extends TypedEmitter {

/**
* @param {string} docId
* @param {{ lang?: string }} [opts]
*/
async getByDocId(docId) {
async getByDocId(docId, { lang } = {}) {
await this.#dataStore.indexer.idle()
const result = this.#sql.getByDocId.get({ docId })
const result = /** @type {undefined | MapeoDoc} */ (
this.#sql.getByDocId.get({ docId })
)
if (!result) throw new NotFoundError()
return deNullify(result)
return this.#translate(deNullify(result), { lang })
}

/** @param {string} versionId */
async getByVersionId(versionId) {
return this.#dataStore.read(versionId)
/**
* @param {string} versionId
* @param {{ lang?: string }} [opts]
*/
async getByVersionId(versionId, { lang } = {}) {
const result = await this.#dataStore.read(versionId)
return this.#translate(result, { lang })
}

/** @param {{ includeDeleted?: boolean }} [opts] */
async getMany({ includeDeleted = false } = {}) {
/**
* @param {MapeoDoc} doc
* @param {{ lang?: string }} [opts]
*/
async #translate(doc, { lang } = {}) {
if (!lang) return doc

const { language, region } = parseBCP47(lang)
if (!language) return doc
const translatedDoc = JSON.parse(JSON.stringify(doc))

let value = {
languageCode: language,
schemaNameRef: translatedDoc.schemaName,
docIdRef: translatedDoc.docId,
regionCode: region !== null ? region : undefined,
}
let translations = await this.#getTranslations(value)
// if passing a region code returns no matches,
// fallback to matching only languageCode
if (translations.length === 0 && value.regionCode) {
value.regionCode = undefined
translations = await this.#getTranslations(value)
}

for (let translation of translations) {
if (typeof getProperty(doc, translation.fieldRef) === 'string') {
setProperty(doc, translation.fieldRef, translation.message)
}
}
return doc
}

/** @param {{ includeDeleted?: boolean, lang?: string }} [opts] */
async getMany({ includeDeleted = false, lang } = {}) {
await this.#dataStore.indexer.idle()
const rows = includeDeleted
? this.#sql.getManyWithDeleted.all()
: this.#sql.getMany.all()
return rows.map((doc) => deNullify(doc))
return await Promise.all(
rows.map(
async (doc) =>
await this.#translate(deNullify(/** @type {MapeoDoc} */ (doc)), {
lang,
})
)
)
}

/**
Expand Down
17 changes: 17 additions & 0 deletions src/mapeo-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export class MapeoProject extends TypedEmitter {
#memberApi
#iconApi
#syncApi
/** @type {TranslationApi} */
#translationApi
#l

Expand Down Expand Up @@ -177,6 +178,7 @@ export class MapeoProject extends TypedEmitter {
},
logger: this.#l,
})

this.#dataStores = {
auth: new DataStore({
coreManager: this.#coreManager,
Expand All @@ -201,56 +203,71 @@ export class MapeoProject extends TypedEmitter {
storage: indexerStorage,
}),
}

/** @type {typeof TranslationApi.prototype.get} */
const getTranslations = (...args) => this.$translation.get(...args)
this.#dataTypes = {
observation: new DataType({
dataStore: this.#dataStores.data,
table: observationTable,
db,
getTranslations,
}),
track: new DataType({
dataStore: this.#dataStores.data,
table: trackTable,
db,
getTranslations,
}),
preset: new DataType({
dataStore: this.#dataStores.config,
table: presetTable,
db,
getTranslations,
}),
field: new DataType({
dataStore: this.#dataStores.config,
table: fieldTable,
db,
getTranslations,
}),
projectSettings: new DataType({
dataStore: this.#dataStores.config,
table: projectSettingsTable,
db: sharedDb,
getTranslations,
}),
coreOwnership: new DataType({
dataStore: this.#dataStores.auth,
table: coreOwnershipTable,
db,
getTranslations,
}),
role: new DataType({
dataStore: this.#dataStores.auth,
table: roleTable,
db,
getTranslations,
}),
deviceInfo: new DataType({
dataStore: this.#dataStores.config,
table: deviceInfoTable,
db,
getTranslations,
}),
icon: new DataType({
dataStore: this.#dataStores.config,
table: iconTable,
db,
getTranslations,
}),
translation: new DataType({
dataStore: this.#dataStores.config,
table: translationTable,
db,
getTranslations: () => {
throw new Error('Cannot get translation for translations')
},
}),
}
const identityKeypair = keyManager.getIdentityKeypair()
Expand Down
Loading
Loading