Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 12 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# fs-db
# fsdown

a silly **work in progress** to use the filesystem as a database.
a silly **work in progress** to use the filesystem (e.g. `.csv`, `.ldjson` files) as a database.

the purpose is for apps to have human editable and readable data that can be iterated on easily (vim macros over sql migrations) and shared more openly (GitHub repos over JSON APIs).

Expand All @@ -17,21 +17,19 @@ npm install --save fs-db
```
var FsDb = require('fs-db')

var fsDb = FsDb({
location: __dirname + '/data',
var db = FsDb({
location: __dirname + '/things.csv',
codec: 'csv'
})

fsDb.createReadStream()
.pipe(process.stdout)
db.readStream()
.on('data', console.log)
```

#### FsDb(options)
#### fsdown(options)

possible `options` are:
`options`:

- `location`: root filesystem directory of the database
- `codec`: codec to use (defaults to 'json'), see [codecs](./codecs)

#### fsDb.createReadStream()

returns a readable [pull stream](https://npmjs.org/package/pull-stream) of objects with [JSON Pointer](https://npmjs.org/package/json-pointer) `id`s based on the path.
- `location` is the path to the database file.
- `codec` is which codec to use (defaults to 'csv'). can be a name of an existing codec, a custom codec object (see [codecs](./codecs) for what is expected of a codec), or an array where the first item is one of the previous values and the second item is options to pass to the codec.
- `keyAttribute` is a string identifier of the attribute used as keys (e.g. 'id').
11 changes: 2 additions & 9 deletions codecs/csv.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
var csv = require('comma-separated-values')

module.exports = {
type: 'csv',
encode: function (obj) {
return csv.encode(obj, { header: true })
},
decode: function (str) {
return csv.parse(str, { header: true })
}
decode: require('csv-parser'),
encode: require('csv-formatter')
}
2 changes: 0 additions & 2 deletions codecs/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
module.exports = {
json: require('./json'),
yml: require('./yml'),
csv: require('./csv')
}
11 changes: 0 additions & 11 deletions codecs/json.js

This file was deleted.

11 changes: 0 additions & 11 deletions codecs/yml.js

This file was deleted.

129 changes: 113 additions & 16 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,131 @@
var pull = require('pull-stream')
var debug = require('debug')('fs-db')
var fs = require('fs')
var defined = require('defined')
var inherits = require('inherits')
var assign = require('lodash.assign')
var forEach = require('lodash.foreach')
var through = require('through2')
var uuid = require('node-uuid')
var pumpify = require('pumpify')
var prepend = require('prepend-stream')

var codecs = require('./codecs')

module.exports = FsDb

function FsDb (options) {
if (!(this instanceof FsDb)) {
if (!(this instanceof FsDb))
return new FsDb(options)

if (typeof options == 'string') {
options = { location: options }
} else {
options = defined(options, {})
}
debug("constructor(", options, ")")

this.location = options.location || process.cwd()
this.fs = options.fs || require('fs')
if (options.location == null) {
throw new Error('fs-db: options.location is required.')
}

var codec = options.codec || 'json'
this.codec = (typeof codec === 'string') ?
codecs[codec] : codec
this.location = options.location
this.codec = getCodec(options.codec)
this.keyAttribute = defined(options.keyAttribute, 'key')
}

FsDb.prototype = {
assign(FsDb.prototype, {
createReadStream: createReadStream,
createWriteStream: createWriteStream
})

function getCodec (codec) {
var codecOptions
if (Array.isArray(codec)) {
codecOptions = codec[1]
codec = codec[0]
} else {
codecOptions = {}
}

if (typeof codec === 'string') {
codec = codecs[codec]
} else if (!isCodec(codec)) {
codec = codecs.csv
}

return {
encode: codec.encode.bind(codec, codecOptions),
decode: codec.decode.bind(codec, codecOptions)
}
}

function createReadStream () {
debug('createReadStream()')

return pull(
require('./lib/read-dir')(this),
require('./lib/read-file')(this),
require('./lib/parse')(this)
function isCodec (codec) {
return (
codec != null &&
typeof codec.encode === 'function' &&
typeof codec.decode === 'function'
)
}

function createReadStream (options) {
debug('createReadStream(', options, ')')

var keyAttribute = this.keyAttribute

return pumpify.obj([
// read from file
fs.createReadStream(this.location),
// parse data into objects
this.codec.decode(),
through.obj(function (row, enc, cb) {
// get key
var key = row[keyAttribute]

// if no key, default to UUID
if (key == null) {
key = uuid()
}

cb(null, { key: key, value: row })
})
])
}

function createWriteStream (options) {
debug('createWriteStream(', options, ')')

var table = {}

return pumpify.obj([
// parse values as json
// add current data to beginning of
// the data that is to be written
prepend.obj(
this.createReadStream(options)
),
// construct in-memory table of data
through.obj(
function transform (row, enc, cb) {
debug("transform row", row)
// perform operation to table
if (row.type === 'del') {
delete table[row.key]
} else {
table[row.key] = row.value
}
cb()
},
function flush (cb) {
// output contents of table
debug("flush table", table)
forEach(table, function (value, key) {
this.push(value)
}, this)
cb()
}
),
// format data to string
this.codec.encode(),
// write to file
fs.createWriteStream(this.location)
])
}
68 changes: 0 additions & 68 deletions lib/parse.js

This file was deleted.

15 changes: 0 additions & 15 deletions lib/read-dir.js

This file was deleted.

27 changes: 0 additions & 27 deletions lib/read-file.js

This file was deleted.

Loading