1.6.1 • Published 4 months ago

@hash-stream/pack v1.6.1

Weekly downloads
-
License
Apache-2.0 OR MIT
Repository
github
Last release
4 months ago

Install

npm install @hash-stream/pack

Usage

Creating Packs

When aiming to create packs (as CAR files) from a given Blob like (object with a ReadableStream), one can use createPacks function. It returns an object with a packStream Async Generator that yields verifiable CAR packs and a containingPromise Promise that resolves to a containingMultihash representing the blob.

import { createPacks } from '@hash-stream/pack'
import { base58btc } from 'multiformats/bases/base58'

async function main() {
  const blob = new Blob(['Hello, world!'])

  const { packStream, containingPromise } = createPacks(blob, { type: 'car' })

  const packs = []
  for await (const pack of packStream) {
    packs.push(pack)
    console.log(
      'Generated pack multihash (base58btc):',
      base58btc.encode(pack.multihash.bytes)
    )
    console.log('Generated pack bytes', pack.bytes)
  }

  const containingMultihash = await containingPromise
  console.log(
    'Containing multihash (base58btc):',
    base58btc.encode(containingMultihash.bytes)
  )
}

main().catch(console.error)

Writing Packs

Created packs from a given Blob like (object with a ReadableStream), can be stored into a given Pack Store, as well as optionally indexed if an index writer is provided.

import { base58btc } from 'multiformats/bases/base58'

// Using multiple level index writer implementation
import { MultipleLevelIndexWriter } from '@hash-stream/index/writer/multiple-level'
import { FSIndexStore } from '@hash-stream/index/store/fs'
import { PackWriter } from '@hash-stream/pack'
import { FSPackStore } from '@hash-stream/pack/store/fs' // Example file system store

async function main() {
  // Initialize the stores
  const indexStore = new FSIndexStore('/path/to/index-store')
  const packStore = new FsStore('/path/to/pack-store')

  // Initialize the index writer
  const indexWriter = new MultipleLevelIndexWriter(indexStore)
  const packWriter = new PackWriter(packStore, {
    indexWriter,
  })

  // Get Blob
  const blob = new Blob(['Hello, world!']) // Example BlobLike object

  // Write Blob as packs
  const { containingMultihash, packsMultihashes } = await packWriter.write(
    blob,
    {
      type: 'car',
      // example number of bytes blob can be sharded in different packs
      shardSize: 10_000_000,
    }
  )

  for (const packMultihash of packsMultihashes) {
    console.log(
      'Pack multihash (base58btc):',
      base58btc.encode(packMultihash.bytes)
    )
  }

  const containingMultihash = await containingPromise
  console.log(
    'Containing multihash (base58btc):',
    base58btc.encode(containingMultihash.bytes)
  )
}

main().catch(console.error)

Reading Packs

import { PackReader } from '@hash-stream/pack'
import { FSPackStore } from '@hash-stream/pack/store/fs' // Example file system store

async function main() {
  // Initialize the stores
  const packStore = new FSPackStore('/path/to/pack-store')

  // Initialize the pack reader
  const packReader = new PackReader(packStore)

  // Get pack multihash
  const packMultihash = // TODO

  const packs = []
  for await (const entry of packReader.stream(packMultihash)) {
    packs.push(entry)
  }

  // Can also fetch blobs inside a pack based on ranges provided
  const blobRanges = [
    {
      multihash: // TBD
      offset: 50,
      length: 300,
    },
    {
      multihash: // TBD
      offset: 370,
      length: 300,
    }
  ]

  const rangeEntries = []
  for await (const entry of packReader.stream(packMultihash, blobRanges)) {
    rangeEntries.push(entry)
  }
}

main().catch(console.error)

Stores

Exported Stores

This package already exports a few stores compatible with PackStore Interface:

  • File system store: store/fs.js
  • Memory store: store/memory.js
  • S3-like Cloud Object store: store/s3-like.js
  • Cloudflare worker bucket like: store/cf-worker-bucket.js

File system store

Stores records within the host file system, by providing the path for a directory.

import fs from 'fs'
import path from 'path'
import os from 'os'

import { FSPackStore } from '@hash-stream/pack/store/fs.js'

const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fs-pack-store'))
const packStore = new FSPackStore(tempDir)

Memory store

Stores records within a Map in memory. This is a good store to use for testing.

import { MemoryPackStore } from '@hash-stream/pack/store/memory.js'

const packStore = new MemoryPackStore()
S3-like Cloud Object store

Stores records using a S3 compatible Cloud Storage solution like S3 or R2.

import { S3Client } from '@aws-sdk/client-s3'
import { S3LikePackStore } from '@hash-stream/pack/store/s3-like'

const client = new S3Client({
  // TODO: setup client options according to target
})
const bucketName = 'pack-store'
const packStore = new S3LikePackStore({
  bucketName,
  client,
})
Cloudflare worker bucket like

Stores records using a Cloudflare worker bucket reference.

import { CloudflareWorkerBucketPackStore } from '@hash-stream/pack/store/s3-like'

// Worker bindings R2 Bucket
const bucket = // TODO

const packStore = new CloudflareWorkerBucketPackStore({
  bucket
})

Custom implementations

Given hash-stream provides a set of building blocks to run a HTTP server for content-addressable data, anyone is welcome to write new implementations for each of the building blocks based on their specifications. This library also exports a test suite to verify if the implementation will be comaptible with the remaining pieces. Here is how you can use it:

import { test } from '@hash-stream/pack/test'

// Run tests for a reader implementation
await test.reader(readerName, () => getNewReaderImplementation())

// Run tests for a writer implementation
await test.writer(readerName, () => getNewWriterImplementation())

Using a Custom Store

Other implementations of a Store may be implemented according to the storage backend intended. The Pack Store must implement the PackStore interface, or separately a PackStoreWriter and a PackStoreReader. A store must define the following methods:

export interface PackStore extends PackStoreWriter, PackStoreReader {}

export interface PackStoreWriter {
  /**
   * Stores a pack file.
   *
   * @param hash - The Multihash digest of the pack.
   * @param data - The pack file bytes.
   * @returns A promise that resolves when the pack file is stored.
   */
  put(hash: MultihashDigest, data: Uint8Array): Promise<void>
}

export interface PackStoreReader {
  /**
   * Retrieves bytes of a pack file by its multihash digest.
   *
   * @param hash - The Multihash digest of the pack.
   * @returns A promise that resolves with the pack file data or null if not found.
   */
  get(hash: MultihashDigest): Promise<Uint8Array | null>

  stream(
    targetMultihash: MultihashDigest,
    ranges?: Array<{
      offset?: number
      length?: number
      multihash: MultihashDigest
    }>
  ): AsyncIterable<VerifiableEntry>
}

Example: Implementing a Custom Store

class MemoryStore {
  constructor() {
    /** @type {Map<string, Uint8Array>} */
    this.storage = new Map()
  }

  async put(hash, data) {
    this.storage.set(hash.toString(), data)
  }

  async *stream(hash, ranges = []) {
    const key = hash.toString()
    const data = this.storage.get(key)
    if (!data) return

    if (ranges.length === 0) {
      yield { multihash: hash, bytes: data }
      return
    }

    for (const { multihash, offset, length } of ranges) {
      /* c8 ignore next 1 */
      const slice = data.slice(offset, length ? offset + length : undefined)
      yield { multihash, bytes: slice }
    }
  }
}

const store = new MemoryStore()

Relevant Specifications

Contributing

Feel free to join in. All welcome. Please open an issue!

License

Dual-licensed under MIT + Apache 2.0

1.6.1

4 months ago

1.6.0

5 months ago

1.5.1

5 months ago

1.5.0

5 months ago

1.4.1

5 months ago

1.4.0

6 months ago

1.3.2

6 months ago

1.3.1

6 months ago

1.3.0

6 months ago

1.2.0

6 months ago

1.1.1

6 months ago

1.1.0

6 months ago

1.0.0

6 months ago