| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963 |
- # BFJ
- [](https://gitlab.com/philbooth/bfj/pipelines)
- [](https://www.npmjs.com/package/bfj)
- [](https://www.npmjs.com/package/bfj)
- [](https://opensource.org/licenses/MIT)
- Big-Friendly JSON. Asynchronous streaming functions for large JSON data sets.
- * [Why would I want those?](#why-would-i-want-those)
- * [Is it fast?](#is-it-fast)
- * [What functions does it implement?](#what-functions-does-it-implement)
- * [How do I install it?](#how-do-i-install-it)
- * [How do I read a JSON file?](#how-do-i-read-a-json-file)
- * [How do I parse a stream of JSON?](#how-do-i-parse-a-stream-of-json)
- * [How do I selectively parse individual items from a JSON stream?](#how-do-i-selectively-parse-individual-items-from-a-json-stream)
- * [How do I write a JSON file?](#how-do-i-write-a-json-file)
- * [How do I create a stream of JSON?](#how-do-i-create-a-stream-of-json)
- * [How do I create a JSON string?](#how-do-i-create-a-json-string)
- * [What other methods are there?](#what-other-methods-are-there)
- * [bfj.walk (stream, options)](#bfjwalk-stream-options)
- * [bfj.eventify (data, options)](#bfjeventify-data-options)
- * [What options can I specify?](#what-options-can-i-specify)
- * [Options for parsing functions](#options-for-parsing-functions)
- * [Options for serialisation functions](#options-for-serialisation-functions)
- * [Is it possible to pause parsing or serialisation from calling code?](#is-it-possible-to-pause-parsing-or-serialisation-from-calling-code)
- * [Can it handle newline-delimited JSON (NDJSON)?](#can-it-handle-newline-delimited-json-ndjson)
- * [Why does it default to bluebird promises?](#why-does-it-default-to-bluebird-promises)
- * [Can I specify a different promise implementation?](#can-i-specify-a-different-promise-implementation)
- * [Is there a change log?](#is-there-a-change-log)
- * [How do I set up the dev environment?](#how-do-i-set-up-the-dev-environment)
- * [What versions of Node.js does it support?](#what-versions-of-nodejs-does-it-support)
- * [What license is it released under?](#what-license-is-it-released-under)
- ## Why would I want those?
- If you need
- to parse huge JSON strings
- or stringify huge JavaScript data sets,
- it monopolises the event loop
- and can lead to out-of-memory exceptions.
- BFJ implements asynchronous functions
- and uses pre-allocated fixed-length arrays
- to try and alleviate those issues.
- ## Is it fast?
- No.
- BFJ yields frequently
- to avoid monopolising the event loop,
- interrupting its own execution
- to let other event handlers run.
- The frequency of those yields
- can be controlled with the [`yieldRate` option](#what-options-can-i-specify),
- but fundamentally it is not designed for speed.
- Furthermore,
- when serialising data to a stream,
- BFJ uses a fixed-length buffer
- to avoid exhausting available memory.
- Whenever that buffer is full,
- serialisation is paused
- until the receiving stream processes some more data,
- regardless of the value of `yieldRate`.
- You can control the size of the buffer
- using the [`bufferLength` option](#options-for-serialisation-functions)
- but really,
- if you need quick results,
- BFJ is not for you.
- ## What functions does it implement?
- Nine functions
- are exported.
- Five are
- concerned with
- parsing, or
- turning JSON strings
- into JavaScript data:
- * [`read`](#how-do-i-read-a-json-file)
- asynchronously parses
- a JSON file from disk.
- * [`parse` and `unpipe`](#how-do-i-parse-a-stream-of-json)
- are for asynchronously parsing
- streams of JSON.
- * [`match`](#how-do-i-selectively-parse-individual-items-from-a-json-stream)
- selectively parses individual items
- from a JSON stream.
- * [`walk`](#bfjwalk-stream-options)
- asynchronously walks
- a stream,
- emitting events
- as it encounters
- JSON tokens.
- Analagous to a
- [SAX parser][sax].
- The other four functions
- handle the reverse transformations,
- serialising
- JavaScript data
- to JSON:
- * [`write`](#how-do-i-write-a-json-file)
- asynchronously serialises data
- to a JSON file on disk.
- * [`streamify`](#how-do-i-create-a-stream-of-json)
- asynchronously serialises data
- to a stream of JSON.
- * [`stringify`](#how-do-i-create-a-json-string)
- asynchronously serialises data
- to a JSON string.
- * [`eventify`](#bfjeventify-data-options)
- asynchronously traverses
- a data structure
- depth-first,
- emitting events
- as it encounters items.
- By default
- it coerces
- promises, buffers and iterables
- to JSON-friendly values.
- ## How do I install it?
- If you're using npm:
- ```
- npm i bfj --save
- ```
- Or if you just want
- the git repo:
- ```
- git clone git@gitlab.com:philbooth/bfj.git
- ```
- ## How do I read a JSON file?
- ```js
- const bfj = require('bfj');
- bfj.read(path, options)
- .then(data => {
- // :)
- })
- .catch(error => {
- // :(
- });
- ```
- `read` returns a [bluebird promise][promise] and
- asynchronously parses
- a JSON file
- from disk.
- It takes two arguments;
- the path to the JSON file
- and an [options](#options-for-parsing-functions) object.
- If there are
- no syntax errors,
- the returned promise is resolved
- with the parsed data.
- If syntax errors occur,
- the promise is rejected
- with the first error.
- ## How do I parse a stream of JSON?
- ```js
- const bfj = require('bfj');
- // By passing a readable stream to bfj.parse():
- bfj.parse(fs.createReadStream(path), options)
- .then(data => {
- // :)
- })
- .catch(error => {
- // :(
- });
- // ...or by passing the result from bfj.unpipe() to stream.pipe():
- request({ url }).pipe(bfj.unpipe((error, data) => {
- if (error) {
- // :(
- } else {
- // :)
- }
- }))
- ```
- * `parse` returns a [bluebird promise][promise]
- and asynchronously parses
- a stream of JSON data.
- It takes two arguments;
- a [readable stream][readable]
- from which
- the JSON
- will be parsed
- and an [options](#options-for-parsing-functions) object.
- If there are
- no syntax errors,
- the returned promise is resolved
- with the parsed data.
- If syntax errors occur,
- the promise is rejected
- with the first error.
- * `unpipe` returns a [writable stream][writable]
- that can be passed to [`stream.pipe`][pipe],
- then parses JSON data
- read from the stream.
- It takes two arguments;
- a callback function
- that will be called
- after parsing is complete
- and an [options](#options-for-parsing-functions) object.
- If there are no errors,
- the callback is invoked
- with the result as the second argument.
- If errors occur,
- the first error is passed
- the callback
- as the first argument.
- ## How do I selectively parse individual items from a JSON stream?
- ```js
- const bfj = require('bfj');
- // Call match with your stream and a selector predicate/regex/string
- const dataStream = bfj.match(jsonStream, selector, options);
- // Get data out of the returned stream with event handlers
- dataStream.on('data', item => { /* ... */ });
- dataStream.on('end', () => { /* ... */);
- dataStream.on('error', () => { /* ... */);
- dataStream.on('dataError', () => { /* ... */);
- // ...or you can pipe it to another stream
- dataStream.pipe(someOtherStream);
- ```
- `match` returns a readable, object-mode stream
- and asynchronously parses individual matching items
- from an input JSON stream.
- It takes three arguments:
- a [readable stream][readable]
- from which the JSON will be parsed;
- a selector argument for determining matches,
- which may be a string, a regular expression or a predicate function;
- and an [options](#options-for-parsing-functions) object.
- If the selector is a string,
- it will be compared to property keys
- to determine whether
- each item in the data is a match.
- If it is a regular expression,
- the comparison will be made
- by calling the [RegExp `test` method][regexp-test]
- with the property key.
- Predicate functions will be called with three arguments:
- `key`, `value` and `depth`.
- If the result of the predicate is a truthy value
- then the item will be deemed a match.
- If there are any syntax errors in the JSON,
- a `dataError` event will be emitted.
- If any other errors occur,
- an `error` event will be emitted.
- ## How do I write a JSON file?
- ```js
- const bfj = require('bfj');
- bfj.write(path, data, options)
- .then(() => {
- // :)
- })
- .catch(error => {
- // :(
- });
- ```
- `write` returns a [bluebird promise][promise]
- and asynchronously serialises a data structure
- to a JSON file on disk.
- The promise is resolved
- when the file has been written,
- or rejected with the error
- if writing failed.
- It takes three arguments;
- the path to the JSON file,
- the data structure to serialise
- and an [options](#options-for-serialisation-functions) object.
- ## How do I create a stream of JSON?
- ```js
- const bfj = require('bfj');
- const stream = bfj.streamify(data, options);
- // Get data out of the stream with event handlers
- stream.on('data', chunk => { /* ... */ });
- stream.on('end', () => { /* ... */);
- stream.on('error', () => { /* ... */);
- stream.on('dataError', () => { /* ... */);
- // ...or you can pipe it to another stream
- stream.pipe(someOtherStream);
- ```
- `streamify` returns a [readable stream][readable]
- and asynchronously serialises
- a data structure to JSON,
- pushing the result
- to the returned stream.
- It takes two arguments;
- the data structure to serialise
- and an [options](#options-for-serialisation-functions) object.
- If there a circular reference is encountered in the data
- and `options.circular` is not set to `'ignore'`,
- a `dataError` event will be emitted.
- If any other errors occur,
- an `error` event will be emitted.
- ## How do I create a JSON string?
- ```js
- const bfj = require('bfj');
- bfj.stringify(data, options)
- .then(json => {
- // :)
- })
- .catch(error => {
- // :(
- });
- ```
- `stringify` returns a [bluebird promise][promise] and
- asynchronously serialises a data structure
- to a JSON string.
- The promise is resolved
- to the JSON string
- when serialisation is complete.
- It takes two arguments;
- the data structure to serialise
- and an [options](#options-for-serialisation-functions) object.
- ## What other methods are there?
- ### bfj.walk (stream, options)
- ```js
- const bfj = require('bfj');
- const emitter = bfj.walk(fs.createReadStream(path), options);
- emitter.on(bfj.events.array, () => { /* ... */ });
- emitter.on(bfj.events.object, () => { /* ... */ });
- emitter.on(bfj.events.property, name => { /* ... */ });
- emitter.on(bfj.events.string, value => { /* ... */ });
- emitter.on(bfj.events.number, value => { /* ... */ });
- emitter.on(bfj.events.literal, value => { /* ... */ });
- emitter.on(bfj.events.endArray, () => { /* ... */ });
- emitter.on(bfj.events.endObject, () => { /* ... */ });
- emitter.on(bfj.events.error, error => { /* ... */ });
- emitter.on(bfj.events.dataError, error => { /* ... */ });
- emitter.on(bfj.events.end, () => { /* ... */ });
- ```
- `walk` returns an [event emitter][eventemitter]
- and asynchronously walks
- a stream of JSON data,
- emitting events
- as it encounters
- tokens.
- It takes two arguments;
- a [readable stream][readable]
- from which
- the JSON
- will be read
- and an [options](#options-for-parsing-functions) object.
- The emitted events
- are defined
- as public properties
- of an object,
- `bfj.events`:
- * `bfj.events.array`
- indicates that
- an array context
- has been entered
- by encountering
- the `[` character.
- * `bfj.events.endArray`
- indicates that
- an array context
- has been left
- by encountering
- the `]` character.
- * `bfj.events.object`
- indicates that
- an object context
- has been entered
- by encountering
- the `{` character.
- * `bfj.events.endObject`
- indicates that
- an object context
- has been left
- by encountering
- the `}` character.
- * `bfj.events.property`
- indicates that
- a property
- has been encountered
- in an object.
- The listener
- will be passed
- the name of the property
- as its argument
- and the next event
- to be emitted
- will represent
- the property's value.
- * `bfj.events.string`
- indicates that
- a string
- has been encountered.
- The listener
- will be passed
- the value
- as its argument.
- * `bfj.events.number`
- indicates that
- a number
- has been encountered.
- The listener
- will be passed
- the value
- as its argument.
- * `bfj.events.literal`
- indicates that
- a JSON literal
- (either `true`, `false` or `null`)
- has been encountered.
- The listener
- will be passed
- the value
- as its argument.
- * `bfj.events.error`
- indicates that
- an error was caught
- from one of the event handlers
- in user code.
- The listener
- will be passed
- the `Error` instance
- as its argument.
- * `bfj.events.dataError`
- indicates that
- a syntax error was encountered
- in the incoming JSON stream.
- The listener
- will be passed
- an `Error` instance
- decorated with `actual`, `expected`, `lineNumber` and `columnNumber` properties
- as its argument.
- * `bfj.events.end`
- indicates that
- the end of the input
- has been reached
- and the stream is closed.
- * `bfj.events.endLine`
- indicates that a root-level newline character
- has been encountered in an [NDJSON](#can-it-handle-newline-delimited-json-ndjson) stream.
- Only emitted if the `ndjson` [option](#options-for-parsing-functions) is set.
- If you are using `bfj.walk`
- to sequentially parse items in an array,
- you might also be interested in
- the [bfj-collections] module.
- ### bfj.eventify (data, options)
- ```js
- const bfj = require('bfj');
- const emitter = bfj.eventify(data, options);
- emitter.on(bfj.events.array, () => { /* ... */ });
- emitter.on(bfj.events.object, () => { /* ... */ });
- emitter.on(bfj.events.property, name => { /* ... */ });
- emitter.on(bfj.events.string, value => { /* ... */ });
- emitter.on(bfj.events.number, value => { /* ... */ });
- emitter.on(bfj.events.literal, value => { /* ... */ });
- emitter.on(bfj.events.endArray, () => { /* ... */ });
- emitter.on(bfj.events.endObject, () => { /* ... */ });
- emitter.on(bfj.events.error, error => { /* ... */ });
- emitter.on(bfj.events.dataError, error => { /* ... */ });
- emitter.on(bfj.events.end, () => { /* ... */ });
- ```
- `eventify` returns an [event emitter][eventemitter]
- and asynchronously traverses
- a data structure depth-first,
- emitting events as it
- encounters items.
- By default it coerces
- promises, buffers and iterables
- to JSON-friendly values.
- It takes two arguments;
- the data structure to traverse
- and an [options](#options-for-serialisation-functions) object.
- The emitted events
- are defined
- as public properties
- of an object,
- `bfj.events`:
- * `bfj.events.array`
- indicates that
- an array
- has been encountered.
- * `bfj.events.endArray`
- indicates that
- the end of an array
- has been encountered.
- * `bfj.events.object`
- indicates that
- an object
- has been encountered.
- * `bfj.events.endObject`
- indicates that
- the end of an object
- has been encountered.
- * `bfj.events.property`
- indicates that
- a property
- has been encountered
- in an object.
- The listener
- will be passed
- the name of the property
- as its argument
- and the next event
- to be emitted
- will represent
- the property's value.
- * `bfj.events.string`
- indicates that
- a string
- has been encountered.
- The listener
- will be passed
- the value
- as its argument.
- * `bfj.events.number`
- indicates that
- a number
- has been encountered.
- The listener
- will be passed
- the value
- as its argument.
- * `bfj.events.literal`
- indicates that
- a JSON literal
- (either `true`, `false` or `null`)
- has been encountered.
- The listener
- will be passed
- the value
- as its argument.
- * `bfj.events.error`
- indicates that
- an error was caught
- from one of the event handlers
- in user code.
- The listener
- will be passed
- the `Error` instance
- as its argument.
- * `bfj.events.dataError`
- indicates that
- a circular reference was encountered in the data
- and the `circular` option was not set to `'ignore'`.
- The listener
- will be passed
- an `Error` instance
- as its argument.
- * `bfj.events.end`
- indicates that
- the end of the data
- has been reached and
- no further events
- will be emitted.
- ## What options can I specify?
- ### Options for parsing functions
- * `options.reviver`:
- Transformation function,
- invoked depth-first
- against the parsed
- data structure.
- This option
- is analagous to the
- [reviver parameter for JSON.parse][reviver].
- * `options.yieldRate`:
- The number of data items to process
- before yielding to the event loop.
- Smaller values yield to the event loop more frequently,
- meaning less time will be consumed by bfj per tick
- but the overall parsing time will be slower.
- Larger values yield to the event loop less often,
- meaning slower tick times but faster overall parsing time.
- The default value is `16384`.
- * `options.Promise`:
- Promise constructor that will be used
- for promises returned by all methods.
- If you set this option,
- please be aware that some promise implementations
- (including native promises)
- may cause your process to die
- with out-of-memory exceptions.
- Defaults to [bluebird's implementation][promise],
- which does not have that problem.
- * `options.ndjson`:
- If set to `true`,
- newline characters at the root level
- will be treated as delimiters between
- discrete chunks of JSON.
- See [NDJSON](#can-it-handle-newline-delimited-json-ndjson) for more information.
- * `options.numbers`:
- For `bfj.match` only,
- set this to `true`
- if you wish to match against numbers
- with a string or regular expression
- `selector` argument.
- * `options.bufferLength`:
- For `bfj.match` only,
- the length of the match buffer.
- Smaller values use less memory
- but may result in a slower parse time.
- The default value is `1024`.
- * `options.highWaterMark`:
- For `bfj.match` only,
- set this if you would like to
- pass a value for the `highWaterMark` option
- to the readable stream constructor.
- ### Options for serialisation functions
- * `options.space`:
- Indentation string
- or the number of spaces
- to indent
- each nested level by.
- This option
- is analagous to the
- [space parameter for JSON.stringify][space].
- * `options.promises`:
- By default,
- promises are coerced
- to their resolved value.
- Set this property
- to `'ignore'`
- for improved performance
- if you don't need
- to coerce promises.
- * `options.buffers`:
- By default,
- buffers are coerced
- using their `toString` method.
- Set this property
- to `'ignore'`
- for improved performance
- if you don't need
- to coerce buffers.
- * `options.maps`:
- By default,
- maps are coerced
- to plain objects.
- Set this property
- to `'ignore'`
- for improved performance
- if you don't need
- to coerce maps.
- * `options.iterables`:
- By default,
- other iterables
- (i.e. not arrays, strings or maps)
- are coerced
- to arrays.
- Set this property
- to `'ignore'`
- for improved performance
- if you don't need
- to coerce iterables.
- * `options.circular`:
- By default,
- circular references
- will cause the write
- to fail.
- Set this property
- to `'ignore'`
- if you'd prefer
- to silently skip past
- circular references
- in the data.
- * `options.bufferLength`:
- The length of the write buffer.
- Smaller values use less memory
- but may result in a slower serialisation time.
- The default value is `1024`.
- * `options.highWaterMark`:
- Set this if you would like to
- pass a value for the `highWaterMark` option
- to the readable stream constructor.
- * `options.yieldRate`:
- The number of data items to process
- before yielding to the event loop.
- Smaller values yield to the event loop more frequently,
- meaning less time will be consumed by bfj per tick
- but the overall serialisation time will be slower.
- Larger values yield to the event loop less often,
- meaning slower tick times but faster overall serialisation time.
- The default value is `16384`.
- * `options.Promise`:
- Promise constructor that will be used
- for promises returned by all methods.
- If you set this option,
- please be aware that some promise implementations
- (including native promises)
- may cause your process to die
- with out-of-memory exceptions.
- Defaults to [bluebird's implementation][promise],
- which does not have that problem.
- ## Is it possible to pause parsing or serialisation from calling code?
- Yes it is!
- Both [`walk`](#bfjwalk-stream-options)
- and [`eventify`](#bfjeventify-data-options)
- decorate their returned event emitters
- with a `pause` method
- that will prevent any further events being emitted.
- The `pause` method itself
- returns a `resume` function
- that you can call to indicate
- that processing should continue.
- For example:
- ```js
- const bfj = require('bfj');
- const emitter = bfj.walk(fs.createReadStream(path), options);
- // Later, when you want to pause parsing:
- const resume = emitter.pause();
- // Then when you want to resume:
- resume();
- ```
- ## Can it handle [newline-delimited JSON (NDJSON)](http://ndjson.org/)?
- Yes.
- If you pass the `ndjson` [option](#options-for-parsing-functions)
- to `bfj.walk`, `bfj.match` or `bfj.parse`,
- newline characters at the root level
- will act as delimiters between
- discrete JSON values:
- * `bfj.walk` will emit a `bfj.events.endLine` event
- each time it encounters a newline character.
- * `bfj.match` will just ignore the newlines
- while it continues looking for matching items.
- * `bfj.parse` will resolve with the first value
- and pause the underlying stream.
- If it's called again with the same stream,
- it will resume processing
- and resolve with the second value.
- To parse the entire stream,
- calls should be made sequentially one-at-a-time
- until the returned promise
- resolves to `undefined`
- (`undefined` is not a valid JSON token).
- `bfj.unpipe` and `bfj.read` will not parse NDJSON.
- ## Why does it default to bluebird promises?
- Until version `4.2.4`,
- native promises were used.
- But they were found
- to cause out-of-memory errors
- when serialising large amounts of data to JSON,
- due to [well-documented problems
- with the native promise implementation](https://alexn.org/blog/2017/10/11/javascript-promise-leaks-memory.html).
- So in version `5.0.0`,
- bluebird promises were used instead.
- In version `5.1.0`,
- an option was added
- that enables callers to specify
- the promise constructor to use.
- Use it at your own risk.
- ## Can I specify a different promise implementation?
- Yes.
- Just pass the `Promise` option
- to any method.
- If you get out-of-memory errors
- when using that option,
- consider changing your promise implementation.
- ## Is there a change log?
- [Yes][history].
- ## How do I set up the dev environment?
- The development environment
- relies on [Node.js][node],
- [ESLint],
- [Mocha],
- [Chai],
- [Proxyquire] and
- [Spooks].
- Assuming that
- you already have
- node and NPM
- set up,
- you just need
- to run
- `npm install`
- to install
- all of the dependencies
- as listed in `package.json`.
- You can
- lint the code
- with the command
- `npm run lint`.
- You can
- run the tests
- with the command
- `npm test`.
- ## What versions of Node.js does it support?
- As of [version `3.0.0`](HISTORY.md#300),
- only Node.js versions 6 or greater
- are supported
- because of the dependency
- on [Hoopy](https://gitlab.com/philbooth/hoopy).
- Previous versions supported
- node 4 and later.
- A separate `node-4` branch was maintained
- until version `5.4.1`,
- which had feature parity version-for-version
- with releases from `master`.
- Releases from the `node-4` branch
- are available in npm
- under the package name [`bfj-node4`](https://www.npmjs.com/package/bfj-node4).
- ## What license is it released under?
- [MIT][license].
- [ci-image]: https://secure.travis-ci.org/philbooth/bfj.png?branch=master
- [ci-status]: http://travis-ci.org/#!/philbooth/bfj
- [sax]: http://en.wikipedia.org/wiki/Simple_API_for_XML
- [promise]: http://bluebirdjs.com/docs/api-reference.html
- [bfj-collections]: https://github.com/hash-bang/bfj-collections
- [eventemitter]: https://nodejs.org/api/events.html#events_class_eventemitter
- [readable]: https://nodejs.org/api/stream.html#stream_readable_streams
- [writable]: https://nodejs.org/api/stream.html#stream_writable_streams
- [pipe]: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
- [regexp-test]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test
- [reviver]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Using_the_reviver_parameter
- [space]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_space_argument
- [history]: HISTORY.md
- [node]: https://nodejs.org/en/
- [eslint]: http://eslint.org/
- [mocha]: https://mochajs.org/
- [chai]: http://chaijs.com/
- [proxyquire]: https://github.com/thlorenz/proxyquire
- [spooks]: https://gitlab.com/philbooth/spooks.js
- [license]: COPYING
|