UNPKG

24.7 kBMarkdownView Raw
1# BFJ
2
3[![Build status](https://gitlab.com/philbooth/bfj/badges/master/pipeline.svg)](https://gitlab.com/philbooth/bfj/pipelines)
4[![Package status](https://img.shields.io/npm/v/bfj.svg)](https://www.npmjs.com/package/bfj)
5[![Downloads](https://img.shields.io/npm/dm/bfj.svg)](https://www.npmjs.com/package/bfj)
6[![License](https://img.shields.io/npm/l/bfj.svg)](https://opensource.org/licenses/MIT)
7
8Big-Friendly JSON. Asynchronous streaming functions for large JSON data sets.
9
10* [Why would I want those?](#why-would-i-want-those)
11* [Is it fast?](#is-it-fast)
12* [What functions does it implement?](#what-functions-does-it-implement)
13* [How do I install it?](#how-do-i-install-it)
14* [How do I read a JSON file?](#how-do-i-read-a-json-file)
15* [How do I parse a stream of JSON?](#how-do-i-parse-a-stream-of-json)
16* [How do I selectively parse individual items from a JSON stream?](#how-do-i-selectively-parse-individual-items-from-a-json-stream)
17* [How do I write a JSON file?](#how-do-i-write-a-json-file)
18* [How do I create a stream of JSON?](#how-do-i-create-a-stream-of-json)
19* [How do I create a JSON string?](#how-do-i-create-a-json-string)
20* [What other methods are there?](#what-other-methods-are-there)
21 * [bfj.walk (stream, options)](#bfjwalk-stream-options)
22 * [bfj.eventify (data, options)](#bfjeventify-data-options)
23* [What options can I specify?](#what-options-can-i-specify)
24 * [Options for parsing functions](#options-for-parsing-functions)
25 * [Options for serialisation functions](#options-for-serialisation-functions)
26* [Is it possible to pause parsing or serialisation from calling code?](#is-it-possible-to-pause-parsing-or-serialisation-from-calling-code)
27* [Can it handle newline-delimited JSON (NDJSON)?](#can-it-handle-newline-delimited-json-ndjson)
28* [Why does it default to bluebird promises?](#why-does-it-default-to-bluebird-promises)
29* [Can I specify a different promise implementation?](#can-i-specify-a-different-promise-implementation)
30* [Is there a change log?](#is-there-a-change-log)
31* [How do I set up the dev environment?](#how-do-i-set-up-the-dev-environment)
32* [What versions of Node.js does it support?](#what-versions-of-nodejs-does-it-support)
33* [What license is it released under?](#what-license-is-it-released-under)
34
35## Why would I want those?
36
37If you need
38to parse huge JSON strings
39or stringify huge JavaScript data sets,
40it monopolises the event loop
41and can lead to out-of-memory exceptions.
42BFJ implements asynchronous functions
43and uses pre-allocated fixed-length arrays
44to try and alleviate those issues.
45
46## Is it fast?
47
48No.
49
50BFJ yields frequently
51to avoid monopolising the event loop,
52interrupting its own execution
53to let other event handlers run.
54The frequency of those yields
55can be controlled with the [`yieldRate` option](#what-options-can-i-specify),
56but fundamentally it is not designed for speed.
57
58Furthermore,
59when serialising data to a stream,
60BFJ uses a fixed-length buffer
61to avoid exhausting available memory.
62Whenever that buffer is full,
63serialisation is paused
64until the receiving stream processes some more data,
65regardless of the value of `yieldRate`.
66You can control the size of the buffer
67using the [`bufferLength` option](#options-for-serialisation-functions)
68but really,
69if you need quick results,
70BFJ is not for you.
71
72## What functions does it implement?
73
74Nine functions
75are exported.
76
77Five are
78concerned with
79parsing, or
80turning JSON strings
81into JavaScript data:
82
83* [`read`](#how-do-i-read-a-json-file)
84 asynchronously parses
85 a JSON file from disk.
86
87* [`parse` and `unpipe`](#how-do-i-parse-a-stream-of-json)
88 are for asynchronously parsing
89 streams of JSON.
90
91* [`match`](#how-do-i-selectively-parse-individual-items-from-a-json-stream)
92 selectively parses individual items
93 from a JSON stream.
94
95* [`walk`](#bfjwalk-stream-options)
96 asynchronously walks
97 a stream,
98 emitting events
99 as it encounters
100 JSON tokens.
101 Analagous to a
102 [SAX parser][sax].
103
104The other four functions
105handle the reverse transformations,
106serialising
107JavaScript data
108to JSON:
109
110* [`write`](#how-do-i-write-a-json-file)
111 asynchronously serialises data
112 to a JSON file on disk.
113
114* [`streamify`](#how-do-i-create-a-stream-of-json)
115 asynchronously serialises data
116 to a stream of JSON.
117
118* [`stringify`](#how-do-i-create-a-json-string)
119 asynchronously serialises data
120 to a JSON string.
121
122* [`eventify`](#bfjeventify-data-options)
123 asynchronously traverses
124 a data structure
125 depth-first,
126 emitting events
127 as it encounters items.
128 By default
129 it coerces
130 promises, buffers and iterables
131 to JSON-friendly values.
132
133## How do I install it?
134
135If you're using npm:
136
137```
138npm i bfj --save
139```
140
141Or if you just want
142the git repo:
143
144```
145git clone git@gitlab.com:philbooth/bfj.git
146```
147
148## How do I read a JSON file?
149
150```js
151const bfj = require('bfj');
152
153bfj.read(path, options)
154 .then(data => {
155 // :)
156 })
157 .catch(error => {
158 // :(
159 });
160```
161
162`read` returns a [bluebird promise][promise] and
163asynchronously parses
164a JSON file
165from disk.
166
167It takes two arguments;
168the path to the JSON file
169and an [options](#options-for-parsing-functions) object.
170
171If there are
172no syntax errors,
173the returned promise is resolved
174with the parsed data.
175If syntax errors occur,
176the promise is rejected
177with the first error.
178
179## How do I parse a stream of JSON?
180
181```js
182const bfj = require('bfj');
183
184// By passing a readable stream to bfj.parse():
185bfj.parse(fs.createReadStream(path), options)
186 .then(data => {
187 // :)
188 })
189 .catch(error => {
190 // :(
191 });
192
193// ...or by passing the result from bfj.unpipe() to stream.pipe():
194request({ url }).pipe(bfj.unpipe((error, data) => {
195 if (error) {
196 // :(
197 } else {
198 // :)
199 }
200}))
201```
202
203* `parse` returns a [bluebird promise][promise]
204 and asynchronously parses
205 a stream of JSON data.
206
207 It takes two arguments;
208 a [readable stream][readable]
209 from which
210 the JSON
211 will be parsed
212 and an [options](#options-for-parsing-functions) object.
213
214 If there are
215 no syntax errors,
216 the returned promise is resolved
217 with the parsed data.
218 If syntax errors occur,
219 the promise is rejected
220 with the first error.
221
222* `unpipe` returns a [writable stream][writable]
223 that can be passed to [`stream.pipe`][pipe],
224 then parses JSON data
225 read from the stream.
226
227 It takes two arguments;
228 a callback function
229 that will be called
230 after parsing is complete
231 and an [options](#options-for-parsing-functions) object.
232
233 If there are no errors,
234 the callback is invoked
235 with the result as the second argument.
236 If errors occur,
237 the first error is passed
238 the callback
239 as the first argument.
240
241## How do I selectively parse individual items from a JSON stream?
242
243```js
244const bfj = require('bfj');
245
246// Call match with your stream and a selector predicate/regex/string
247const dataStream = bfj.match(jsonStream, selector, options);
248
249// Get data out of the returned stream with event handlers
250dataStream.on('data', item => { /* ... */ });
251dataStream.on('end', () => { /* ... */);
252dataStream.on('error', () => { /* ... */);
253dataStream.on('dataError', () => { /* ... */);
254
255// ...or you can pipe it to another stream
256dataStream.pipe(someOtherStream);
257```
258
259`match` returns a readable, object-mode stream
260and asynchronously parses individual matching items
261from an input JSON stream.
262
263It takes three arguments:
264a [readable stream][readable]
265from which the JSON will be parsed;
266a selector argument for determining matches,
267which may be a string, a regular expression or a predicate function;
268and an [options](#options-for-parsing-functions) object.
269
270If the selector is a string,
271it will be compared to property keys
272to determine whether
273each item in the data is a match.
274If it is a regular expression,
275the comparison will be made
276by calling the [RegExp `test` method][regexp-test]
277with the property key.
278Predicate functions will be called with three arguments:
279`key`, `value` and `depth`.
280If the result of the predicate is a truthy value
281then the item will be deemed a match.
282
283In addition to the regular options
284accepted by other parsing functions,
285you can also specify `minDepth`
286to only apply the selector
287to certain depths.
288This can improve performance
289and memory usage,
290if you know that
291you're not interested in
292parsing top-level items.
293
294If there are any syntax errors in the JSON,
295a `dataError` event will be emitted.
296If any other errors occur,
297an `error` event will be emitted.
298
299## How do I write a JSON file?
300
301```js
302const bfj = require('bfj');
303
304bfj.write(path, data, options)
305 .then(() => {
306 // :)
307 })
308 .catch(error => {
309 // :(
310 });
311```
312
313`write` returns a [bluebird promise][promise]
314and asynchronously serialises a data structure
315to a JSON file on disk.
316The promise is resolved
317when the file has been written,
318or rejected with the error
319if writing failed.
320
321It takes three arguments;
322the path to the JSON file,
323the data structure to serialise
324and an [options](#options-for-serialisation-functions) object.
325
326## How do I create a stream of JSON?
327
328```js
329const bfj = require('bfj');
330
331const stream = bfj.streamify(data, options);
332
333// Get data out of the stream with event handlers
334stream.on('data', chunk => { /* ... */ });
335stream.on('end', () => { /* ... */);
336stream.on('error', () => { /* ... */);
337stream.on('dataError', () => { /* ... */);
338
339// ...or you can pipe it to another stream
340stream.pipe(someOtherStream);
341```
342
343`streamify` returns a [readable stream][readable]
344and asynchronously serialises
345a data structure to JSON,
346pushing the result
347to the returned stream.
348
349It takes two arguments;
350the data structure to serialise
351and an [options](#options-for-serialisation-functions) object.
352
353If there a circular reference is encountered in the data
354and `options.circular` is not set to `'ignore'`,
355a `dataError` event will be emitted.
356If any other errors occur,
357an `error` event will be emitted.
358
359## How do I create a JSON string?
360
361```js
362const bfj = require('bfj');
363
364bfj.stringify(data, options)
365 .then(json => {
366 // :)
367 })
368 .catch(error => {
369 // :(
370 });
371```
372
373`stringify` returns a [bluebird promise][promise] and
374asynchronously serialises a data structure
375to a JSON string.
376The promise is resolved
377to the JSON string
378when serialisation is complete.
379
380It takes two arguments;
381the data structure to serialise
382and an [options](#options-for-serialisation-functions) object.
383
384## What other methods are there?
385
386### bfj.walk (stream, options)
387
388```js
389const bfj = require('bfj');
390
391const emitter = bfj.walk(fs.createReadStream(path), options);
392
393emitter.on(bfj.events.array, () => { /* ... */ });
394emitter.on(bfj.events.object, () => { /* ... */ });
395emitter.on(bfj.events.property, name => { /* ... */ });
396emitter.on(bfj.events.string, value => { /* ... */ });
397emitter.on(bfj.events.number, value => { /* ... */ });
398emitter.on(bfj.events.literal, value => { /* ... */ });
399emitter.on(bfj.events.endArray, () => { /* ... */ });
400emitter.on(bfj.events.endObject, () => { /* ... */ });
401emitter.on(bfj.events.error, error => { /* ... */ });
402emitter.on(bfj.events.dataError, error => { /* ... */ });
403emitter.on(bfj.events.end, () => { /* ... */ });
404```
405
406`walk` returns an [event emitter][eventemitter]
407and asynchronously walks
408a stream of JSON data,
409emitting events
410as it encounters
411tokens.
412
413It takes two arguments;
414a [readable stream][readable]
415from which
416the JSON
417will be read
418and an [options](#options-for-parsing-functions) object.
419
420The emitted events
421are defined
422as public properties
423of an object,
424`bfj.events`:
425
426* `bfj.events.array`
427 indicates that
428 an array context
429 has been entered
430 by encountering
431 the `[` character.
432
433* `bfj.events.endArray`
434 indicates that
435 an array context
436 has been left
437 by encountering
438 the `]` character.
439
440* `bfj.events.object`
441 indicates that
442 an object context
443 has been entered
444 by encountering
445 the `{` character.
446
447* `bfj.events.endObject`
448 indicates that
449 an object context
450 has been left
451 by encountering
452 the `}` character.
453
454* `bfj.events.property`
455 indicates that
456 a property
457 has been encountered
458 in an object.
459 The listener
460 will be passed
461 the name of the property
462 as its argument
463 and the next event
464 to be emitted
465 will represent
466 the property's value.
467
468* `bfj.events.string`
469 indicates that
470 a string
471 has been encountered.
472 The listener
473 will be passed
474 the value
475 as its argument.
476
477* `bfj.events.number`
478 indicates that
479 a number
480 has been encountered.
481 The listener
482 will be passed
483 the value
484 as its argument.
485
486* `bfj.events.literal`
487 indicates that
488 a JSON literal
489 (either `true`, `false` or `null`)
490 has been encountered.
491 The listener
492 will be passed
493 the value
494 as its argument.
495
496* `bfj.events.error`
497 indicates that
498 an error was caught
499 from one of the event handlers
500 in user code.
501 The listener
502 will be passed
503 the `Error` instance
504 as its argument.
505
506* `bfj.events.dataError`
507 indicates that
508 a syntax error was encountered
509 in the incoming JSON stream.
510 The listener
511 will be passed
512 an `Error` instance
513 decorated with `actual`, `expected`, `lineNumber` and `columnNumber` properties
514 as its argument.
515
516* `bfj.events.end`
517 indicates that
518 the end of the input
519 has been reached
520 and the stream is closed.
521
522* `bfj.events.endLine`
523 indicates that a root-level newline character
524 has been encountered in an [NDJSON](#can-it-handle-newline-delimited-json-ndjson) stream.
525 Only emitted if the `ndjson` [option](#options-for-parsing-functions) is set.
526
527If you are using `bfj.walk`
528to sequentially parse items in an array,
529you might also be interested in
530the [bfj-collections] module.
531
532### bfj.eventify (data, options)
533
534```js
535const bfj = require('bfj');
536
537const emitter = bfj.eventify(data, options);
538
539emitter.on(bfj.events.array, () => { /* ... */ });
540emitter.on(bfj.events.object, () => { /* ... */ });
541emitter.on(bfj.events.property, name => { /* ... */ });
542emitter.on(bfj.events.string, value => { /* ... */ });
543emitter.on(bfj.events.number, value => { /* ... */ });
544emitter.on(bfj.events.literal, value => { /* ... */ });
545emitter.on(bfj.events.endArray, () => { /* ... */ });
546emitter.on(bfj.events.endObject, () => { /* ... */ });
547emitter.on(bfj.events.error, error => { /* ... */ });
548emitter.on(bfj.events.dataError, error => { /* ... */ });
549emitter.on(bfj.events.end, () => { /* ... */ });
550```
551
552`eventify` returns an [event emitter][eventemitter]
553and asynchronously traverses
554a data structure depth-first,
555emitting events as it
556encounters items.
557By default it coerces
558promises, buffers and iterables
559to JSON-friendly values.
560
561It takes two arguments;
562the data structure to traverse
563and an [options](#options-for-serialisation-functions) object.
564
565The emitted events
566are defined
567as public properties
568of an object,
569`bfj.events`:
570
571* `bfj.events.array`
572 indicates that
573 an array
574 has been encountered.
575
576* `bfj.events.endArray`
577 indicates that
578 the end of an array
579 has been encountered.
580
581* `bfj.events.object`
582 indicates that
583 an object
584 has been encountered.
585
586* `bfj.events.endObject`
587 indicates that
588 the end of an object
589 has been encountered.
590
591* `bfj.events.property`
592 indicates that
593 a property
594 has been encountered
595 in an object.
596 The listener
597 will be passed
598 the name of the property
599 as its argument
600 and the next event
601 to be emitted
602 will represent
603 the property's value.
604
605* `bfj.events.string`
606 indicates that
607 a string
608 has been encountered.
609 The listener
610 will be passed
611 the value
612 as its argument.
613
614* `bfj.events.number`
615 indicates that
616 a number
617 has been encountered.
618 The listener
619 will be passed
620 the value
621 as its argument.
622
623* `bfj.events.literal`
624 indicates that
625 a JSON literal
626 (either `true`, `false` or `null`)
627 has been encountered.
628 The listener
629 will be passed
630 the value
631 as its argument.
632
633* `bfj.events.error`
634 indicates that
635 an error was caught
636 from one of the event handlers
637 in user code.
638 The listener
639 will be passed
640 the `Error` instance
641 as its argument.
642
643* `bfj.events.dataError`
644 indicates that
645 a circular reference was encountered in the data
646 and the `circular` option was not set to `'ignore'`.
647 The listener
648 will be passed
649 an `Error` instance
650 as its argument.
651
652* `bfj.events.end`
653 indicates that
654 the end of the data
655 has been reached and
656 no further events
657 will be emitted.
658
659## What options can I specify?
660
661### Options for parsing functions
662
663* `options.reviver`:
664 Transformation function,
665 invoked depth-first
666 against the parsed
667 data structure.
668 This option
669 is analagous to the
670 [reviver parameter for JSON.parse][reviver].
671
672* `options.yieldRate`:
673 The number of data items to process
674 before yielding to the event loop.
675 Smaller values yield to the event loop more frequently,
676 meaning less time will be consumed by bfj per tick
677 but the overall parsing time will be slower.
678 Larger values yield to the event loop less often,
679 meaning slower tick times but faster overall parsing time.
680 The default value is `16384`.
681
682* `options.Promise`:
683 Promise constructor that will be used
684 for promises returned by all methods.
685 If you set this option,
686 please be aware that some promise implementations
687 (including native promises)
688 may cause your process to die
689 with out-of-memory exceptions.
690 Defaults to [bluebird's implementation][promise],
691 which does not have that problem.
692
693* `options.ndjson`:
694 If set to `true`,
695 newline characters at the root level
696 will be treated as delimiters between
697 discrete chunks of JSON.
698 See [NDJSON](#can-it-handle-newline-delimited-json-ndjson) for more information.
699
700* `options.numbers`:
701 For `bfj.match` only,
702 set this to `true`
703 if you wish to match against numbers
704 with a string or regular expression
705 `selector` argument.
706
707* `options.bufferLength`:
708 For `bfj.match` only,
709 the length of the match buffer.
710 Smaller values use less memory
711 but may result in a slower parse time.
712 The default value is `1024`.
713
714* `options.highWaterMark`:
715 For `bfj.match` only,
716 set this if you would like to
717 pass a value for the `highWaterMark` option
718 to the readable stream constructor.
719
720### Options for serialisation functions
721
722* `options.space`:
723 Indentation string
724 or the number of spaces
725 to indent
726 each nested level by.
727 This option
728 is analagous to the
729 [space parameter for JSON.stringify][space].
730
731* `options.promises`:
732 By default,
733 promises are coerced
734 to their resolved value.
735 Set this property
736 to `'ignore'`
737 for improved performance
738 if you don't need
739 to coerce promises.
740
741* `options.buffers`:
742 By default,
743 buffers are coerced
744 using their `toString` method.
745 Set this property
746 to `'ignore'`
747 for improved performance
748 if you don't need
749 to coerce buffers.
750
751* `options.maps`:
752 By default,
753 maps are coerced
754 to plain objects.
755 Set this property
756 to `'ignore'`
757 for improved performance
758 if you don't need
759 to coerce maps.
760
761* `options.iterables`:
762 By default,
763 other iterables
764 (i.e. not arrays, strings or maps)
765 are coerced
766 to arrays.
767 Set this property
768 to `'ignore'`
769 for improved performance
770 if you don't need
771 to coerce iterables.
772
773* `options.circular`:
774 By default,
775 circular references
776 will cause the write
777 to fail.
778 Set this property
779 to `'ignore'`
780 if you'd prefer
781 to silently skip past
782 circular references
783 in the data.
784
785* `options.bufferLength`:
786 The length of the write buffer.
787 Smaller values use less memory
788 but may result in a slower serialisation time.
789 The default value is `1024`.
790
791* `options.highWaterMark`:
792 Set this if you would like to
793 pass a value for the `highWaterMark` option
794 to the readable stream constructor.
795
796* `options.yieldRate`:
797 The number of data items to process
798 before yielding to the event loop.
799 Smaller values yield to the event loop more frequently,
800 meaning less time will be consumed by bfj per tick
801 but the overall serialisation time will be slower.
802 Larger values yield to the event loop less often,
803 meaning slower tick times but faster overall serialisation time.
804 The default value is `16384`.
805
806* `options.Promise`:
807 Promise constructor that will be used
808 for promises returned by all methods.
809 If you set this option,
810 please be aware that some promise implementations
811 (including native promises)
812 may cause your process to die
813 with out-of-memory exceptions.
814 Defaults to [bluebird's implementation][promise],
815 which does not have that problem.
816
817## Is it possible to pause parsing or serialisation from calling code?
818
819Yes it is!
820Both [`walk`](#bfjwalk-stream-options)
821and [`eventify`](#bfjeventify-data-options)
822decorate their returned event emitters
823with a `pause` method
824that will prevent any further events being emitted.
825The `pause` method itself
826returns a `resume` function
827that you can call to indicate
828that processing should continue.
829
830For example:
831
832```js
833const bfj = require('bfj');
834const emitter = bfj.walk(fs.createReadStream(path), options);
835
836// Later, when you want to pause parsing:
837
838const resume = emitter.pause();
839
840// Then when you want to resume:
841
842resume();
843```
844
845## Can it handle [newline-delimited JSON (NDJSON)](http://ndjson.org/)?
846
847Yes.
848If you pass the `ndjson` [option](#options-for-parsing-functions)
849to `bfj.walk`, `bfj.match` or `bfj.parse`,
850newline characters at the root level
851will act as delimiters between
852discrete JSON values:
853
854* `bfj.walk` will emit a `bfj.events.endLine` event
855 each time it encounters a newline character.
856
857* `bfj.match` will just ignore the newlines
858 while it continues looking for matching items.
859
860* `bfj.parse` will resolve with the first value
861 and pause the underlying stream.
862 If it's called again with the same stream,
863 it will resume processing
864 and resolve with the second value.
865 To parse the entire stream,
866 calls should be made sequentially one-at-a-time
867 until the returned promise
868 resolves to `undefined`
869 (`undefined` is not a valid JSON token).
870
871`bfj.unpipe` and `bfj.read` will not parse NDJSON.
872
873## Why does it default to bluebird promises?
874
875Until version `4.2.4`,
876native promises were used.
877But they were found
878to cause out-of-memory errors
879when serialising large amounts of data to JSON,
880due to [well-documented problems
881with the native promise implementation](https://alexn.org/blog/2017/10/11/javascript-promise-leaks-memory.html).
882So in version `5.0.0`,
883bluebird promises were used instead.
884In version `5.1.0`,
885an option was added
886that enables callers to specify
887the promise constructor to use.
888Use it at your own risk.
889
890## Can I specify a different promise implementation?
891
892Yes.
893Just pass the `Promise` option
894to any method.
895If you get out-of-memory errors
896when using that option,
897consider changing your promise implementation.
898
899## Is there a change log?
900
901[Yes][history].
902
903## How do I set up the dev environment?
904
905The development environment
906relies on [Node.js][node],
907[ESLint],
908[Mocha],
909[Chai],
910[Proxyquire] and
911[Spooks].
912Assuming that
913you already have
914node and NPM
915set up,
916you just need
917to run
918`npm install`
919to install
920all of the dependencies
921as listed in `package.json`.
922
923You can
924lint the code
925with the command
926`npm run lint`.
927
928You can
929run the tests
930with the command
931`npm test`.
932
933## What versions of Node.js does it support?
934
935As of [version `7.0.0`](HISTORY.md#700),
936only Node.js versions 8 or greater
937are supported.
938
939Between versions [`3.0.0`](HISTORY.md#300)
940and [`6.1.2`](HISTORY.md#612),
941only Node.js versions 6 or greater
942were supported.
943
944Until [version `2.1.2`](HISTORY.md#212),
945only Node.js versions 4 or greater
946were supported.
947
948## What license is it released under?
949
950[MIT][license].
951
952[ci-image]: https://secure.travis-ci.org/philbooth/bfj.png?branch=master
953[ci-status]: http://travis-ci.org/#!/philbooth/bfj
954[sax]: http://en.wikipedia.org/wiki/Simple_API_for_XML
955[promise]: http://bluebirdjs.com/docs/api-reference.html
956[bfj-collections]: https://github.com/hash-bang/bfj-collections
957[eventemitter]: https://nodejs.org/api/events.html#events_class_eventemitter
958[readable]: https://nodejs.org/api/stream.html#stream_readable_streams
959[writable]: https://nodejs.org/api/stream.html#stream_writable_streams
960[pipe]: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
961[regexp-test]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test
962[reviver]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Using_the_reviver_parameter
963[space]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_space_argument
964[history]: HISTORY.md
965[node]: https://nodejs.org/en/
966[eslint]: http://eslint.org/
967[mocha]: https://mochajs.org/
968[chai]: http://chaijs.com/
969[proxyquire]: https://github.com/thlorenz/proxyquire
970[spooks]: https://gitlab.com/philbooth/spooks.js
971[license]: COPYING