Skip to content

Commit

Permalink
nullIfNotFound instead
Browse files Browse the repository at this point in the history
  • Loading branch information
EvanHahn committed Nov 20, 2024
1 parent 1a0af6d commit a652b05
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 158 deletions.
22 changes: 0 additions & 22 deletions src/datatype/get-if-exists.js

This file was deleted.

9 changes: 9 additions & 0 deletions src/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,12 @@ export class NotFoundError extends Error {
super(message)
}
}

/**
* @param {unknown} err
* @returns {null}
*/
export function nullIfNotFound(err) {
if (err instanceof NotFoundError) return null
throw err
}
11 changes: 7 additions & 4 deletions src/mapeo-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ import { Logger } from './logger.js'
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 { NotFoundError, nullIfNotFound } from './errors.js'
/** @import { ProjectSettingsValue } from '@comapeo/schema' */
/** @import { CoreStorage, KeyPair, Namespace, ReplicationStream } from './types.js' */

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

const existing = await getByDocIdIfExists(projectSettings, this.#projectId)
const existing = await projectSettings
.getByDocId(this.#projectId)
.catch(nullIfNotFound)

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

const existingDoc = await getByDocIdIfExists(deviceInfo, configCoreId)
const existingDoc = await deviceInfo
.getByDocId(configCoreId)
.catch(nullIfNotFound)
if (existingDoc) {
return await deviceInfo.update(existingDoc.versionId, doc)
} else {
Expand Down
10 changes: 7 additions & 3 deletions src/roles.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +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 { nullIfNotFound } from './errors.js'
import { TypedEmitter } from 'tiny-typed-emitter'
/** @import { Namespace } from './types.js' */

Expand Down Expand Up @@ -270,7 +270,9 @@ export class Roles extends TypedEmitter {
* @returns {Promise<Role>}
*/
async getRole(deviceId) {
const roleRecord = await getByDocIdIfExists(this.#dataType, deviceId)
const roleRecord = await this.#dataType
.getByDocId(deviceId)
.catch(nullIfNotFound)
if (!roleRecord) {
// The project creator will have the creator role
const authCoreId = await this.#coreOwnership.getCoreId(deviceId, 'auth')
Expand Down Expand Up @@ -385,7 +387,9 @@ export class Roles extends TypedEmitter {
}
}

const existingRoleDoc = await getByDocIdIfExists(this.#dataType, deviceId)
const existingRoleDoc = await this.#dataType
.getByDocId(deviceId)
.catch(nullIfNotFound)

if (existingRoleDoc) {
await this.#dataType.update(
Expand Down
4 changes: 2 additions & 2 deletions src/translation-api.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
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 { nullIfNotFound } from './errors.js'
import { omit } from './lib/omit.js'
/** @import { Translation, TranslationValue } from '@comapeo/schema' */
/** @import { SetOptional } from 'type-fest' */
Expand Down Expand Up @@ -50,7 +50,7 @@ export default class TranslationApi {
async put(value) {
const identifiers = omit(value, ['message'])
const docId = hashObject(identifiers)
const doc = await getByDocIdIfExists(this.#dataType, docId)
const doc = await this.#dataType.getByDocId(docId).catch(nullIfNotFound)
if (doc) {
return await this.#dataType.update(doc.versionId, value)
} else {
Expand Down
102 changes: 89 additions & 13 deletions test/data-type/index.js → test/data-type.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
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 } 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, 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 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 { parseVersionId } from '@comapeo/schema'

import { testenv } from './test-helpers.js'
import { decode, decodeBlockPrefix, parseVersionId } from '@comapeo/schema'

/** @type {import('@comapeo/schema').ObservationValue} */
const obsFixture = {
Expand All @@ -46,8 +45,7 @@ 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 @@ -83,8 +81,7 @@ 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 @@ -306,3 +303,82 @@ test('translation', async () => {
`not passing a a language code returns the untranslated message`
)
})

/**
* @param {object} [opts={}]
* @param {Buffer} [opts.projectKey]
*/
async function testenv(opts = {}) {
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 }
}
20 changes: 0 additions & 20 deletions test/data-type/get-if-exists.js

This file was deleted.

93 changes: 0 additions & 93 deletions test/data-type/test-helpers.js

This file was deleted.

12 changes: 11 additions & 1 deletion test/errors.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import test, { describe } from 'node:test'
import assert from 'node:assert/strict'
import { NotFoundError } from '../src/errors.js'
import { NotFoundError, nullIfNotFound } from '../src/errors.js'

describe('NotFoundError', () => {
test('subclasses Error', () => {
Expand All @@ -15,3 +15,13 @@ describe('NotFoundError', () => {
assert.equal(new NotFoundError('foo').message, 'foo')
})
})

describe('nullIfNotFound', () => {
test('returns null if passed a NotFoundError', () => {
assert.equal(nullIfNotFound(new NotFoundError()), null)
})

test('throws if passed something other than a NotFoundError', () => {
assert.throws(() => nullIfNotFound(new Error('foo')), { message: 'foo' })
})
})

0 comments on commit a652b05

Please sign in to comment.