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

revert: remove getByDocId optional parameter #968

Merged
merged 4 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
22 changes: 22 additions & 0 deletions src/datatype/get-if-exists.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { NotFoundError } from '../errors.js'
/** @import { DataStore } from '../datastore/index.js' */
/** @import { MapeoDocMap, MapeoValueMap } from '../types.js' */
/** @import { DataType, MapeoDocTables } from './index.js' */

/**
* @template {MapeoDocTables} TTable
* @template {TTable['_']['name']} TSchemaName
* @template {MapeoDocMap[TSchemaName]} TDoc
* @template {MapeoValueMap[TSchemaName]} TValue
* @param {DataType<DataStore, TTable, TSchemaName, TDoc, TValue>} dataType
* @param {string} docId
* @returns {Promise<null | TDoc & { forks: string[] }>}
*/
export async function getByDocIdIfExists(dataType, docId) {
try {
return await dataType.getByDocId(docId)
} catch (err) {
if (err instanceof NotFoundError) return null
throw err
}
}
8 changes: 2 additions & 6 deletions src/datatype/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import TranslationApi from '../translation-api.js'
type MapeoDocTableName = `${MapeoDoc['schemaName']}Table`
type GetMapeoDocTables<T> = T[keyof T & MapeoDocTableName]
/** Union of Drizzle schema tables that correspond to MapeoDoc types (e.g. excluding backlink tables and other utility tables) */
type MapeoDocTables =
export type MapeoDocTables =
| GetMapeoDocTables<typeof import('../schema/project.js')>
| GetMapeoDocTables<typeof import('../schema/client.js')>
type MapeoDocTablesMap = {
Expand Down Expand Up @@ -87,12 +87,8 @@ export class DataType<

getByDocId(
docId: string,
opts?: { mustBeFound?: true; lang?: string }
opts?: { lang?: string }
): Promise<TDoc & { forks: string[] }>
getByDocId(
docId: string,
opts?: { mustBeFound?: boolean; lang?: string }
): Promise<null | (TDoc & { forks: string[] })>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is part of the revert.


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

Expand Down
31 changes: 9 additions & 22 deletions src/datatype/index.js
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All changes to this file are just reversions.

Original file line number Diff line number Diff line change
Expand Up @@ -164,30 +164,16 @@ export class DataType extends TypedEmitter {
}

/**
* @overload
* @param {string} docId
* @param {object} [options]
* @param {true} [options.mustBeFound]
* @param {string} [options.lang]
* @returns {Promise<TDoc & { forks: string[] }>}
*/
/**
* @param {string} docId
* @param {object} [options]
* @param {boolean} [options.mustBeFound]
* @param {string} [options.lang]
* @returns {Promise<null | (TDoc & { forks: string[] })>}
* @param {{ lang?: string }} [opts]
*/
async getByDocId(docId, { mustBeFound = true, lang } = {}) {
async getByDocId(docId, { lang } = {}) {
await this.#dataStore.indexer.idle()
const result = this.#sql.getByDocId.get({ docId })
if (result) {
return this.#translate(deNullify(result), { lang })
} else if (mustBeFound) {
throw new NotFoundError()
} else {
return null
}
const result = /** @type {undefined | MapeoDoc} */ (
this.#sql.getByDocId.get({ docId })
)
if (!result) throw new NotFoundError()
return this.#translate(deNullify(result), { lang })
}

/**
Expand All @@ -200,7 +186,7 @@ export class DataType extends TypedEmitter {
}

/**
* @param {any} doc
* @param {MapeoDoc} doc
* @param {{ lang?: string }} [opts]
*/
async #translate(doc, { lang } = {}) {
Expand Down Expand Up @@ -292,6 +278,7 @@ export class DataType extends TypedEmitter {
const doc = {
...existingDoc,
updatedAt: new Date().toISOString(),
// @ts-expect-error - TS just doesn't work in this class
links: [existingDoc.versionId, ...existingDoc.forks],
deleted: true,
}
Expand Down
9 changes: 3 additions & 6 deletions src/mapeo-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import { IconApi } from './icon-api.js'
import { readConfig } from './config-import.js'
import TranslationApi from './translation-api.js'
import { NotFoundError } from './errors.js'
import { getByDocIdIfExists } from './datatype/get-if-exists.js'
/** @import { ProjectSettingsValue } from '@comapeo/schema' */
/** @import { CoreStorage, KeyPair, Namespace, ReplicationStream } from './types.js' */

Expand Down Expand Up @@ -600,9 +601,7 @@ export class MapeoProject extends TypedEmitter {
async $setProjectSettings(settings) {
const { projectSettings } = this.#dataTypes

const existing = await projectSettings.getByDocId(this.#projectId, {
mustBeFound: false,
})
const existing = await getByDocIdIfExists(projectSettings, this.#projectId)

if (existing) {
return extractEditableProjectSettings(
Expand Down Expand Up @@ -710,9 +709,7 @@ export class MapeoProject extends TypedEmitter {
schemaName: /** @type {const} */ ('deviceInfo'),
}

const existingDoc = await deviceInfo.getByDocId(configCoreId, {
mustBeFound: false,
})
const existingDoc = await getByDocIdIfExists(deviceInfo, configCoreId)
if (existingDoc) {
return await deviceInfo.update(existingDoc.versionId, doc)
} else {
Expand Down
13 changes: 5 additions & 8 deletions src/roles.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { currentSchemaVersions } from '@comapeo/schema'
import mapObject from 'map-obj'
import { kCreateWithDocId, kDataStore } from './datatype/index.js'
import { assert, setHas } from './utils.js'
import { getByDocIdIfExists } from './datatype/get-if-exists.js'
import { TypedEmitter } from 'tiny-typed-emitter'
/** @import { Namespace } from './types.js' */

Expand Down Expand Up @@ -269,10 +270,8 @@ export class Roles extends TypedEmitter {
* @returns {Promise<Role>}
*/
async getRole(deviceId) {
const roleAssignment = await this.#dataType.getByDocId(deviceId, {
mustBeFound: false,
})
if (!roleAssignment) {
const roleRecord = await getByDocIdIfExists(this.#dataType, deviceId)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not strictly related, but I renamed this to make the diff for #188 a little smaller.

if (!roleRecord) {
// The project creator will have the creator role
const authCoreId = await this.#coreOwnership.getCoreId(deviceId, 'auth')
if (authCoreId === this.#projectCreatorAuthCoreId) {
Expand All @@ -284,7 +283,7 @@ export class Roles extends TypedEmitter {
}
}

const { roleId } = roleAssignment
const { roleId } = roleRecord
if (!isRoleId(roleId)) {
return BLOCKED_ROLE
}
Expand Down Expand Up @@ -386,9 +385,7 @@ export class Roles extends TypedEmitter {
}
}

const existingRoleDoc = await this.#dataType.getByDocId(deviceId, {
mustBeFound: false,
})
const existingRoleDoc = await getByDocIdIfExists(this.#dataType, deviceId)

if (existingRoleDoc) {
await this.#dataType.update(
Expand Down
3 changes: 2 additions & 1 deletion src/translation-api.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { and, sql } from 'drizzle-orm'
import { kCreateWithDocId, kSelect } from './datatype/index.js'
import { getByDocIdIfExists } from './datatype/get-if-exists.js'
import { hashObject } from './utils.js'
import { omit } from './lib/omit.js'
/** @import { Translation, TranslationValue } from '@comapeo/schema' */
Expand Down Expand Up @@ -49,7 +50,7 @@ export default class TranslationApi {
async put(value) {
const identifiers = omit(value, ['message'])
const docId = hashObject(identifiers)
const doc = await this.#dataType.getByDocId(docId, { mustBeFound: false })
const doc = await getByDocIdIfExists(this.#dataType, docId)
if (doc) {
return await this.#dataType.update(doc.versionId, value)
} else {
Expand Down
20 changes: 20 additions & 0 deletions test/data-type/get-if-exists.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { testenv } from './test-helpers.js'
import test, { describe } from 'node:test'
import assert from 'node:assert/strict'
import { getByDocIdIfExists } from '../../src/datatype/get-if-exists.js'
import { valueOf } from '@comapeo/schema'
import { generate } from '@mapeo/mock-data'

describe('getByDocIdIfExists', () => {
test('resolves with null if no document exists with that ID', async () => {
const { dataType } = await testenv()
assert.equal(await getByDocIdIfExists(dataType, 'foo bar'), null)
})

test('resolves with the document if it exists', async () => {
const { dataType } = await testenv()
const fixture = valueOf(generate('observation')[0])
const observation = await dataType.create(fixture)
assert(await getByDocIdIfExists(dataType, observation.docId))
})
})
110 changes: 13 additions & 97 deletions test/data-type.js → test/data-type/index.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { DataStore } from '../src/datastore/index.js'
import { DataStore } from '../../src/datastore/index.js'
import {
createCoreManager,
waitForCores,
replicate,
} from './helpers/core-manager.js'
} from '../helpers/core-manager.js'
import RAM from 'random-access-memory'
import crypto from 'hypercore-crypto'
import { observationTable, translationTable } from '../src/schema/project.js'
import { DataType, kCreateWithDocId } from '../src/datatype/index.js'
import { IndexWriter } from '../src/index-writer/index.js'
import { NotFoundError } from '../src/errors.js'
import { observationTable } from '../../src/schema/project.js'
import { DataType, kCreateWithDocId } from '../../src/datatype/index.js'
import { IndexWriter } from '../../src/index-writer/index.js'
import { NotFoundError } from '../../src/errors.js'

import Database from 'better-sqlite3'
import { drizzle } from 'drizzle-orm/better-sqlite3'
import { migrate } from 'drizzle-orm/better-sqlite3/migrator'
import { randomBytes } from 'crypto'
import TranslationApi from '../src/translation-api.js'
import { getProperty } from 'dot-prop'
import { decode, decodeBlockPrefix, parseVersionId } from '@comapeo/schema'
import { parseVersionId } from '@comapeo/schema'

import { testenv } from './test-helpers.js'

/** @type {import('@comapeo/schema').ObservationValue} */
const obsFixture = {
Expand All @@ -45,7 +46,8 @@ test('private createWithDocId() method', async () => {
const sqlite = new Database(':memory:')
const db = drizzle(sqlite)
migrate(db, {
migrationsFolder: new URL('../drizzle/project', import.meta.url).pathname,
migrationsFolder: new URL('../../drizzle/project', import.meta.url)
.pathname,
})

const coreManager = createCoreManager()
Expand Down Expand Up @@ -81,7 +83,8 @@ test('private createWithDocId() method throws when doc exists', async () => {
const sqlite = new Database(':memory:')
const db = drizzle(sqlite)
migrate(db, {
migrationsFolder: new URL('../drizzle/project', import.meta.url).pathname,
migrationsFolder: new URL('../../drizzle/project', import.meta.url)
.pathname,
})

const coreManager = createCoreManager()
Expand Down Expand Up @@ -209,14 +212,6 @@ test('getByDocId() throws if no document exists with that ID', async () => {
await assert.rejects(() => dataType.getByDocId('foo bar'), NotFoundError)
})

test('getByDocId() can return null if no document exists with that ID', async () => {
const { dataType } = await testenv({ projectKey: randomBytes(32) })
assert.equal(
await dataType.getByDocId('foo bar', { mustBeFound: false }),
null
)
})

test('delete()', async () => {
const projectKey = randomBytes(32)
const { dataType } = await testenv({ projectKey })
Expand Down Expand Up @@ -311,82 +306,3 @@ test('translation', async () => {
`not passing a a language code returns the untranslated message`
)
})

/**
* @param {object} [opts={}]
* @param {Buffer} [opts.projectKey]
*/
async function testenv(opts = {}) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was moved into a helper (see below).

const sqlite = new Database(':memory:')
const db = drizzle(sqlite)
migrate(db, {
migrationsFolder: new URL('../drizzle/project', import.meta.url).pathname,
})

const coreManager = createCoreManager({ ...opts, db })

const indexWriter = new IndexWriter({
tables: [observationTable, translationTable],
sqlite,
})

const dataStore = new DataStore({
coreManager,
namespace: 'data',
batch: async (entries) => indexWriter.batch(entries),
storage: () => new RAM(),
reindex: false,
})

const configDataStore = new DataStore({
coreManager,
namespace: 'config',
batch: async (entries) => {
/** @type {import('multi-core-indexer').Entry[]} */
const entriesToIndex = []
for (const entry of entries) {
const { schemaName } = decodeBlockPrefix(entry.block)
try {
if (schemaName === 'translation') {
const doc = decode(entry.block, {
coreDiscoveryKey: entry.key,
index: entry.index,
})
assert(
doc.schemaName === 'translation',
'expected a translation doc'
)
translationApi.index(doc)
}
entriesToIndex.push(entry)
} catch {
// Ignore errors thrown by values that can't be decoded for now
}
}
const indexed = await indexWriter.batch(entriesToIndex)
return indexed
},
storage: () => new RAM(),
reindex: false,
})

const translationDataType = new DataType({
dataStore: configDataStore,
table: translationTable,
db,
getTranslations: () => {
throw new Error('Cannot get translations for translations')
},
})

const translationApi = new TranslationApi({ dataType: translationDataType })

const dataType = new DataType({
dataStore,
table: observationTable,
db,
getTranslations: translationApi.get.bind(translationApi),
})

return { coreManager, dataType, dataStore, translationApi }
}
Loading
Loading