Skip to content

block API from #35 #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/basics-browser.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @ts-check

import * as base64 from './bases/base64-browser.js'
import { CID, hasher, digest, varint, bytes, hashes, codecs, bases as _bases } from './basics.js'
import { CID, Block, hasher, digest, varint, bytes, hashes, codecs, bases as _bases } from './basics.js'

const bases = { ..._bases, ...base64 }

export { CID, hasher, digest, varint, bytes, hashes, codecs, bases }
export { CID, Block, hasher, digest, varint, bytes, hashes, codecs, bases }
4 changes: 2 additions & 2 deletions src/basics-import.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import { CID, hasher, digest, varint, bytes, hashes, codecs, bases as _bases } from './basics.js'
import { CID, Block, hasher, digest, varint, bytes, hashes, codecs, bases as _bases } from './basics.js'
import * as base64 from './bases/base64-import.js'

const bases = { ..._bases, ...base64 }
export { CID, hasher, digest, varint, bytes, hashes, codecs, bases }
export { CID, Block, hasher, digest, varint, bytes, hashes, codecs, bases }
4 changes: 2 additions & 2 deletions src/basics.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import * as sha2 from './hashes/sha2.js'
import raw from './codecs/raw.js'
import json from './codecs/json.js'

import { CID, hasher, digest, varint, bytes } from './index.js'
import { CID, Block, hasher, digest, varint, bytes } from './index.js'

const bases = { ...base32, ...base58 }
const hashes = { ...sha2 }
const codecs = { raw, json }

export { CID, hasher, digest, varint, bytes, hashes, bases, codecs }
export { CID, Block, hasher, digest, varint, bytes, hashes, bases, codecs }
308 changes: 308 additions & 0 deletions src/block.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
// @ts-check

import CID from './cid.js'

/**
* @template {number} Code
* @template T
* @class
*/
export default class Block {
/**
* @param {CID|null} cid
* @param {Code} code
* @param {T} data
* @param {Uint8Array} bytes
* @param {BlockConfig} config
*/
constructor (cid, code, data, bytes, { hasher }) {
/** @type {CID|Promise<CID>|null} */
this._cid = cid
this.code = code
this.data = data
this.bytes = bytes
this.hasher = hasher
}

async cid () {
const { _cid: cid } = this
if (cid != null) {
return await cid
} else {
const { bytes, code, hasher } = this
// First we store promise to avoid a race condition if cid is called
// whlie promise is pending.
const promise = createCID(hasher, bytes, code)
this._cid = promise
const cid = await promise
// Once promise resolves we store an actual CID.
this._cid = cid
return cid
}
}

links () {
return links(this.data, [])
}

tree () {
return tree(this.data, [])
}

/**
* @param {string} path
*/
get (path) {
return get(this.data, path.split('/').filter(Boolean))
}

/**
* @template {number} Code
* @template T
* @param {Encoder<Code, T>} codec
* @param {BlockConfig} options
*/
static encoder (codec, options) {
return new BlockEncoder(codec, options)
}

/**
* @template {number} Code
* @template T
* @param {Decoder<Code, T>} codec
* @param {BlockConfig} options
*/
static decoder (codec, options) {
return new BlockDecoder(codec, options)
}

/**
* @template {number} Code
* @template T
* @param {Object} codec
* @param {Encoder<Code, T>} codec.encoder
* @param {Decoder<Code, T>} codec.decoder
* @param {BlockConfig} options
* @returns {BlockCodec<Code, T>}
*/

static codec ({ encoder, decoder }, options) {
return new BlockCodec(encoder, decoder, options)
}
}

/**
* @template T
* @param {T} source
* @param {Array<string|number>} base
* @returns {Iterable<[string, CID]>}
*/
const links = function * (source, base) {
for (const [key, value] of Object.entries(source)) {
const path = [...base, key]
if (value != null && typeof value === 'object') {
if (Array.isArray(value)) {
for (const [index, element] of value.entries()) {
const elementPath = [...path, index]
const cid = CID.asCID(element)
if (cid) {
yield [elementPath.join('/'), cid]
} else if (typeof element === 'object') {
yield * links(element, elementPath)
}
}
} else {
const cid = CID.asCID(value)
if (cid) {
yield [path.join('/'), cid]
} else {
yield * links(value, path)
}
}
}
}
}

/**
* @template T
* @param {T} source
* @param {Array<string|number>} base
* @returns {Iterable<string>}
*/
const tree = function * (source, base) {
for (const [key, value] of Object.entries(source)) {
const path = [...base, key]
yield path.join('/')
if (value != null && typeof value === 'object' && !CID.asCID(value)) {
if (Array.isArray(value)) {
for (const [index, element] of value.entries()) {
const elementPath = [...path, index]
yield elementPath.join('/')
if (typeof element === 'object' && !CID.asCID(element)) {
yield * tree(element, elementPath)
}
}
} else {
yield * tree(value, path)
}
}
}
}

/**
* @template T
* @param {T} source
* @param {string[]} path
*/
const get = (source, path) => {
let node = source
for (const [index, key] of path.entries()) {
node = node[key]
if (node == null) {
throw new Error(`Object has no property at ${path.slice(0, index - 1).map(part => `[${JSON.stringify(part)}]`).join('')}`)
}
const cid = CID.asCID(node)
if (cid) {
return { value: cid, remaining: path.slice(index).join('/') }
}
}
return { value: node }
}

/**
*
* @param {Hasher} hasher
* @param {Uint8Array} bytes
* @param {number} code
*/

const createCID = async (hasher, bytes, code) => {
const multihash = await hasher.digest(bytes)
return CID.createV1(code, multihash)
}

/**
* @template {number} Code
* @template T
*/
class BlockCodec {
/**
* @param {Encoder<Code, T>} encoder
* @param {Decoder<Code, T>} decoder
* @param {BlockConfig} config
*/

constructor (encoder, decoder, config) {
this.encoder = new BlockEncoder(encoder, config)
this.decoder = new BlockDecoder(decoder, config)
this.config = config
}

/**
* @param {Uint8Array} bytes
* @param {Partial<BlockConfig>} [options]
* @returns {Block<Code, T>}
*/
decode (bytes, options) {
return this.decoder.decode(bytes, { ...this.config, ...options })
}

/**
* @param {T} data
* @param {Partial<BlockConfig>} [options]
* @returns {Block<Code, T>}
*/
encode (data, options) {
return this.encoder.encode(data, { ...this.config, ...options })
}
}

/**
* @class
* @template {number} Code
* @template T
*/
class BlockEncoder {
/**
* @param {Encoder<Code, T>} codec
* @param {BlockConfig} config
*/
constructor (codec, config) {
this.codec = codec
this.config = config
}

/**
* @param {T} data
* @param {Partial<BlockConfig>} [options]
* @returns {Block<Code, T>}
*/
encode (data, options) {
const { codec } = this
const bytes = codec.encode(data)
return new Block(null, codec.code, data, bytes, { ...this.config, ...options })
}
}

/**
* @class
* @template {number} Code
* @template T
*/
class BlockDecoder {
/**
* @param {Decoder<Code, T>} codec
* @param {BlockConfig} config
*/
constructor (codec, config) {
this.codec = codec
this.config = config
}

/**
* @param {Uint8Array} bytes
* @param {Partial<BlockConfig>} [options]
* @returns {Block<Code, T>}
*/
decode (bytes, options) {
const data = this.codec.decode(bytes)
return new Block(null, this.codec.code, data, bytes, { ...this.config, ...options })
}
}
/**
* @typedef {import('./block/interface').Config} BlockConfig
* @typedef {import('./hashes/interface').MultihashHasher} Hasher
**/

/**
* @template T
* @typedef {import('./bases/interface').MultibaseEncoder<T>} MultibaseEncoder
*/

/**
* @template T
* @typedef {import('./bases/interface').MultibaseDecoder<T>} MultibaseDecoder
*/

/**
* @template T
* @typedef {import('./bases/interface').MultibaseCodec<T>} MultibaseCodec
*/

/**
* @template {number} Code
* @template T
* @typedef {import('./codecs/interface').BlockEncoder<Code, T>} Encoder
*/

/**
* @template {number} Code
* @template T
* @typedef {import('./codecs/interface').BlockDecoder<Code, T>} Decoder
*/

/**
* @template {number} Code
* @template T
* @typedef {import('./codecs/interface').BlockCodec<Code, T>} Codec
*/
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// @ts-check

import CID from './cid.js'
import Block from './block.js'
import * as varint from './varint.js'
import * as bytes from './bytes.js'
import * as hasher from './hashes/hasher.js'
import * as digest from './hashes/digest.js'
import * as codec from './codecs/codec.js'

export { CID, hasher, digest, varint, bytes, codec }
export { CID, Block, hasher, digest, varint, bytes, codec }