diff --git a/packages/helia/package.json b/packages/helia/package.json index 7ac7e641c..a81066322 100644 --- a/packages/helia/package.json +++ b/packages/helia/package.json @@ -144,6 +144,7 @@ "@libp2p/interfaces": "^3.3.1", "blockstore-core": "^3.0.0", "cborg": "^1.10.0", + "datastore-core": "^8.0.4", "interface-blockstore": "^4.0.1", "interface-datastore": "^7.0.3", "interface-store": "^3.0.4", @@ -167,7 +168,6 @@ "@ipld/dag-json": "^10.0.1", "@libp2p/websockets": "^5.0.3", "aegir": "^38.1.0", - "datastore-core": "^8.0.4", "libp2p": "^0.42.2" }, "typedoc": { diff --git a/packages/helia/src/helia.ts b/packages/helia/src/helia.ts index 9237961f4..8a2f1737a 100644 --- a/packages/helia/src/helia.ts +++ b/packages/helia/src/helia.ts @@ -1,4 +1,4 @@ -import type { GCOptions, Helia, InfoResponse } from '@helia/interface' +import type { GCOptions, Helia } from '@helia/interface' import type { Libp2p } from '@libp2p/interface-libp2p' import type { Datastore } from 'interface-datastore' import { identity } from 'multiformats/hashes/identity' @@ -12,6 +12,8 @@ import { PinsImpl } from './pins.js' import { assertDatastoreVersionIsCurrent } from './utils/datastore-version.js' import drain from 'it-drain' import { CustomProgressEvent } from 'progress-events' +import { MemoryDatastore } from 'datastore-core' +import { MemoryBlockstore } from 'blockstore-core' export class HeliaImpl implements Helia { public libp2p: Libp2p @@ -19,7 +21,7 @@ export class HeliaImpl implements Helia { public datastore: Datastore public pins: Pins - #bitswap: Bitswap + #bitswap?: Bitswap constructor (init: HeliaInit) { const hashers: MultihashHasher[] = [ @@ -29,52 +31,67 @@ export class HeliaImpl implements Helia { ...(init.hashers ?? []) ] - this.pins = new PinsImpl(init.datastore, init.blockstore, init.dagWalkers ?? []) + const datastore = init.datastore ?? new MemoryDatastore() + const blockstore = init.blockstore ?? new MemoryBlockstore() - this.#bitswap = createBitswap(init.libp2p, init.blockstore, { - hashLoader: { - getHasher: async (codecOrName: string | number) => { - const hasher = hashers.find(hasher => { - return hasher.code === codecOrName || hasher.name === codecOrName - }) + // @ts-expect-error incomplete libp2p implementation + const libp2p = init.libp2p ?? new Proxy({}, { + get (_, prop) { + const noop = (): void => {} + const noops = ['start', 'stop'] - if (hasher != null) { - return await Promise.resolve(hasher) - } + if (noops.includes(prop.toString())) { + return noop + } - throw new Error(`Could not load hasher for code/name "${codecOrName}"`) + if (prop === 'isProxy') { + return true } + + throw new Error('Please configure Helia with a libp2p instance') + }, + set () { + throw new Error('Please configure Helia with a libp2p instance') } }) - this.libp2p = init.libp2p - this.blockstore = new BlockStorage(init.blockstore, this.#bitswap, this.pins) - this.datastore = init.datastore + this.pins = new PinsImpl(datastore, blockstore, init.dagWalkers ?? []) + + if (init.libp2p != null) { + this.#bitswap = createBitswap(libp2p, blockstore, { + hashLoader: { + getHasher: async (codecOrName: string | number) => { + const hasher = hashers.find(hasher => { + return hasher.code === codecOrName || hasher.name === codecOrName + }) + + if (hasher != null) { + return await Promise.resolve(hasher) + } + + throw new Error(`Could not load hasher for code/name "${codecOrName}"`) + } + } + }) + } + + this.libp2p = libp2p + this.blockstore = new BlockStorage(blockstore, this.pins, this.#bitswap) + this.datastore = datastore } async start (): Promise { await assertDatastoreVersionIsCurrent(this.datastore) - this.#bitswap.start() + this.#bitswap?.start() await this.libp2p.start() } async stop (): Promise { - this.#bitswap.stop() + this.#bitswap?.stop() await this.libp2p.stop() } - async info (): Promise { - return { - peerId: this.libp2p.peerId, - multiaddrs: this.libp2p.getMultiaddrs(), - agentVersion: this.libp2p.identifyService.host.agentVersion, - protocolVersion: this.libp2p.identifyService.host.protocolVersion, - protocols: this.libp2p.getProtocols(), - status: this.libp2p.isStarted() ? 'running' : 'stopped' - } - } - async gc (options: GCOptions = {}): Promise { const releaseLock = await this.blockstore.lock.writeLock() diff --git a/packages/helia/src/index.ts b/packages/helia/src/index.ts index 60e83a807..202630dbf 100644 --- a/packages/helia/src/index.ts +++ b/packages/helia/src/index.ts @@ -48,17 +48,17 @@ export interface HeliaInit { /** * A libp2p node is required to perform network operations */ - libp2p: Libp2p + libp2p?: Libp2p /** * The blockstore is where blocks are stored */ - blockstore: Blockstore + blockstore?: Blockstore /** * The datastore is where data is stored */ - datastore: Datastore + datastore?: Datastore /** * By default sha256, sha512 and identity hashes are supported for @@ -83,7 +83,7 @@ export interface HeliaInit { /** * Create and return a Helia node */ -export async function createHelia (init: HeliaInit): Promise { +export async function createHelia (init: HeliaInit = {}): Promise { const helia = new HeliaImpl(init) if (init.start !== false) { diff --git a/packages/helia/src/storage.ts b/packages/helia/src/storage.ts index 80c4dce56..ef56fc805 100644 --- a/packages/helia/src/storage.ts +++ b/packages/helia/src/storage.ts @@ -23,13 +23,13 @@ export interface BlockStorageOptions extends AbortOptions { export class BlockStorage extends BaseBlockstore implements Blockstore { public lock: Mortice private readonly child: Blockstore - private readonly bitswap: Bitswap + private readonly bitswap?: Bitswap private readonly pins: Pins /** * Create a new BlockStorage */ - constructor (blockstore: Blockstore, bitswap: Bitswap, pins: Pins) { + constructor (blockstore: Blockstore, pins: Pins, bitswap?: Bitswap) { super() this.child = blockstore @@ -54,10 +54,10 @@ export class BlockStorage extends BaseBlockstore implements Blockstore { * Put a block to the underlying datastore */ async put (cid: CID, block: Uint8Array, options: AbortOptions = {}): Promise { - const releaseLock = await this.lock.writeLock() + const releaseLock = await this.lock.readLock() try { - if (this.bitswap.isStarted()) { + if (this.bitswap?.isStarted() === true) { await this.bitswap.put(cid, block, options) } else { await this.child.put(cid, block, options) @@ -71,16 +71,16 @@ export class BlockStorage extends BaseBlockstore implements Blockstore { * Put a multiple blocks to the underlying datastore */ async * putMany (blocks: AwaitIterable<{ key: CID, value: Uint8Array }>, options: AbortOptions = {}): AsyncGenerator<{ key: CID, value: Uint8Array }, void, undefined> { - const releaseLock = await this.lock.writeLock() + const releaseLock = await this.lock.readLock() try { - const missingBlocks = filter(blocks, async ({ key }) => { return !(await this.child.has(key)) }) + const missingBlocks = filter(blocks, async ({ key }) => { + return !(await this.child.has(key)) + }) - if (this.bitswap.isStarted()) { - yield * this.bitswap.putMany(missingBlocks, options) - } else { - yield * this.child.putMany(missingBlocks, options) - } + const store = this.bitswap?.isStarted() === true ? this.bitswap : this.child + + yield * store.putMany(missingBlocks, options) } finally { releaseLock() } @@ -93,8 +93,8 @@ export class BlockStorage extends BaseBlockstore implements Blockstore { const releaseLock = await this.lock.readLock() try { - if (!(await this.has(cid)) && this.bitswap.isStarted()) { - return await this.bitswap.get(cid, options) + if (!(await this.has(cid)) && this.bitswap?.isStarted() === true) { + return await this.bitswap?.get(cid, options) } else { return await this.child.get(cid, options) } @@ -115,7 +115,7 @@ export class BlockStorage extends BaseBlockstore implements Blockstore { void Promise.resolve().then(async () => { for await (const cid of cids) { - if (!(await this.has(cid)) && this.bitswap.isStarted()) { + if (!(await this.has(cid)) && this.bitswap?.isStarted() === true) { getFromBitswap.push(cid) } else { getFromChild.push(cid) @@ -128,10 +128,15 @@ export class BlockStorage extends BaseBlockstore implements Blockstore { getFromBitswap.throw(err) }) - yield * merge( - this.bitswap.getMany(getFromBitswap, options), + const streams = [ this.child.getMany(getFromChild, options) - ) + ] + + if (this.bitswap?.isStarted() === true) { + streams.push(this.bitswap.getMany(getFromBitswap, options)) + } + + yield * merge(...streams) } finally { releaseLock() } diff --git a/packages/helia/test/index.spec.ts b/packages/helia/test/index.spec.ts index bb9a5ff97..8445b5979 100644 --- a/packages/helia/test/index.spec.ts +++ b/packages/helia/test/index.spec.ts @@ -8,6 +8,8 @@ import { noise } from '@chainsafe/libp2p-noise' import { yamux } from '@chainsafe/libp2p-yamux' import { createHelia } from '../src/index.js' import type { Helia } from '@helia/interface' +import { CID } from 'multiformats/cid' +import { Key } from 'interface-datastore' describe('helia', () => { let helia: Helia @@ -37,28 +39,11 @@ describe('helia', () => { }) it('stops and starts', async () => { - const startedInfo = await helia.info() - - expect(startedInfo).to.have.property('status', 'running') - expect(startedInfo).to.have.property('protocols') - .with.property('length').that.is.greaterThan(0) + expect(helia.libp2p.isStarted()).to.be.true() await helia.stop() - const stoppedInfo = await helia.info() - - expect(stoppedInfo).to.have.property('status', 'stopped') - expect(stoppedInfo).to.have.property('protocols') - .with.lengthOf(0) - }) - - it('returns node information', async () => { - const info = await helia.info() - - expect(info).to.have.property('peerId').that.is.ok() - expect(info).to.have.property('multiaddrs').that.is.an('array') - expect(info).to.have.property('agentVersion').that.is.a('string') - expect(info).to.have.property('protocolVersion').that.is.a('string') + expect(helia.libp2p.isStarted()).to.be.false() }) it('should have a blockstore', async () => { @@ -92,8 +77,23 @@ describe('helia', () => { }) }) - const info = await helia.info() + expect(helia.libp2p.isStarted()).to.be.false() + }) + + it('does not require any constructor args', async () => { + const helia = await createHelia() + + const cid = CID.parse('QmaQwYWpchozXhFv8nvxprECWBSCEppN9dfd2VQiJfRo3F') + const block = Uint8Array.from([0, 1, 2, 3]) + await helia.blockstore.put(cid, block) + await expect(helia.blockstore.has(cid)).to.eventually.be.true() + + const key = new Key(`/${cid.toString()}`) + await helia.datastore.put(key, block) + await expect(helia.datastore.has(key)).to.eventually.be.true() - expect(info).to.have.property('status', 'stopped') + expect(() => { + helia.libp2p.isStarted() + }).to.throw('Please configure Helia with a libp2p instance') }) }) diff --git a/packages/interface/src/index.ts b/packages/interface/src/index.ts index 92dd9e235..9fd119446 100644 --- a/packages/interface/src/index.ts +++ b/packages/interface/src/index.ts @@ -18,7 +18,6 @@ import type { Libp2p } from '@libp2p/interface-libp2p' import type { Blockstore } from 'interface-blockstore' import type { AbortOptions } from '@libp2p/interfaces' import type { PeerId } from '@libp2p/interface-peer-id' -import type { Multiaddr } from '@multiformats/multiaddr' import type { Datastore } from 'interface-datastore' import type { Pins } from './pins.js' import type { ProgressEvent, ProgressOptions } from 'progress-events' @@ -48,22 +47,6 @@ export interface Helia { */ pins: Pins - /** - * Returns information about this node - * - * @example - * - * ```typescript - * import { createHelia } from 'helia' - * - * const node = await createHelia() - * const id = await node.info() - * console.info(id) - * // { peerId: PeerId(12D3Foo), ... } - * ``` - */ - info: (options?: InfoOptions) => Promise - /** * Starts the Helia node */ @@ -94,35 +77,3 @@ export interface InfoOptions extends AbortOptions { */ peerId?: PeerId } - -export interface InfoResponse { - /** - * The ID of the peer this info is about - */ - peerId: PeerId - - /** - * The multiaddrs the peer is listening on - */ - multiaddrs: Multiaddr[] - - /** - * The peer's reported agent version - */ - agentVersion: string - - /** - * The peer's reported protocol version - */ - protocolVersion: string - - /** - * The protocols the peer supports - */ - protocols: string[] - - /** - * The status of the node - */ - status: 'running' | 'stopped' -}