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: remove project.ready() #386

Merged
merged 4 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
29 changes: 27 additions & 2 deletions src/core-ownership.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { kTable, kSelect, kCreateWithDocId } from './datatype/index.js'
import { eq, or } from 'drizzle-orm'
import mapObject from 'map-obj'
import { discoveryKey } from 'hypercore-crypto'
import pDefer from 'p-defer'

/**
* @typedef {import('./types.js').CoreOwnershipWithSignatures} CoreOwnershipWithSignatures
*/

export class CoreOwnership {
#dataType
#ownershipWriteDone
/**
*
* @param {object} opts
Expand All @@ -25,16 +27,38 @@ export class CoreOwnership {
* import('@mapeo/schema').CoreOwnership,
* import('@mapeo/schema').CoreOwnershipValue
* >} opts.dataType
* @param {Record<import('./core-manager/index.js').Namespace, import('./types.js').KeyPair>} opts.coreKeypairs
* @param {import('./types.js').KeyPair} opts.identityKeypair
*/
constructor({ dataType }) {
constructor({ dataType, coreKeypairs, identityKeypair }) {
this.#dataType = dataType
const authWriterCore = dataType.writerCore
const deferred = pDefer()
this.#ownershipWriteDone = deferred.promise

const writeOwnership = () => {
if (authWriterCore.length > 0) {
deferred.resolve()
return
}
this.#writeOwnership(identityKeypair, coreKeypairs)
.then(deferred.resolve)
.catch(deferred.reject)
}
// @ts-ignore - opened missing from types
if (authWriterCore.opened) {
writeOwnership()
} else {
authWriterCore.on('ready', writeOwnership)
}
}

/**
* @param {string} coreId
* @returns {Promise<string>} deviceId of device that owns the core
*/
async getOwner(coreId) {
await this.#ownershipWriteDone
const table = this.#dataType[kTable]
const expressions = []
for (const namespace of NAMESPACES) {
Expand All @@ -57,6 +81,7 @@ export class CoreOwnership {
* @returns {Promise<string>} coreId of core belonging to `deviceId` for `namespace`
*/
async getCoreId(deviceId, namespace) {
await this.#ownershipWriteDone
const result = await this.#dataType.getByDocId(deviceId)
return result[`${namespace}CoreId`]
}
Expand All @@ -66,7 +91,7 @@ export class CoreOwnership {
* @param {import('./types.js').KeyPair} identityKeypair
* @param {Record<typeof NAMESPACES[number], import('./types.js').KeyPair>} coreKeypairs
*/
async writeOwnership(identityKeypair, coreKeypairs) {
async #writeOwnership(identityKeypair, coreKeypairs) {
/** @type {import('./types.js').CoreOwnershipWithSignaturesValue} */
const docValue = {
schemaName: 'coreOwnership',
Expand Down
3 changes: 3 additions & 0 deletions src/datatype/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { type BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'
import { SQLiteSelectBuilder } from 'drizzle-orm/sqlite-core'
import { RunResult } from 'better-sqlite3'
import type Hypercore from 'hypercore'

type MapeoDocTableName = `${MapeoDoc['schemaName']}Table`
type GetMapeoDocTables<T> = T[keyof T & MapeoDocTableName]
Expand Down Expand Up @@ -59,6 +60,8 @@ export class DataType<

get [kTable](): TTable

get writerCore(): Hypercore<'binary', Buffer>

[kCreateWithDocId](
docId: string,
value:
Expand Down
4 changes: 4 additions & 0 deletions src/datatype/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ export class DataType {
return this.#table
}

get writerCore() {
return this.#dataStore.writerCore
}

/**
* @template {import('type-fest').Exact<TValue, T>} T
* @param {T} value
Expand Down
2 changes: 0 additions & 2 deletions src/mapeo-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,6 @@ export class MapeoManager extends TypedEmitter {
try {
// 4. Write device info into project
const project = await this.getProject(projectPublicId)
await project.ready()

try {
const deviceInfo = await this.getDeviceInfo()
Expand Down Expand Up @@ -548,7 +547,6 @@ export class MapeoManager extends TypedEmitter {
* @returns {Promise<boolean>}
*/
async #waitForInitialSync(project, { timeoutMs = 5000 } = {}) {
await project.ready()
const [capability, projectSettings] = await Promise.all([
project.$getOwnCapabilities(),
project.$getProjectSettings(),
Expand Down
43 changes: 10 additions & 33 deletions src/mapeo-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import Database from 'better-sqlite3'
import { decodeBlockPrefix } from '@mapeo/schema'
import { drizzle } from 'drizzle-orm/better-sqlite3'
import { migrate } from 'drizzle-orm/better-sqlite3/migrator'
import pDefer from 'p-defer'
import { discoveryKey } from 'hypercore-crypto'

import { CoreManager, NAMESPACES } from './core-manager/index.js'
Expand Down Expand Up @@ -61,7 +60,6 @@ export class MapeoProject {
#blobStore
#coreOwnership
#capabilities
#ownershipWriteDone
#memberApi
#iconApi
#syncApi
Expand Down Expand Up @@ -220,9 +218,16 @@ export class MapeoProject {
db,
}),
}

const identityKeypair = keyManager.getIdentityKeypair()
const coreKeypairs = getCoreKeypairs({
projectKey,
projectSecretKey,
keyManager,
})
this.#coreOwnership = new CoreOwnership({
dataType: this.#dataTypes.coreOwnership,
coreKeypairs,
identityKeypair,
})
this.#capabilities = new Capabilities({
dataType: this.#dataTypes.role,
Expand Down Expand Up @@ -302,27 +307,6 @@ export class MapeoProject {
this.#syncApi[kHandleDiscoveryKey](discoveryKey, stream)
})

///////// 5. Write core ownership record

const deferred = pDefer()
// Avoid uncaught rejection. If this is rejected then project.ready() will reject
deferred.promise.catch(() => {})
this.#ownershipWriteDone = deferred.promise

const authCore = this.#coreManager.getWriterCore('auth').core
authCore.on('ready', () => {
if (authCore.length > 0) return
const identityKeypair = keyManager.getIdentityKeypair()
const coreKeypairs = getCoreKeypairs({
projectKey,
projectSecretKey,
keyManager,
})
this.#coreOwnership
.writeOwnership(identityKeypair, coreKeypairs)
.then(deferred.resolve)
.catch(deferred.reject)
})
this.#l.log('Created project instance %h', projectKey)
}

Expand Down Expand Up @@ -355,13 +339,6 @@ export class MapeoProject {
return this.#deviceId
}

/**
* Resolves when hypercores have all loaded
*/
async ready() {
await Promise.all([this.#coreManager.ready(), this.#ownershipWriteDone])
}

/**
* @param {import('multi-core-indexer').Entry[]} entries
* @param {{projectIndexWriter: IndexWriter, sharedIndexWriter: IndexWriter}} indexWriters
Expand Down Expand Up @@ -544,11 +521,11 @@ function extractEditableProjectSettings(projectDoc) {
* @param {Buffer} opts.projectKey
* @param {Buffer} [opts.projectSecretKey]
* @param {import('@mapeo/crypto').KeyManager} opts.keyManager
* @returns {Record<import('./core-manager/core-index.js').Namespace, import('./types.js').KeyPair>}
* @returns {Record<import('./core-manager/index.js').Namespace, import('./types.js').KeyPair>}
*/
function getCoreKeypairs({ projectKey, projectSecretKey, keyManager }) {
const keypairs =
/** @type {Record<import('./core-manager/core-index.js').Namespace, import('./types.js').KeyPair>} */ ({})
/** @type {Record<import('./core-manager/index.js').Namespace, import('./types.js').KeyPair>} */ ({})

for (const namespace of NAMESPACES) {
keypairs[namespace] =
Expand Down
2 changes: 0 additions & 2 deletions test-e2e/capabilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ test('New device without capabilities', async (t) => {
{ waitForSync: false }
)
const project = await manager.getProject(projectId)
await project.ready()

const ownCapabilities = await project.$getOwnCapabilities()

Expand Down Expand Up @@ -147,7 +146,6 @@ test('getMany() - on newly invited device before sync', async (t) => {
{ waitForSync: false }
)
const project = await manager.getProject(projectId)
await project.ready()

const expected = {
[deviceId]: NO_ROLE_CAPABILITIES,
Expand Down
1 change: 0 additions & 1 deletion test-e2e/core-ownership.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ test('CoreOwnership', async (t) => {
const projectId = await manager.createProject()
const project = await manager.getProject(projectId)
const coreOwnership = project[kCoreOwnership]
await project.ready()

const identityKeypair = km.getIdentityKeypair()
const deviceId = identityKeypair.publicKey.toString('hex')
Expand Down
5 changes: 0 additions & 5 deletions test-e2e/device-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ test('device info written to projects', (t) => {
const projectId = await manager.createProject()
const project = await manager.getProject(projectId)

await project.ready()

const me = await project.$member.getById(project.deviceId)

st.is(me.deviceId, project.deviceId)
Expand Down Expand Up @@ -74,8 +72,6 @@ test('device info written to projects', (t) => {

const project = await manager.getProject(projectId)

await project.ready()

const me = await project.$member.getById(project.deviceId)

st.alike({ name: me.name }, { name: 'mapeo' })
Expand All @@ -101,7 +97,6 @@ test('device info written to projects', (t) => {
const projects = await Promise.all(
projectIds.map(async (projectId) => {
const project = await manager.getProject(projectId)
await project.ready()
return project
})
)
Expand Down
4 changes: 0 additions & 4 deletions test-e2e/media-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ test('retrieving blobs using url', async (t) => {

const project = await manager.getProject(await manager.createProject())

await project.ready()

const exceptionPromise1 = t.exception(async () => {
await project.$blobs.getUrl({
driveId: randomBytes(32).toString('hex'),
Expand Down Expand Up @@ -126,8 +124,6 @@ test('retrieving icons using url', async (t) => {

const project = await manager.getProject(await manager.createProject())

await project.ready()

const exceptionPromise1 = t.exception(async () => {
await project.$icons.getIconUrl(randomBytes(32).toString('hex'), {
mimeType: 'image/png',
Expand Down
4 changes: 0 additions & 4 deletions test-e2e/members.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ test('getting yourself after creating project', async (t) => {

const deviceInfo = await manager.getDeviceInfo()
const project = await manager.getProject(await manager.createProject())
await project.ready()

const me = await project.$member.getById(project.deviceId)

Expand Down Expand Up @@ -62,7 +61,6 @@ test('getting yourself after adding project (but not yet synced)', async (t) =>
{ waitForSync: false }
)
)
await project.ready()

const me = await project.$member.getById(project.deviceId)

Expand Down Expand Up @@ -98,7 +96,6 @@ test('getting invited member after invite rejected', async (t) => {

const projectId = await invitor.createProject()
const project = await invitor.getProject(projectId)
await project.ready()

await invite({
invitor,
Expand Down Expand Up @@ -131,7 +128,6 @@ test('getting invited member after invite accepted', async (t) => {
const { name: inviteeName } = await invitee.getDeviceInfo()
const projectId = await invitor.createProject()
const project = await invitor.getProject(projectId)
await project.ready()

await invite({
invitor,
Expand Down
Loading