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 getById method to member api #262

Merged
merged 6 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
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
3 changes: 3 additions & 0 deletions src/mapeo-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ export class MapeoProject {
return { name: settings.name }
},
},
dataTypes: {
deviceInfo: this.#dataTypes.deviceInfo,
},
})

///////// 4. Write core ownership record
Expand Down
25 changes: 24 additions & 1 deletion src/member-api.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { TypedEmitter } from 'tiny-typed-emitter'
import { InviteResponse_Decision } from './generated/rpc.js'

/** @typedef {import('./datatype/index.js').DataType<import('./datastore/index.js').DataStore<'config'>, typeof import('./schema/project.js').deviceInfoTable, "deviceInfo", import('@mapeo/schema').DeviceInfo, import('@mapeo/schema').DeviceInfoValue>} DeviceInfoDataType */
/** @typedef {{ deviceId: string, name: import('@mapeo/schema').DeviceInfo['name'] }} MemberInfo */

export class MemberApi extends TypedEmitter {
#capabilities
#encryptionKeys
#projectKey
#rpc
#dataTypes
#queries

/**
Expand All @@ -16,14 +20,24 @@ export class MemberApi extends TypedEmitter {
* @param {import('./rpc/index.js').MapeoRPC} opts.rpc
* @param {Object} opts.queries
* @param {() => Promise<import('./generated/rpc.js').Invite_ProjectInfo>} opts.queries.getProjectInfo
* @param {Object} opts.dataTypes
* @param {Pick<DeviceInfoDataType, 'getByDocId'>} opts.dataTypes.deviceInfo
*/
constructor({ capabilities, encryptionKeys, projectKey, rpc, queries }) {
constructor({
capabilities,
encryptionKeys,
projectKey,
rpc,
queries,
dataTypes,
}) {
super()
this.#capabilities = capabilities
this.#encryptionKeys = encryptionKeys
this.#queries = queries
this.#projectKey = projectKey
this.#rpc = rpc
this.#dataTypes = dataTypes
}

/**
Expand Down Expand Up @@ -51,4 +65,13 @@ export class MemberApi extends TypedEmitter {

return response
}

/**
* @param {string} deviceId
* @returns {Promise<MemberInfo>}
*/
async getById(deviceId) {
const { name } = await this.#dataTypes.deviceInfo.getByDocId(deviceId)
return { deviceId, name }
}
}
56 changes: 55 additions & 1 deletion tests/member-api.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { test } from 'brittle'
import { randomBytes } from 'crypto'
import { KeyManager } from '@mapeo/crypto'

import { MapeoRPC } from '../src/rpc/index.js'
import { MemberApi } from '../src/member-api.js'
import { InviteResponse_Decision } from '../src/generated/rpc.js'
Expand Down Expand Up @@ -141,3 +140,58 @@ test('invite() does not assign role to invited device if invite is not accepted'
})
}
})

test('getById() works', async (t) => {
const projectKey = KeyManager.generateProjectKeypair().publicKey
const encryptionKeys = { auth: randomBytes(32) }

const rpc = new MapeoRPC()

const deviceId = randomBytes(32).toString('hex')

/** @type {import('@mapeo/schema').DeviceInfo} */
const deviceInfo = {
schemaName: 'deviceInfo',
docId: deviceId,
name: 'mapeo',
versionId: `${deviceId}/0`,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
links: [],
}

const deviceInfoRecords = [deviceInfo]

const memberApi = new MemberApi({
capabilities: { async assignRole() {} },
encryptionKeys,
projectKey,
rpc,
queries: { getProjectInfo: async () => {} },
dataTypes: {
deviceInfo: {
async getByDocId(deviceId) {
const info = deviceInfoRecords.find(({ docId }) => docId === deviceId)
if (!info) throw new Error(`No record with ID ${deviceId}`)
return info
},
},
},
})

const member = await memberApi.getById(deviceId)

t.alike(
member,
{
deviceId,
name: deviceInfo.name,
},
'returns matching member'
)

await t.exception(async () => {
const randomId = randomBytes(32)
await memberApi.getById(randomId)
}, 'throws when no match')
})
Loading