1.6.1 • Published 8 months ago

@hash-stream/pack v1.6.1

Weekly downloads
-
License
Apache-2.0 OR MIT
Repository
github
Last release
8 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

8 months ago

1.6.0

9 months ago

1.5.1

9 months ago

1.5.0

9 months ago

1.4.1

9 months ago

1.4.0

10 months ago

1.3.2

10 months ago

1.3.1

10 months ago

1.3.0

10 months ago

1.2.0

10 months ago

1.1.1

11 months ago

1.1.0

11 months ago

1.0.0

11 months ago