ipfs-core
Version:
JavaScript implementation of the IPFS specification
117 lines (100 loc) • 3.17 kB
JavaScript
import { CID } from 'multiformats/cid'
import { createUnsafe } from 'multiformats/block'
import { CarWriter } from '@ipld/car/writer'
import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'
import { logger } from '@libp2p/logger'
import * as raw from 'multiformats/codecs/raw'
import * as json from 'multiformats/codecs/json'
import { walk } from 'multiformats/traversal'
const log = logger('ipfs:components:dag:import')
// blocks that we're OK with not inspecting for links
/** @type {number[]} */
const NO_LINKS_CODECS = [
raw.code, // raw
json.code // JSON
]
/**
* @typedef {import('../../types').Preload} Preload
* @typedef {import('ipfs-repo').IPFSRepo} IPFSRepo
* @typedef {import('@ipld/car/api').BlockWriter} BlockWriter
* @typedef {import('ipfs-core-types/src/utils').AbortOptions} AbortOptions
*/
/**
* @template T
* @template C
* @template A
* @template V
* @typedef {import('multiformats/block').Block<T, C, A, V>} Block
*/
/**
* @param {object} config
* @param {IPFSRepo} config.repo
* @param {Preload} config.preload
* @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs
*/
export function createExport ({ repo, preload, codecs }) {
/**
* @type {import('ipfs-core-types/src/dag').API<{}>["export"]}
*/
async function * dagExport (root, options = {}) {
if (options.preload !== false) {
preload(root)
}
const cid = CID.asCID(root)
if (!cid) {
throw new Error(`Unexpected error converting CID type: ${root}`)
}
log(`Exporting ${cid} as car`)
const { writer, out } = await CarWriter.create([cid])
// we need to write with one async channel and send the CarWriter output
// with another to the caller, but if the write causes an error we capture
// that and make sure it gets propagated
/** @type {Error|null} */
let err = null
;(async () => {
try {
const load = makeLoader(repo, writer, {
signal: options.signal,
timeout: options.timeout
}, codecs)
await walk({ cid, load })
} catch (/** @type {any} */ e) {
err = e
} finally {
writer.close()
}
})()
for await (const chunk of out) {
if (err) {
break
}
yield chunk
}
if (err) {
throw err
}
}
return withTimeoutOption(dagExport)
}
/**
* @param {IPFSRepo} repo
* @param {BlockWriter} writer
* @param {AbortOptions} options
* @param {import('ipfs-core-utils/multicodecs').Multicodecs} codecs
* @returns {(cid:CID)=>Promise<ReturnType<createUnsafe>|null>}
*/
function makeLoader (repo, writer, options, codecs) {
return async (cid) => {
const codec = await codecs.getCodec(cid.code)
if (!codec) {
throw new Error(`Can't decode links in block with codec 0x${cid.code.toString(16)} to form complete DAG`)
}
const bytes = await repo.blocks.get(cid, options)
log(`Adding block ${cid} to car`)
await writer.put({ cid, bytes })
if (NO_LINKS_CODECS.includes(cid.code)) {
return null // skip this block, no need to look inside
}
return createUnsafe({ bytes, cid, codec })
}
}