Skip to content
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

feat: align implementation with receipt 0.2 spec #271

Merged
merged 15 commits into from
Mar 30, 2023
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