cafe-js

Content Addressable Function Environment

3 years after

Cafe JS

Content Addressable Function Environment

Inspired from Joe Armstrong's "Why do we need modules at all?" post. This is mostly an experiment to apply that idea to the JS module system where every function is referenced by a unique content addressable URI.

import fib from "urn:sha1:102e81953b29d9d59998daf6b9cecfa4dfa2f265";

fib(42);

(View the source of urn:sha1:102e81953b29d9d59998daf6b9cecfa4dfa2f265)

Installation

The loader only works in node.js at the moment, but it'd be cool to have it running as a ES6 Module Loader extension someday.

$ git clone https://github.com/josh/cafe-js
$ cd cafe-js/
$ make

Usage

From an ES6 file, import the resolver to add support for urn: to node's module loader. Then load a function from the db.

// hello.js
import "./path/to/cafe-js/lib/resolver";
import hello from "urn:sha1:0b9495f5a81146461dbfb5c1ecc1f311bf659d85";

console.log(hello())

Run the file with 6to5.

$ 6to5-node hello.js

Module Rules

  • Should use ES6 module syntax. CJS is much harder to statically analysis. 6to5 is a runtime dependency for this library.

  • Definitions should only contain static imports to other urn:sha1: functions and a single default function export.

import fib from "urn:sha1:102e81953b29d9d59998daf6b9cecfa4dfa2f265";
import square from "urn:sha1:d5eec40e8de115d86b2070b921bab16c3cabb7b1";

export default function() {
  return fib(square(2));
};
  • Definitions may export immutable values besides functions. Exposing mutable objects may have undefined behaviors.
export default "Hello, World!";
  • Modules may still be simulated by exporting a named object. Though, is discouraged. Prefer exporting the minimal function defintion.
import fib from "urn:sha1:102e81953b29d9d59998daf6b9cecfa4dfa2f265";
import square from "urn:sha1:d5eec40e8de115d86b2070b921bab16c3cabb7b1";

export default { fib, square };
  • Definitions should be normalized to maximize deduplication. I haven't quite finished the code for this, but ideally the functions will be parsed into an AST and outputted in a normalized format.

Register new functions

// hello.js
export default function(name) {
  return "Hello, " + name;
};
$ ./bin/hash-fn ./hello.js
urn:sha1:aff2b576a423310a0c74797ca4a1509ee9de3745

$ git show aff2b576a423310a0c74797ca4a1509ee9de3745
export default function(name) {
  return "Hello, " + name;
};

$ cd db/
$ git push origin gh-pages

Git DB

Git is used as the DB out of convenience. Made it pretty easy to publish as a repo itself. But some serious version of this project might use a more distributed db that could hold millions of variants of functions. In that case, using SHA256 would be much better than SHA1.

All functions are stored as Git Blobs. You can actually look them up from the object store with git show.

$ git show 0b9495f5a81146461dbfb5c1ecc1f311bf659d85
export default function() {
  return "Hello, World";
}

For indexing the object refs themselves, all the objects are stored as a tree in git inside of the db directory.

$ git ls-tree -r fb91a6ae86aad75ee13fcdbe4d66b55015975f55
100644 blob 032c8c075b35b7314b53db46837644fac2ce77a6    03/2c8c075b35b7314b53db46837644fac2ce77a6.js
100644 blob 091f0aa4859182d4f8023b5917b09d40fc59d24c    09/1f0aa4859182d4f8023b5917b09d40fc59d24c.js
100644 blob 0b9495f5a81146461dbfb5c1ecc1f311bf659d85    0b/9495f5a81146461dbfb5c1ecc1f311bf659d85.js

To work around directory limits, the objects are sharded into subdirectories by the first 2 hex characters of the OID. Then a .js file extension is added on.

You can browse these trees directly on GitHub, https://github.com/josh/cafe-js/tree/2a64ec43baf62aa948c7e6cebd0a29b146f0bddf.

The latest db tree is tracked under the gh-pages ref. It would probably be called db if GitHub Pages could use custom branch names.

Shortcomings

Loading hundreds of tiny function files from disk or over HTTP is going to be terribly slow to do everything during runtime. Compiling a single concatenated with something like Browserify would help.

Modifying existing functions is a pain in the ass. If you want to fix a low level helper function, you have to update every call site and every caller of every caller. I think this could be improved with tooling that automatically generated these symbol renames. You'd give it your root application function ID, the old ID of the function you want to rename and the new ID.

How do you even figure out what functions are avaliable? You can search through db, but you typically find a bunch of results, most being buggy variants and then the most update to date version. Maybe function metadata data might help aid some sort of search tool. Once published, curated directories of the good functions would be important.

Contributing

Fork the repo of course! You'll need to manage and submit pull requests for both the master and gh-pages branches. The bootstrapping code and tests live in master while all the code database is tracked under gh-pages. gh-pages was chosen just so all the functions could be published to the web automatically via GitHub Pages.

Related Repositories

gulp-cheatsheet

gulp-cheatsheet

A cheatsheet for gulp.js ...

awesome-cn-cafe

awesome-cn-cafe

A curated list of awesome café places in China. ...

CafeTownsend-Angular-Rails

CafeTownsend-Angular-Rails

AngularJS and Rails port of the famous Cafe Townsend demo originally written in ...

grunt-cafe-mocha

grunt-cafe-mocha

DEPRECATED: A Mocha server-side Grunt plugin for testing that actually works. ...

cafe

cafe

Brewing up some JavaScript. (DEPRECATED: use/fork reflect.js) ...


Top Contributors

josh brntbeer