Skip to content

Commit

Permalink
feat: align implementation with receipt 0.2 spec (#271)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gozala authored Mar 30, 2023
1 parent 5341416 commit aeea7e3
Show file tree
Hide file tree
Showing 18 changed files with 427 additions and 326 deletions.
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"dependencies": {
"@ipld/car": "^5.1.0",
"@ipld/dag-cbor": "^9.0.0",
"@ipld/dag-ucan": "^3.2.0",
"@ipld/dag-ucan": "^3.3.2",
"@ucanto/interface": "workspace:^",
"multiformats": "^11.0.0"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/car.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { base32 } from 'multiformats/bases/base32'
import { create as createLink } from './link.js'
import { sha256 } from 'multiformats/hashes/sha2'

export const name = 'CAR'

/** @type {API.MulticodecCode<0x0202, 'CAR'>} */
export const code = 0x0202

/**
Expand Down
90 changes: 65 additions & 25 deletions packages/core/src/dag.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { create as createLink } from './link.js'
import { sha256 } from 'multiformats/hashes/sha2'
import * as MF from 'multiformats/interface'
import * as CBOR from './cbor.js'
import { identity } from 'multiformats/hashes/identity'

/**
* Function takes arbitrary value and if it happens to be an `IPLDView`
* it will iterate over it's blocks. It is just a convenience for traversing
* arbitrary structures that may contain `IPLDView`s in them.
* arbitrary structures that may contain `IPLDView`s in them.
* Note if you pass anything other than `IPLDView` it will not attempt
* to find views nested inside them, instead it will just emit no blocks.
*
Expand Down Expand Up @@ -36,21 +37,76 @@ export const iterate = function* (value) {
*/
export const createStore = () => new Map()

/** @type {API.MulticodecCode<typeof identity.code, typeof identity.name>} */
const EMBED_CODE = identity.code

/**
* Gets block corresponding to the given CID from the store. If store does not
* contain the block, `fallback` is returned. If `fallback` is not provided, it
* will throw an error.
*
* @template {T} U
* @template T
* @template [E=never]
* @param {API.Link<U>} cid
* @param {BlockStore<T>} store
* @param {E} [fallback]
* @returns {API.Block<U>|E}
*/
export const get = (cid, store, fallback) => {
// If CID uses identity hash, we can return the block data directly
if (cid.multihash.code === EMBED_CODE) {
return { cid, bytes: cid.multihash.digest }
}

const block = /** @type {API.Block<U>|undefined} */ (store.get(`${cid}`))
return block ? block : fallback === undefined ? notFound(cid) : fallback
}

/**
* @template T
* @template {T} U
* @param {U} source
* @template {API.MulticodecCode} [C=API.MulticodecCode<typeof CBOR.code, typeof CBOR.name>]
* @param {object} options
* @param {MF.BlockEncoder<C, U>} [options.codec]
* @returns {API.Block<U, C, typeof EMBED_CODE> & { data: U }}
*/
export const embed = (source, { codec } = {}) => {
const encoder = /** @type {MF.BlockEncoder<C, U>} */ (codec || CBOR)
const bytes = encoder.encode(source)
const digest = identity.digest(bytes)
return {
cid: createLink(encoder.code, digest),
bytes,
data: source,
}
}

/**
* @param {API.Link} link
* @returns {never}
*/
const notFound = link => {
throw new Error(`Block for the ${link} is not found`)
}

/**
* @template T
* @template {T} U
* @template {API.MulticodecCode} C
* @template {API.MulticodecCode} A
* @param {U} source
* @param {BlockStore<T>} store
* @param {object} options
* @param {MF.BlockEncoder<number, U>} [options.codec]
* @param {MF.MultihashHasher} [options.hasher]
* @returns {Promise<API.Block<U> & { data: U }>}
* @param {MF.BlockEncoder<C, unknown>} [options.codec]
* @param {MF.MultihashHasher<A>} [options.hasher]
* @returns {Promise<API.Block<U, C, A> & { data: U }>}
*/
export const encodeInto = async (
source,
store,
{ codec = CBOR, hasher = sha256 } = {}
) => {
export const writeInto = async (source, store, options = {}) => {
const codec = /** @type {MF.BlockEncoder<C, U>} */ (options.codec || CBOR)
const hasher = /** @type {MF.MultihashHasher<A>} */ (options.hasher || sha256)

const bytes = codec.encode(source)
const digest = await hasher.digest(bytes)
/** @type {API.Link<U, typeof codec.code, typeof hasher.code>} */
Expand Down Expand Up @@ -90,19 +146,3 @@ export const addEveryInto = (source, store) => {
addInto(block, store)
}
}

/**
* @template T
* @param {API.Link<T>} link
* @param {BlockStore<T>} store
* @returns {API.Block<T> & { data: T }}
*/
export const decodeFrom = (link, store) => {
const block = store.get(`${link}`)
/* c8 ignore next 3 */
if (!block) {
throw new Error(`Block for the ${link} is not found`)
}
const data = /** @type {T} */ (CBOR.decode(block.bytes))
return { cid: link, bytes: block.bytes, data }
}
15 changes: 15 additions & 0 deletions packages/core/src/delegation.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as UCAN from '@ipld/dag-ucan'
import * as API from '@ucanto/interface'
import * as Link from './link.js'
import * as DAG from './dag.js'

/**
* @deprecated
Expand Down Expand Up @@ -413,6 +414,20 @@ export const importDAG = dag => {
*/
export const create = ({ root, blocks }) => new Delegation(root, blocks)

/**
* @template {API.Capabilities} C
* @template [T=undefined]
* @param {object} dag
* @param {API.UCANLink<C>} dag.root
* @param {Map<string, API.Block>} dag.blocks
* @param {T} [fallback]
* @returns {API.Delegation<C>|T}
*/
export const view = ({ root, blocks }, fallback) => {
const block = DAG.get(root, blocks, null)
return block ? create({ root: block, blocks }) : /** @type {T} */ (fallback)
}

/**
* @param {API.Delegation} delegation
*/
Expand Down
36 changes: 20 additions & 16 deletions packages/core/src/invocation.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,37 @@ import * as DAG from './dag.js'
export const invoke = options => new IssuedInvocation(options)

/**
* Takes a link of the `root` block and a map of blocks and constructs an
* `Invocation` from it. If `root` is not included in the provided blocks it
* throws an error. If root points to wrong block (that is not an invocation)
* it will misbehave and likely throw some errors on field access.
*
* @template {API.Capability} C
* @param {object} dag
* @param {API.UCANLink<[C]>} dag.root
* @param {Map<string, API.Block>} dag.blocks
* @param {API.UCANBlock<[C]>} dag.root
* @param {Map<string, API.Block<unknown>>} [dag.blocks]
* @returns {API.Invocation<C>}
*/
export const view = ({ root, blocks }) => {
const { bytes, cid } = DAG.decodeFrom(root, blocks)
return new Invocation({ bytes, cid }, blocks)
}
export const create = ({ root, blocks }) => new Invocation(root, blocks)

/**
* Takes a link of the `root` block and a map of blocks and constructs an
* `Invocation` from it. If `root` is not included in the provided blocks
* provided fallback is returned and if not provided than throws an error.
* If root points to wrong block (that is not an invocation) it will misbehave
* and likely throw some errors on field access.
*
* @template {API.Invocation} Invocation
* @template [T=undefined]
* @param {object} dag
* @param {ReturnType<Invocation['link']>} dag.root
* @param {Map<string, API.Block>} dag.blocks
* @returns {Invocation|ReturnType<Invocation['link']>}
* @param {T} [fallback]
* @returns {Invocation|T}
*/
export const embed = ({ root, blocks }) =>
blocks.has(root.toString())
? /** @type {Invocation} */ (view({ root, blocks }))
: root
export const view = ({ root, blocks }, fallback) => {
const block = DAG.get(root, blocks, null)
const view = block
? /** @type {Invocation} */ (create({ root: block, blocks }))
: /** @type {T} */ (fallback)

return view
}

/**
* @template {API.Capability} Capability
Expand Down
Loading

0 comments on commit aeea7e3

Please sign in to comment.