A js object to IPFS merkle dag mapper
Merkling is a small library for interacting with the IPLD directed acyclic graph (DAG) in js as if it were an in-memory data structure. The idea is to introduce an exciting new impedance mismatch to the world.
This is alpha software
At this stage Merkling is only an exploration of different IPLD api.
IPLD (Interplanetary Linked Data) is the graph data strucuture that underpins IPFS and its file sharing capabilities. In IPFS it is used for modelling a filesystem, but it is capable of modelling more fine grained data structures.
Being able to store and syncronise a complex graph of data between many clients with the trust guarantees that content addressing provides makes IPLD an attractive option for building dapps. However the IPLD api is low level.
Merkling is an attempt at a different api borrowing from the world of ORMs (nothing could possibly go wrong). The idea is to use ES6 proxies to seamlessly confuse developers with the illusion that IPLD blocks are JS objects and that edges from IPLD block to IPLD block are JS object references.
See the usage section for an example.
npm install --save merkling@alpha
Merkling requires pulling an ipfs instance, either through js-ipfs or ipfs-http-client
const Merkling = require('merkling')
var ipfsClient = require('ipfs-http-client')
var ipfs = ipfsClient('/ip4/127.0.0.1/tcp/5001')
const main = async () => {
const merkling = new Merkling({ ipfs })
// Create an IPLD graph
let feedCid
await merkling.withSession(async session => {
// Create an IPLD block in-memory
// (so not persisted to IPFS)
const feed = session.create({
title: 'Thoughts',
author: 'anon',
posts: []
})
// ... and create a few more IPLD blocks
const post1 = session.create({
text: 'A beginning'
})
const post2 = session.create({
text: 'A middle'
})
const post3 = session.create({
text: 'An end'
})
// Manipulate and create relationships between
// them as if they were JS objects
post1.next = post2
post2.next = post3
feed.posts = [post1, post2, post3]
// Persist all IPLD blocks in the session
// in the right order and with their links
// [feed] --> [post1]
// | |
// |------> [post2]
// | |
// |------> [post3]
await session.save()
// Print the CID of the root IPLD block
// of the graph
feedCid = Merkling.cid(feed)
console.log(`Original Feed CID: ${feedCid.toBaseEncodedString()}`)
})
// Update the IPLD graph, we persisted in a
// new session
let updateFeedCid
await merkling.withSession(async session => {
// Read the root IPLD block from IPFS,
// its linked IPLD nodes will be unloaded
// proxies
const savedFeed = await session.get(feedCid)
// Force the loading from IPFS of the linked posts
for (const post of savedFeed.posts) {
await Merkling.resolve(post)
}
// Update a subnode of the root
savedFeed.posts[1].text = 'A longer middle'
// Persist the changes
await session.save()
// Print the updated CID of the Root IPLD block
updateFeedCid = Merkling.cid(savedFeed)
console.log(`Updated Feed CID: ${updateFeedCid.toBaseEncodedString()}`)
})
// Print the new updated graph
await merkling.withSession(async session => {
const savedFeed = await session.get(updateFeedCid)
console.log('')
console.log(`Title: ${savedFeed.title} by ${savedFeed.author}`)
console.log('---------------------')
for (const post of savedFeed.posts) {
await Merkling.resolve(post)
console.log(post.text)
}
console.log('')
})
}
main()
Which will produce the output:
Feed CID: zBwWX9pGzXr5vkRELYnJGvcmtK2JcH2Cw43JJB8XZywzq2eRKiP6W31yuSjb43K7TvY9bBm2WHqLCQNbscxPmkUsJWtqV
Updated Feed CID: zBwWX54YgyC83zywoo9E63i7k3DAx72KMCEK6ZXrweEg6BECnjg7eszhH6gfzuGrkRKA7YHBmLnNMRdHhpPiL5Trc87UC
Title: Thoughts by anon
---------------------
A beginning
A longer middle
An end
The core IPLD access client.
options
IMerklingOptions
Create a new merkling session to act as an intermediary with the IPFS node.
Returns MerklingSession a new merkling session
Run an action in the form of a callback over a new session that is disposed of afterwards.
sessionAction
function (session: MerklingSession): void an action func that operates on the new session
Returns Promise<void> a promise that completes after the session action
Retrieve the CID for given proxy. Will returned undefined if the passed object is not a Merkling proxy and will return null if the proxy is dirty.
proxy
Proxy the object to get the CID for
Returns (ICid | null | undefined) the CID or undefined if the object will never have a CID or null if it is currently dirty
Asynchronously force the retrieval of the state stored in IPFS of the IPLD block the proxy represents.
proxy
Proxy An IPLD block proxy
Returns Promise<Proxy> the given proxy now loaded and clean.
Retreive the stored state that the Merkling proxy is representing. This will include references to other Merkling proxies.
proxy
Proxy the proxy to inspect
Returns {} the internal state of the IPLD block being represented
Determine whether a given object is a tracked Merkling proxy.
potentialProxy
Proxy
Returns boolean the answer
Determine whether a given object is a Merkling proxy representing an IPLD block.
potentialIpldNode
Proxy
Returns boolean the answer
Determine if a proxy has unsaved changes. Will return true if the passed object is not a proxy.
proxy
Proxy the object to test
Returns boolean the answer
$0
{ipfs: IIpfsNode}$0.ipfs
Create a new dirty Merkling proxy that can be manipulated before later being persisted.
objState
T a js object of the state for the IPLD block
Returns T a proxy representing the given state as an IPLD block
Get an IPLD block from IPFS and return it as a tracked Merkling proxy.
hashOrCid
(string | ICid) a CID or base encoded version
Returns Promise<any> a clean proxy with the state loaded and accessible
hash
(string | ICid)
Returns IMerklingProxyState
Save all proxies that are currently marked as dirty, and any proxies that depend on them.
Returns Promise<void>
Merkling requires ES6 Proxies to do its black magic, hence requires a recent version of node or an ever green browser.
PRs accepted.
Small note: If editing the README, please conform to the standard-readme specification.
MIT © 2019 John Kane