diff --git a/README.md b/README.md index c9c5ded..c7dfe44 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,24 @@ for Node.js. It is based on [Strata](http://bigeasy.github.com/strata/), an evented I/O b‑tree. Memento is in development. + +## Open and Shut + +Memento creates a store which is an `EventEmitter`. You can create a new store +by providing a directory for the store to `createStore`. + +```javascript +#!/usr/bin/env node + +var memento = require('memento'); +var store = memento.createStore(tmp); + +store.on('ready', function () { + store.close(); + store.on('close', function () { ok(1, 'created') }); +}); + +store.on('error', function (e) { throw e }); +``` + +The directory must be empty. diff --git a/index.js b/index.js new file mode 100644 index 0000000..0518101 --- /dev/null +++ b/index.js @@ -0,0 +1,27 @@ +var EventEmitter = require('events').EventEmitter, util = require('util'); + +var slice = [].slice; + +function die () { + console.log.apply(console, slice.call(arguments, 0)); + process.exit(1); +} + +function say () { console.log.apply(console, slice.call(arguments, 0)) } + +function Store () { +} +util.inherits(Store, EventEmitter); + +function createStore (directory) { + var store = new Store(directory); + process.nextTick(function () { store.emit('ready') }); + return store; +} + +Store.prototype.close = function () { + var store = this; + process.nextTick(function () { store.emit('close') }); +} + +exports.createStore = createStore; diff --git a/t/basics/create.t.js b/t/basics/create.t.js new file mode 100755 index 0000000..ced05c5 --- /dev/null +++ b/t/basics/create.t.js @@ -0,0 +1,11 @@ +#!/usr/bin/env node + +require('./proof')(1, function (step, tmp, ok) { + var memento = require('../..'); + var store = memento.createStore(tmp); + store.on('ready', function () { + store.close(); + store.on('close', function () { ok(1, 'created') }); + }); + store.on('error', function (e) { throw e }); +}); diff --git a/t/basics/proof.js b/t/basics/proof.js new file mode 100644 index 0000000..50c9645 --- /dev/null +++ b/t/basics/proof.js @@ -0,0 +1 @@ +module.exports = require('../proof')(__dirname); diff --git a/t/proof.js b/t/proof.js new file mode 100644 index 0000000..1ef9945 --- /dev/null +++ b/t/proof.js @@ -0,0 +1,168 @@ +var fs = require("fs"), path = require("path"), crypto = require("crypto"), Strata = require(".."); + +function check (callback, forward) { + return function (error, result) { + if (error) callback(error); + else forward(result); + } +} + +function objectify (directory, callback) { + var files, segments = {}, count = 0; + + fs.readdir(directory, check(callback, list)); + + function list ($1) { + (files = $1).forEach(function (file) { + if (!/^\./.test(file)) readFile(file); + else read(); + }); + } + + function readFile (file) { + segments[file] = []; + + fs.readFile(path.resolve(directory, file), "utf8", check(callback, lines)); + + function lines (lines) { + lines = lines.split(/\n/); + lines.pop(); + lines.forEach(function (json) { + json = json.replace(/[\da-f]+$/, ""); + segments[file].push(JSON.parse(json)); + }); + read(); + } + } + + function read () { + if (++count == files.length) callback(null, segments); + } +} + +function stringify (directory, callback) { + objectify(directory, check(callback, segments)); + + function segments (segments) { + console.log(JSON.stringify(segments, null, 2)); + callback(); + } +} + +function load (segments, callback) { + fs.readFile(segments, "utf8", check(callback, parse)); + + function parse (json) { callback(null, JSON.parse(json)) } +} + +function gather (step, strata) { + var records = []; + step(function () { + records = [] + strata.iterator("a", step()); + }, function (cursor) { + return true; + }, function page (more, cursor) { + if (more) return cursor.offset; + cursor.unlock(); + step(null, records); + }, function item (i, cursor, page) { + if (i < cursor.length) { + cursor.get(i, step()); + } else { + cursor.next(step(page)); + } + }, function (record, i, item) { + records.push(record); + step(item)(null, i + 1); + }); +} + +function serialize (segments, directory, callback) { + if (typeof segments == "string") load(segments, check(callback, write)); + else write (segments); + + function write (segments) { + var files = Object.keys(segments), count = 0; + + files.forEach(function (segment) { + var records = []; + segments[segment].forEach(function (line) { + var record = [ JSON.stringify(line) ]; + record.push(crypto.createHash("sha1").update(record[0]).digest("hex")); + records.push(record.join(" ")); + }); + records = records.join("\n") + "\n"; + fs.writeFile(path.resolve(directory, segment), records, "utf8", check(callback, written)); + }); + + function written () { if (++count == files.length) callback(null) } + } +} + +function deltree (directory, callback) { + var files, count = 0; + + readdir(); + + function readdir () { + fs.readdir(directory, extant); + } + + function extant (error, $1) { + if (error) { + if (error.code != "ENOENT") callback(error); + else callback(); + } else { + list($1); + } + } + + function list ($1) { + (files = $1).forEach(function (file) { + stat(path.resolve(directory, file)); + }); + deleted(); + } + + function stat (file) { + var stat; + + fs.stat(file, check(callback, inspect)); + + function inspect ($1) { + if ((stat = $1).isDirectory()) deltree(file, check(callback, unlink)); + else unlink(); + } + + function unlink () { + if (stat.isDirectory()) fs.rmdir(file, check(callback, deleted)); + else fs.unlink(file, check(callback, deleted)); + } + } + + function deleted () { + if (++count > files.length) fs.rmdir(directory, callback); + } +} + +module.exports = function (dirname) { + var tmp = dirname + "/tmp"; + return require("proof")(function cleanup (step) { + deltree(tmp, step()); + }, function (step) { + step(function () { + fs.mkdir(tmp, 0755, step()); + }, function () { + return { Strata: Strata + , tmp: tmp + , load: load + , stringify: stringify + , serialize: serialize + , gather: gather + , objectify: objectify + }; + }); + }); +}; +module.exports.stringify = stringify;