From 89bfa6d9fc2bb2218164563936920ec5bfeea8ad Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Tue, 12 Sep 2023 15:04:07 -0400 Subject: [PATCH 1/4] feat: add getById method to member api --- src/mapeo-project.js | 3 +++ src/member-api.js | 13 +++++++++++++ tests/member-api.js | 15 ++++++++++++--- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/mapeo-project.js b/src/mapeo-project.js index 8b5466e32..68e12d21a 100644 --- a/src/mapeo-project.js +++ b/src/mapeo-project.js @@ -224,6 +224,9 @@ export class MapeoProject { const settings = await this.$getProjectSettings() return { name: settings.name } }, + getDeviceInfo: async (deviceId) => { + return this.#dataTypes.deviceInfo.getByDocId(deviceId) + }, }, }) diff --git a/src/member-api.js b/src/member-api.js index 010b5b93c..4e6754a3e 100644 --- a/src/member-api.js +++ b/src/member-api.js @@ -1,6 +1,8 @@ import { TypedEmitter } from 'tiny-typed-emitter' import { InviteResponse_Decision } from './generated/rpc.js' +/** @typedef {{ deviceId: string, name: import('@mapeo/schema').DeviceInfo['name'] }} MemberInfo */ + export class MemberApi extends TypedEmitter { #capabilities #encryptionKeys @@ -16,6 +18,8 @@ export class MemberApi extends TypedEmitter { * @param {import('./rpc/index.js').MapeoRPC} opts.rpc * @param {Object} opts.queries * @param {() => Promise} opts.queries.getProjectInfo + * @param {(deviceId: string) => Promise} opts.queries.getDeviceInfo + * */ constructor({ capabilities, encryptionKeys, projectKey, rpc, queries }) { super() @@ -51,4 +55,13 @@ export class MemberApi extends TypedEmitter { return response } + + /** + * @param {string} deviceId + * @returns {Promise} + */ + async getById(deviceId) { + const deviceInfo = await this.#queries.getDeviceInfo(deviceId) + return { deviceId, name: deviceInfo.name } + } } diff --git a/tests/member-api.js b/tests/member-api.js index 295c4d3dc..aeee4164c 100644 --- a/tests/member-api.js +++ b/tests/member-api.js @@ -22,7 +22,10 @@ test('invite() sends expected project-related details', async (t) => { encryptionKeys, projectKey, rpc: r1, - queries: { getProjectInfo: async () => projectInfo }, + queries: { + getProjectInfo: async () => projectInfo, + getDeviceInfo: async () => {}, + }, }) r1.on('peers', async (peers) => { @@ -70,7 +73,10 @@ test('invite() assigns role to invited device after invite accepted', async (t) encryptionKeys: { auth: randomBytes(32) }, projectKey: KeyManager.generateProjectKeypair().publicKey, rpc: r1, - queries: { getProjectInfo: async () => {} }, + queries: { + getProjectInfo: async () => {}, + getDeviceInfo: async () => {}, + }, }) r1.on('peers', async (peers) => { @@ -119,7 +125,10 @@ test('invite() does not assign role to invited device if invite is not accepted' encryptionKeys: { auth: randomBytes(32) }, projectKey: KeyManager.generateProjectKeypair().publicKey, rpc: r1, - queries: { getProjectInfo: async () => {} }, + queries: { + getProjectInfo: async () => {}, + getDeviceInfo: async () => {}, + }, }) r1.on('peers', async (peers) => { From ff0c6f0f8e9dc5bb73b44f9afb62d9501f5c4fc8 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Wed, 13 Sep 2023 16:42:58 -0400 Subject: [PATCH 2/4] specify dataTypes opt instead of queries opt --- src/mapeo-project.js | 6 +++--- src/member-api.js | 20 +++++++++++++++----- tests/member-api.js | 15 +++------------ 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/mapeo-project.js b/src/mapeo-project.js index 68e12d21a..130797a72 100644 --- a/src/mapeo-project.js +++ b/src/mapeo-project.js @@ -224,9 +224,9 @@ export class MapeoProject { const settings = await this.$getProjectSettings() return { name: settings.name } }, - getDeviceInfo: async (deviceId) => { - return this.#dataTypes.deviceInfo.getByDocId(deviceId) - }, + }, + dataTypes: { + deviceInfo: this.#dataTypes.deviceInfo, }, }) diff --git a/src/member-api.js b/src/member-api.js index 4e6754a3e..bea0ce135 100644 --- a/src/member-api.js +++ b/src/member-api.js @@ -1,6 +1,7 @@ import { TypedEmitter } from 'tiny-typed-emitter' import { InviteResponse_Decision } from './generated/rpc.js' +/** @typedef {import('./datatype/index.js').DataType, 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 { @@ -8,6 +9,7 @@ export class MemberApi extends TypedEmitter { #encryptionKeys #projectKey #rpc + #dataTypes #queries /** @@ -18,16 +20,24 @@ export class MemberApi extends TypedEmitter { * @param {import('./rpc/index.js').MapeoRPC} opts.rpc * @param {Object} opts.queries * @param {() => Promise} opts.queries.getProjectInfo - * @param {(deviceId: string) => Promise} opts.queries.getDeviceInfo - * + * @param {Object} opts.dataTypes + * @param {Pick} 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 } /** @@ -61,7 +71,7 @@ export class MemberApi extends TypedEmitter { * @returns {Promise} */ async getById(deviceId) { - const deviceInfo = await this.#queries.getDeviceInfo(deviceId) - return { deviceId, name: deviceInfo.name } + const { name } = await this.#dataTypes.deviceInfo.getByDocId(deviceId) + return { deviceId, name } } } diff --git a/tests/member-api.js b/tests/member-api.js index aeee4164c..295c4d3dc 100644 --- a/tests/member-api.js +++ b/tests/member-api.js @@ -22,10 +22,7 @@ test('invite() sends expected project-related details', async (t) => { encryptionKeys, projectKey, rpc: r1, - queries: { - getProjectInfo: async () => projectInfo, - getDeviceInfo: async () => {}, - }, + queries: { getProjectInfo: async () => projectInfo }, }) r1.on('peers', async (peers) => { @@ -73,10 +70,7 @@ test('invite() assigns role to invited device after invite accepted', async (t) encryptionKeys: { auth: randomBytes(32) }, projectKey: KeyManager.generateProjectKeypair().publicKey, rpc: r1, - queries: { - getProjectInfo: async () => {}, - getDeviceInfo: async () => {}, - }, + queries: { getProjectInfo: async () => {} }, }) r1.on('peers', async (peers) => { @@ -125,10 +119,7 @@ test('invite() does not assign role to invited device if invite is not accepted' encryptionKeys: { auth: randomBytes(32) }, projectKey: KeyManager.generateProjectKeypair().publicKey, rpc: r1, - queries: { - getProjectInfo: async () => {}, - getDeviceInfo: async () => {}, - }, + queries: { getProjectInfo: async () => {} }, }) r1.on('peers', async (peers) => { From 9e36a6fdb9be1f01cbc2c7749bc7f3cfcc9bee3f Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Wed, 13 Sep 2023 16:43:07 -0400 Subject: [PATCH 3/4] add basic test --- tests/member-api.js | 56 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/tests/member-api.js b/tests/member-api.js index 295c4d3dc..a8bdd217d 100644 --- a/tests/member-api.js +++ b/tests/member-api.js @@ -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' @@ -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') +}) From b2981e9aba04ce1b29dfef9f1b11e0a344e972a8 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Mon, 18 Sep 2023 11:08:06 -0400 Subject: [PATCH 4/4] update package.lock --- package-lock.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 5b867d486..a0bfd9343 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,6 @@ "": { "name": "@mapeo/core", "version": "9.0.0-alpha.0", - "hasInstallScript": true, "license": "MIT", "dependencies": { "@digidem/types": "^2.1.0",