0.0.0 • Published 7 months ago

onchfs v0.0.0

Weekly downloads
-
License
MIT
Repository
-
Last release
7 months ago

Package API

As we are aiming for long-term adoption of the ONCHFS, we are looking at building a cohesive programming API for the official packages.

There are 3 different approaches:

Exporting nested objects for grouping similar APIs

Rational: we can granuarly define how we nest the different properties of the API to guide users in their usage (hierarchy defines level of abstractions).

import ONCHFS from "onchfs"

// preparing a file
const file = ONCHFS.files.prepareFile(...)

// preparing a directory
const dir = ONCHFS.files.prepareDirectory(...)

// generate the inscriptions from a directory
const inscriptions = ONCHFS.files.generateInscriptions(...)

// create a proxy-resolver
const resolver = ONCHFS.proxy.createResolver(...)

// resolve an URI
const components = ONCHFS.uri.parse(...)
const components = ONCHFS.uri.resolve(...)

// UNCOMMON OPERATIONS

// chunk bytes
const chunks = ONCHFS.files.utils.chunkBytes(...)

// encode/decode some metadata
const encoded = ONCHFS.files.utils.metadata.encode(...)
const decoded = ONCHFS.files.utils.metadata.decode(...)

Alternatively, we can use a similar approach and yet provide a way to import 1 level deep:

// gives access to the same API as above
import * as ONCHFS from "onchfs"

// possibility to import 1-deep nested APIs
import { files, uri, proxy } from "onchfs"

// ex

// preparing a file
const file = files.prepareFile(...)

// preparing a directory
const dir = files.prepareDirectory(...)

// generate the inscriptions from a directory
const inscriptions = files.generateInscriptions(...)

// create a proxy-resolver
const resolver = proxy.createResolver(...)

// resolve an URI
const components = uri.parse(...)
const components = uri.resolve(...)

// UNCOMMON OPERATIONS

// chunk bytes
const chunks = files.utils.chunkBytes(...)

// encode/decode some metadata
const encoded = files.utils.metadata.encode(...)
const decoded = files.utils.metadata.decode(...)

Pros:

  • we have full control over the API exposed
  • the file structure doesn't have to reflect the API, usage logic does

Cons:

  • uncommon operations are deeply nested, and we don't provide a way to import an atomic operation; the dot notation must be used to access "hidden" features
  • on VSCode, with autocomplete, everything shows as a property: it's hard to differenciate regular static properties from methods; maybe it can be solved with some import/export voodoo, didn't investigate.
  • need to bundle all the package even if we just want the URI resolution

Exposing everything at the top level

I won't describe more this strategy as I think it's not great for the DX. While a manual can be used to understand the different functions, there's no way developers understand which functions they are supposed to used, resulting in a mess of an API.

Multi-package architecture

We would divide ONCHFS into smaller packages which themselves give access to a subset of the features in a smaller scope.

import {
  prepareFile,
  prepareDirectory,
  generateInscriptions,
  metadata,
  utils
} from "@onchfs/files"
// alternatively
import * as ONCHFSFiles from "@onchfs/files"

// preparing a file
const file = prepareFile(...)

// preparing a directory
const dir = prepareDirectory(...)

// generate the inscriptions from a directory
const inscriptions = generateInscriptions(...)

// encode/decode some metadata
const encoded = metadata.encode(...)
const decoded = metadata.decode(...)


import { createProxyResolver } from "@onchfs/proxy"

// create a proxy resolver
const resolver = createProxyResolver(...)


import { parseURI } from "@onchfs/uri"

const components = parseURI(...)

Pros

  • clean API, it's easier to access the components we need in the app
  • type friendly; functions are functions (and not object properties)
  • bundle-optimized: consumers can only ship what they need for their app

Cons

  • DX a bit tedious sometimes when building a fullstack single app (multiple onchfs packages have to be imported)
  • harder to maintain for us: need to engineer finer solution for the deployment of the various modules (can be mitigated with a strong strategy)

More ideas ?

TODOs

Improving the API of the onchfs package

Consider various use-cases, readility, conciseness, etc..

Maybe divide into 2 APIs, accessible through a single core API ? If needed

  • files
  • resolver

Publish strategy

  • from monorepo
  • into different packages

Improvements to the URI

  • Before the CID, any number of / is accepted, however the URI should be normalized so that there are no / before the CID

API Improvements

/**
 * OUTPUT MODE
 * Possibility to instanciate onchfs object instead of using the main one to
 * get access to some top-level configuration, such as the output data type of
 * the most common operations.
 *
 * * This is optional
 *
 * * tbd if good idea, not sure; maybe we just use hex everywhere as backend on
 *   node can easily work with it, and front-ends won't really manipulate bytes
 *   (if an application requires to do so they can use onchfs.utils)
 */
const onchfs = new Onchfs({
  outputsEncoding: "uint8array", // other: "hex"
})

/**
 * PREPARING FILES
 * Polymorphic API for preparing files & directories, makes it more
 * straighforward and clear.
 */

// preparing file (uint8array, string)
const file = onchfs.files.prepare(bytes, filename)

// preparing a directory
const directory = onchfs.files.prepare([
  { path: "index.html", content: bytes0 },
  { path: "style.css", content: bytes1 },
  { path: "lib/main.js", content: bytes3 },
  { path: "lib/processing.min.js", content: bytes4 },
])

/**
 * Generating/optimizing inscriptions
 */

// in any case the output of the prepare command can be fed into the
// inscriptions function
// this will create an optimised list of inscriptions
const inscriptions = await onchfs.inscriptions.prepare(file, {
  // if an inode with such CID is found the optimizer will remove the relevant
  // inscriptions.
  getInode: async (cid) => {
    return await blockchainNode.getInode(cid)
  }
})

/**
 * Working with metadata
 */
const encoded = onchfs.metadata.encode(...)
const decoded = onchfs.metadata.decode(...)


/**
 * Writing a proxy
 */

const resolver = onchfs.resolver.create(
  // a list of resolver, order matters as if an URI without an authority has to
  // be resolved, each network will be tested until the resource is found on one
  [
    {
      blockchain: "tezos:mainnet",
      rpcs: ["https://rpc1.fxhash.xyz", "..."]
    },
    {
      blockchain: "tezos:ghostnet",
      rpcs: ["https://rpc1.fxhash-dev.xyz", "..."],
      // optional, the blockchain default one will be used by default
      contract: "KT..."
    },
  ]
)

app.use(async (req, res, next) => {
  const response = await resolver.resolve(req.path)
  // ...
})

// also possible to create a custom resolver with low-level primitives
const resolver = onchfs.resolver.custom({
  getInode: async (cid, path) => {
    // handle
  },
  getFile: async (cid) => {
    // handle
  }
})

/**
 * URI
 */

const components = onchfs.uri.parse("onchfs://...")