diff --git a/src/manifest.js b/src/manifest.js index faf87b4..cb2dd6e 100644 --- a/src/manifest.js +++ b/src/manifest.js @@ -2,30 +2,59 @@ import * as Block from 'multiformats/block' import * as dagCbor from '@ipld/dag-cbor' import { sha256 } from 'multiformats/hashes/sha2' import { base58btc } from 'multiformats/bases/base58' +import { ComposedStorage, IPFSBlockStorage, LRUStorage } from './storage/index.js' const codec = dagCbor const hasher = sha256 const hashStringEncoding = base58btc // Creates a DB manifest file and saves it in IPFS -export default async ({ storage, name, type, accessController, meta }) => { - if (!storage) throw new Error('storage is required') - if (!name) throw new Error('name is required') - if (!type) throw new Error('type is required') - if (!accessController) throw new Error('accessController is required') - - const manifest = Object.assign( - { - name, - type, - accessController - }, - // meta field is only added to manifest if meta parameter is defined - meta !== undefined ? { meta } : {} +const Manifest = async ({ ipfs, storage } = {}) => { + storage = storage || await ComposedStorage( + await LRUStorage({ size: 1000 }), + await IPFSBlockStorage({ ipfs, pin: true }) ) - const { cid, bytes } = await Block.encode({ value: manifest, codec, hasher }) - const hash = cid.toString(hashStringEncoding) - await storage.put(hash, bytes) - return { hash, manifest } + const get = async (address) => { + const bytes = await storage.get(address) + const { value } = await Block.decode({ bytes, codec, hasher }) + return value + } + + const create = async ({ name, type, accessController, meta }) => { + if (!name) throw new Error('name is required') + if (!type) throw new Error('type is required') + if (!accessController) throw new Error('accessController is required') + + const manifest = Object.assign( + { + name, + type, + accessController + }, + // meta field is only added to manifest if meta parameter is defined + meta !== undefined ? { meta } : {} + ) + + const { cid, bytes } = await Block.encode({ value: manifest, codec, hasher }) + const hash = cid.toString(hashStringEncoding) + await storage.put(hash, bytes) + + return { + hash, + manifest + } + } + + const close = async () => { + await storage.close() + } + + return { + get, + create, + close + } } + +export default Manifest diff --git a/src/orbitdb.js b/src/orbitdb.js index 20ef2a9..d218f77 100644 --- a/src/orbitdb.js +++ b/src/orbitdb.js @@ -1,18 +1,12 @@ import { Events, KeyValue, Documents } from './db/index.js' -import { ComposedStorage, IPFSBlockStorage, LRUStorage } from './storage/index.js' import KeyStore from './key-store.js' import { Identities } from './identities/index.js' import OrbitDBAddress, { isValidAddress } from './address.js' -import DBManifest from './manifest.js' +import Manifests from './manifest.js' import { createId } from './utils/index.js' import pathJoin from './utils/path-join.js' -import * as Block from 'multiformats/block' -import * as dagCbor from '@ipld/dag-cbor' -import { sha256 } from 'multiformats/hashes/sha2' import * as AccessControllers from './access-controllers/index.js' - -const codec = dagCbor -const hasher = sha256 +import IPFSAccessController from './access-controllers/ipfs.js' // Mapping for database types const databaseTypes = { @@ -28,6 +22,8 @@ const addDatabaseType = (type, store) => { databaseTypes[type] = store } +const DefaultAccessController = IPFSAccessController + const OrbitDB = async ({ ipfs, id, identity, keystore, directory } = {}) => { if (ipfs == null) { throw new Error('IPFS instance is a required argument. See https://github.com/orbitdb/orbit-db/blob/master/API.md#createinstance') @@ -40,10 +36,7 @@ const OrbitDB = async ({ ipfs, id, identity, keystore, directory } = {}) => { const identities = await Identities({ ipfs, keystore }) identity = identity || await identities.createIdentity({ id }) - const manifestStorage = await ComposedStorage( - await LRUStorage({ size: 1000 }), - await IPFSBlockStorage({ ipfs, pin: true }) - ) + const manifests = await Manifests({ ipfs }) let databases = {} @@ -61,23 +54,20 @@ const OrbitDB = async ({ ipfs, id, identity, keystore, directory } = {}) => { if (isValidAddress(address)) { // If the address given was a valid OrbitDB address, eg. '/orbitdb/zdpuAuK3BHpS7NvMBivynypqciYCuy2UW77XYBPUYRnLjnw13' const addr = OrbitDBAddress(address) - const bytes = await manifestStorage.get(addr.path) - const { value } = await Block.decode({ bytes, codec, hasher }) - manifest = value + manifest = await manifests.get(addr.path) const acType = manifest.accessController.split('/', 2).pop() - const acAddress = manifest.accessController.replaceAll(`/${acType}/`, '') - accessController = await AccessControllers.get(acType)()({ orbitdb: { open, identity, ipfs }, identities, address: acAddress }) + AccessController = AccessControllers.get(acType)() + accessController = await AccessController({ orbitdb: { open, identity, ipfs }, identities, address: acAddress }) name = manifest.name type = type || manifest.type meta = manifest.meta } else { // If the address given was not valid, eg. just the name of the database type = type || 'events' - AccessController = AccessController || AccessControllers.get('ipfs')({ storage: manifestStorage }) + AccessController = AccessController || DefaultAccessController() accessController = await AccessController({ orbitdb: { open, identity, ipfs }, identities }) - const m = await DBManifest({ storage: manifestStorage, name: address, type, accessController: accessController.address, meta }) - + const m = await manifests.create({ name: address, type, accessController: accessController.address, meta }) manifest = m.manifest address = OrbitDBAddress(m.hash) name = manifest.name @@ -89,11 +79,14 @@ const OrbitDB = async ({ ipfs, id, identity, keystore, directory } = {}) => { if (!Database) { throw new Error(`Unsupported database type: '${type}'`) } - const db = await Database({ ipfs, identity, address: address.toString(), name, access: accessController, directory, meta, syncAutomatically: sync != null ? sync : true, headsStorage, entryStorage, indexStorage, referencesCount }) - db.events.on('close', onDatabaseClosed(address.toString())) + address = address.toString() - databases[address.toString()] = db + const db = await Database({ ipfs, identity, address, name, access: accessController, directory, meta, syncAutomatically: sync, headsStorage, entryStorage, indexStorage, referencesCount }) + + db.events.on('close', onDatabaseClosed(address)) + + databases[address] = db return db } @@ -106,8 +99,8 @@ const OrbitDB = async ({ ipfs, id, identity, keystore, directory } = {}) => { if (keystore) { await keystore.close() } - if (manifestStorage) { - await manifestStorage.close() + if (manifests) { + await manifests.close() } databases = {} } diff --git a/test/manifest.test.js b/test/manifest.test.js index 934628e..fa81366 100644 --- a/test/manifest.test.js +++ b/test/manifest.test.js @@ -1,73 +1,76 @@ import { strictEqual, deepStrictEqual } from 'assert' import rmrf from 'rimraf' import * as IPFS from 'ipfs-core' -import Manifest from '../src/manifest.js' -import IPFSBlockStorage from '../src/storage/ipfs-block.js' +import Manifests from '../src/manifest.js' import config from './config.js' describe('Manifest', () => { const repo = './ipfs' let ipfs - let storage + let manifests before(async () => { ipfs = await IPFS.create({ ...config.daemon1, repo }) - storage = await IPFSBlockStorage({ ipfs }) + manifests = await Manifests({ ipfs }) }) after(async () => { - await storage.close() + await manifests.close() await ipfs.stop() await rmrf(repo) }) it('creates a manifest', async () => { - const name = 'manifest' - const type = 'manifest' + const name = 'database' + const type = 'keyvalue' const accessController = 'test/default-access-controller' - const expectedHash = 'zdpuAx3LaygjPHa2zsUmRoR4jQPm2WYrExsvz2gncfm62aRKv' + const expectedHash = 'zdpuAn26ookFToGNmpVHgEM71YMULiyS8mAs9UQtV1g6eEyRP' const expectedManifest = { name, type, accessController } - const { hash, manifest } = await Manifest({ storage, name, type, accessController }) + const { hash, manifest } = await manifests.create({ name, type, accessController }) strictEqual(hash, expectedHash) deepStrictEqual(manifest, expectedManifest) }) - it('creates a manifest with metadata', async () => { - const name = 'manifest' - const type = 'manifest' + it('loads a manifest', async () => { + const name = 'database' + const type = 'keyvalue' const accessController = 'test/default-access-controller' - const expectedHash = 'zdpuAmegd2PpDfTQRVhGiATCkWQDvp3JygT9WksWgJkG2u313' + const expectedHash = 'zdpuAn26ookFToGNmpVHgEM71YMULiyS8mAs9UQtV1g6eEyRP' + const expectedManifest = { + name, + type, + accessController + } + + const manifest = await manifests.get(expectedHash) + + deepStrictEqual(manifest, expectedManifest) + }) + + it('creates a manifest with metadata', async () => { + const name = 'database' + const type = 'keyvalue' + const accessController = 'test/default-access-controller' + const expectedHash = 'zdpuAyWPs4yAXS6W7CY4UM68pV2NCpzAJr98aMA4zS5XRq5ga' const meta = { name, description: 'more information about the database' } - const { hash, manifest } = await Manifest({ storage, name, type, accessController, meta }) + const { hash, manifest } = await manifests.create({ name, type, accessController, meta }) strictEqual(hash, expectedHash) deepStrictEqual(manifest.meta, meta) }) - it('throws an error if storage is not specified', async () => { - let err - - try { - await Manifest({}) - } catch (e) { - err = e.toString() - } - - strictEqual(err, 'Error: storage is required') - }) - it('throws an error if name is not specified', async () => { let err try { - await Manifest({ storage }) + await manifests.create({}) } catch (e) { err = e.toString() } @@ -79,7 +82,7 @@ describe('Manifest', () => { let err try { - await Manifest({ storage, name: 'manifest' }) + await manifests.create({ name: 'database' }) } catch (e) { err = e.toString() } @@ -91,7 +94,7 @@ describe('Manifest', () => { let err try { - await Manifest({ storage, name: 'manifest', type: 'manifest' }) + await manifests.create({ name: 'database', type: 'keyvalue' }) } catch (e) { err = e.toString() }