@ipld/car
Version:
Content Addressable aRchive format reader and writer
195 lines (181 loc) • 5.53 kB
JavaScript
import { asyncIterableReader, bytesReader, createDecoder } from './decoder.js'
/**
* @typedef {import('multiformats').CID} CID
* @typedef {import('./api').Block} Block
* @typedef {import('./api').CarReader} CarReaderIface
* @typedef {import('./coding').BytesReader} BytesReader
* @typedef {import('./coding').CarHeader} CarHeader
* @typedef {import('./coding').CarV2Header} CarV2Header
*/
/**
* Provides blockstore-like access to a CAR.
*
* Implements the `RootsReader` interface:
* {@link CarReader.getRoots `getRoots()`}. And the `BlockReader` interface:
* {@link CarReader.get `get()`}, {@link CarReader.has `has()`},
* {@link CarReader.blocks `blocks()`} (defined as a `BlockIterator`) and
* {@link CarReader.cids `cids()`} (defined as a `CIDIterator`).
*
* Load this class with either `import { CarReader } from '@ipld/car/reader'`
* (`const { CarReader } = require('@ipld/car/reader')`). Or
* `import { CarReader } from '@ipld/car'` (`const { CarReader } = require('@ipld/car')`).
* The former will likely result in smaller bundle sizes where this is
* important.
*
* @name CarReader
* @class
* @implements {CarReaderIface}
* @property {number} version The version number of the CAR referenced by this
* reader (should be `1` or `2`).
*/
export class CarReader {
/**
* @constructs CarReader
* @param {CarHeader|CarV2Header} header
* @param {Block[]} blocks
*/
constructor (header, blocks) {
this._header = header
this._blocks = blocks
this._keys = blocks.map((b) => b.cid.toString())
}
/**
* @property
* @memberof CarReader
* @instance
*/
get version () {
return this._header.version
}
/**
* Get the list of roots defined by the CAR referenced by this reader. May be
* zero or more `CID`s.
*
* @function
* @memberof CarReader
* @instance
* @async
* @returns {Promise<CID[]>}
*/
async getRoots () {
return this._header.roots
}
/**
* Check whether a given `CID` exists within the CAR referenced by this
* reader.
*
* @function
* @memberof CarReader
* @instance
* @async
* @param {CID} key
* @returns {Promise<boolean>}
*/
async has (key) {
return this._keys.indexOf(key.toString()) > -1
}
/**
* Fetch a `Block` (a `{ cid:CID, bytes:Uint8Array }` pair) from the CAR
* referenced by this reader matching the provided `CID`. In the case where
* the provided `CID` doesn't exist within the CAR, `undefined` will be
* returned.
*
* @function
* @memberof CarReader
* @instance
* @async
* @param {CID} key
* @returns {Promise<Block | undefined>}
*/
async get (key) {
const index = this._keys.indexOf(key.toString())
return index > -1 ? this._blocks[index] : undefined
}
/**
* Returns a `BlockIterator` (`AsyncIterable<Block>`) that iterates over all
* of the `Block`s (`{ cid:CID, bytes:Uint8Array }` pairs) contained within
* the CAR referenced by this reader.
*
* @function
* @memberof CarReader
* @instance
* @async
* @generator
* @returns {AsyncGenerator<Block>}
*/
async * blocks () {
for (const block of this._blocks) {
yield block
}
}
/**
* Returns a `CIDIterator` (`AsyncIterable<CID>`) that iterates over all of
* the `CID`s contained within the CAR referenced by this reader.
*
* @function
* @memberof CarReader
* @instance
* @async
* @generator
* @returns {AsyncGenerator<CID>}
*/
async * cids () {
for (const block of this._blocks) {
yield block.cid
}
}
/**
* Instantiate a {@link CarReader} from a `Uint8Array` blob. This performs a
* decode fully in memory and maintains the decoded state in memory for full
* access to the data via the `CarReader` API.
*
* @async
* @static
* @memberof CarReader
* @param {Uint8Array} bytes
* @returns {Promise<CarReader>}
*/
static async fromBytes (bytes) {
if (!(bytes instanceof Uint8Array)) {
throw new TypeError('fromBytes() requires a Uint8Array')
}
return decodeReaderComplete(bytesReader(bytes))
}
/**
* Instantiate a {@link CarReader} from a `AsyncIterable<Uint8Array>`, such as
* a [modern Node.js stream](https://nodejs.org/api/stream.html#stream_streams_compatibility_with_async_generators_and_async_iterators).
* This performs a decode fully in memory and maintains the decoded state in
* memory for full access to the data via the `CarReader` API.
*
* Care should be taken for large archives; this API may not be appropriate
* where memory is a concern or the archive is potentially larger than the
* amount of memory that the runtime can handle.
*
* @async
* @static
* @memberof CarReader
* @param {AsyncIterable<Uint8Array>} asyncIterable
* @returns {Promise<CarReader>}
*/
static async fromIterable (asyncIterable) {
if (!asyncIterable || !(typeof asyncIterable[Symbol.asyncIterator] === 'function')) {
throw new TypeError('fromIterable() requires an async iterable')
}
return decodeReaderComplete(asyncIterableReader(asyncIterable))
}
}
/**
* @private
* @param {BytesReader} reader
* @returns {Promise<CarReader>}
*/
export async function decodeReaderComplete (reader) {
const decoder = createDecoder(reader)
const header = await decoder.header()
const blocks = []
for await (const block of decoder.blocks()) {
blocks.push(block)
}
return new CarReader(header, blocks)
}
export const __browser = true