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() #392

Merged
merged 79 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
d687f1d
WIP initial work
gmaclennan Oct 26, 2023
74a77e2
rename Rpc to LocalPeers
gmaclennan Oct 26, 2023
e301c85
Handle deviceInfo internally, id -> deviceId
gmaclennan Oct 26, 2023
ae371fd
Tests for stream error handling
gmaclennan Oct 26, 2023
ccdf39f
remove unnecessary constructor
gmaclennan Oct 26, 2023
a52254b
return replication stream
gmaclennan Oct 26, 2023
50698e6
Attach protomux instance to peer info
gmaclennan Oct 26, 2023
ae35e9c
rename and re-organize
gmaclennan Oct 26, 2023
be64a3d
revert changes outside scope of PR
gmaclennan Oct 26, 2023
ee2020e
WIP initial work
gmaclennan Oct 26, 2023
0cd25da
Tie everything together
gmaclennan Oct 26, 2023
395345d
rename getProjectInstance
gmaclennan Oct 27, 2023
7963a3e
feat: client.listLocalPeers() & `local-peers` evt
gmaclennan Oct 27, 2023
9bff9ac
feat: add $sync API methods
gmaclennan Oct 27, 2023
1c0dc6b
feat: Add project.$waitForInitialSync() method
gmaclennan Oct 27, 2023
1f4fad2
Wait for initial sync within addProject()
gmaclennan Oct 27, 2023
d00bf10
fix: don't add core bitfield until core is ready
gmaclennan Oct 28, 2023
989b429
feat: expose deviceId on coreManager
gmaclennan Oct 28, 2023
bbda3f0
fix: wait for project.ready() in waitForInitialSync
gmaclennan Oct 28, 2023
0a32989
fix: skip waitForSync in tests
gmaclennan Oct 28, 2023
dc7a7c3
don't enable/disable namespace if not needed
gmaclennan Oct 30, 2023
1895679
start core download when created via sparse: false
gmaclennan Oct 30, 2023
763b57b
Add debug logging
gmaclennan Oct 30, 2023
2fb1072
fix timeout
gmaclennan Oct 30, 2023
b5f1a00
fix: Add new cores to the indexer (!!!)
gmaclennan Oct 30, 2023
d3fb8ad
remove unnecessary log stmt
gmaclennan Oct 30, 2023
32c4d32
get capabilities.getMany() to include creator
gmaclennan Oct 30, 2023
fe01caa
fix invite test
gmaclennan Oct 30, 2023
5a47b56
keep blob cores sparse
gmaclennan Oct 30, 2023
9921f66
optional param for LocalPeers
gmaclennan Nov 7, 2023
6dfa0d6
Merge branch 'main' into feat/initial-download
gmaclennan Nov 9, 2023
68455be
re-org sync and replication
gmaclennan Nov 10, 2023
7513788
update package-lock
gmaclennan Nov 10, 2023
f58bbaf
Merge branch 'main' into feat/initial-download
gmaclennan Nov 10, 2023
8c6a081
chore: Add debug logging
gmaclennan Nov 10, 2023
3bad62a
Merge branch 'chore/add-logging' into feat/initial-download
gmaclennan Nov 10, 2023
8a63746
Add new logger to discovery + dnssd
gmaclennan Nov 10, 2023
ed69a78
Get invite test working
gmaclennan Nov 10, 2023
7300da3
fix manager logger
gmaclennan Nov 11, 2023
b2eda92
cleanup invite test (and make it fail :(
gmaclennan Nov 11, 2023
2224f2b
fix: handle duplicate connections to LocalPeers
gmaclennan Nov 14, 2023
97df1c0
Merge branch 'main' into fix/local-peers
gmaclennan Nov 14, 2023
bc5e8d6
fix stream close before channel open
gmaclennan Nov 14, 2023
c1b473c
send invite to non-existent peer
gmaclennan Nov 14, 2023
07cac79
fixed fake timers implementation for tests
gmaclennan Nov 14, 2023
3407e0c
new tests for duplicate connections
gmaclennan Nov 14, 2023
6174ce0
Merge branch 'fix/local-peers' into feat/initial-download
gmaclennan Nov 14, 2023
8387f84
cleanup and small fix
gmaclennan Nov 14, 2023
6a886b8
Better state debug logging
gmaclennan Nov 14, 2023
d50fcea
chain of invites test
gmaclennan Nov 14, 2023
5bf1941
fix max listeners and add skipped test
gmaclennan Nov 14, 2023
34d2d34
fix: only request a core key from one peer
gmaclennan Nov 16, 2023
158ac7c
cleanup members tests with new helprs
gmaclennan Nov 16, 2023
687b9bc
wait for project ready when adding
gmaclennan Nov 16, 2023
e6225fe
only create 4 clients for chain of invites test
gmaclennan Nov 16, 2023
a912ede
add e2e sync tests
gmaclennan Nov 17, 2023
bbfe301
add published @mapeo/mock-data
gmaclennan Nov 20, 2023
ea442a3
fix: don't open cores in sparse mode
gmaclennan Nov 20, 2023
72f0dff
fix: option to skip auto download for tests
gmaclennan Nov 20, 2023
a07c278
e2e test for stop-start sync
gmaclennan Nov 20, 2023
800d136
fix coreManager unit tests
gmaclennan Nov 20, 2023
3afafbb
fix blob store tests
gmaclennan Nov 20, 2023
ca60188
Merge branch 'main' into feat/initial-download
gmaclennan Nov 20, 2023
420e21a
fix discovery-key event
gmaclennan Nov 20, 2023
5cd349d
add coreCount to sync state
gmaclennan Nov 20, 2023
04520f6
test sync with blocked peer & fix bugs
gmaclennan Nov 20, 2023
53e388e
fix datatype unit tests
gmaclennan Nov 20, 2023
41895a7
fix blobs server unit tests
gmaclennan Nov 20, 2023
7df2c73
remote peer-sync-controller unit test
gmaclennan Nov 20, 2023
1133e75
fix type issues caused by bad lockfile
achou11 Nov 20, 2023
5d7c251
ignore debug type errors
gmaclennan Nov 21, 2023
a2f8a1b
fixes for review comments
gmaclennan Nov 21, 2023
cd89ec4
move utils-new into utils
gmaclennan Nov 21, 2023
1f67ba8
Merge branch 'main' into feat/initial-download
gmaclennan Nov 23, 2023
83ffe92
Add debug info to test that sometimes fails
gmaclennan Nov 23, 2023
4b0c71b
Update package-lock.json version
gmaclennan Nov 23, 2023
d560d0a
remove project.ready() (breaks things)
gmaclennan Nov 24, 2023
6896021
wait for coreOwnership write before returning
gmaclennan Nov 24, 2023
3e5bde8
Merge branch 'main' into feat/remove-project-ready
gmaclennan Nov 28, 2023
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