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", diff --git a/src/mapeo-project.js b/src/mapeo-project.js index 8b5466e32..130797a72 100644 --- a/src/mapeo-project.js +++ b/src/mapeo-project.js @@ -225,6 +225,9 @@ export class MapeoProject { return { name: settings.name } }, }, + dataTypes: { + deviceInfo: this.#dataTypes.deviceInfo, + }, }) ///////// 4. Write core ownership record diff --git a/src/member-api.js b/src/member-api.js index 010b5b93c..bea0ce135 100644 --- a/src/member-api.js +++ b/src/member-api.js @@ -1,11 +1,15 @@ 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 { #capabilities #encryptionKeys #projectKey #rpc + #dataTypes #queries /** @@ -16,14 +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 {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 } /** @@ -51,4 +65,13 @@ export class MemberApi extends TypedEmitter { return response } + + /** + * @param {string} deviceId + * @returns {Promise} + */ + async getById(deviceId) { + const { name } = await this.#dataTypes.deviceInfo.getByDocId(deviceId) + return { deviceId, name } + } } 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') +})