source: frontend/node_modules/bfj/README.md

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 24.3 KB
Line 
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/JSONPath/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, a JSONPath 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.
278If it is a JSONPath expression,
279it must start with `$.` to identify the root node
280and only use `child` scope expressions for subsequent nodes.
281Predicate functions will be called with three arguments:
282`key`, `value` and `depth`.
283If the result of the predicate is a truthy value
284then the item will be deemed a match.
285
286In addition to the regular options
287accepted by other parsing functions,
288you can also specify `minDepth`
289to only apply the selector
290to certain depths.
291This can improve performance
292and memory usage,
293if you know that
294you're not interested in
295parsing top-level items.
296
297If there are any syntax errors in the JSON,
298a `dataError` event will be emitted.
299If any other errors occur,
300an `error` event will be emitted.
301
302## How do I write a JSON file?
303
304```js
305const bfj = require('bfj');
306
307bfj.write(path, data, options)
308 .then(() => {
309 // :)
310 })
311 .catch(error => {
312 // :(
313 });
314```
315
316`write` returns a [bluebird promise][promise]
317and asynchronously serialises a data structure
318to a JSON file on disk.
319The promise is resolved
320when the file has been written,
321or rejected with the error
322if writing failed.
323
324It takes three arguments;
325the path to the JSON file,
326the data structure to serialise
327and an [options](#options-for-serialisation-functions) object.
328
329## How do I create a stream of JSON?
330
331```js
332const bfj = require('bfj');
333
334const stream = bfj.streamify(data, options);
335
336// Get data out of the stream with event handlers
337stream.on('data', chunk => { /* ... */ });
338stream.on('end', () => { /* ... */);
339stream.on('error', () => { /* ... */);
340stream.on('dataError', () => { /* ... */);
341
342// ...or you can pipe it to another stream
343stream.pipe(someOtherStream);
344```
345
346`streamify` returns a [readable stream][readable]
347and asynchronously serialises
348a data structure to JSON,
349pushing the result
350to the returned stream.
351
352It takes two arguments;
353the data structure to serialise
354and an [options](#options-for-serialisation-functions) object.
355
356If there a circular reference is encountered in the data
357and `options.circular` is not set to `'ignore'`,
358a `dataError` event will be emitted.
359If any other errors occur,
360an `error` event will be emitted.
361
362## How do I create a JSON string?
363
364```js
365const bfj = require('bfj');
366
367bfj.stringify(data, options)
368 .then(json => {
369 // :)
370 })
371 .catch(error => {
372 // :(
373 });
374```
375
376`stringify` returns a [bluebird promise][promise] and
377asynchronously serialises a data structure
378to a JSON string.
379The promise is resolved
380to the JSON string
381when serialisation is complete.
382
383It takes two arguments;
384the data structure to serialise
385and an [options](#options-for-serialisation-functions) object.
386
387## What other methods are there?
388
389### bfj.walk (stream, options)
390
391```js
392const bfj = require('bfj');
393
394const emitter = bfj.walk(fs.createReadStream(path), options);
395
396emitter.on(bfj.events.array, () => { /* ... */ });
397emitter.on(bfj.events.object, () => { /* ... */ });
398emitter.on(bfj.events.property, name => { /* ... */ });
399emitter.on(bfj.events.string, value => { /* ... */ });
400emitter.on(bfj.events.number, value => { /* ... */ });
401emitter.on(bfj.events.literal, value => { /* ... */ });
402emitter.on(bfj.events.endArray, () => { /* ... */ });
403emitter.on(bfj.events.endObject, () => { /* ... */ });
404emitter.on(bfj.events.error, error => { /* ... */ });
405emitter.on(bfj.events.dataError, error => { /* ... */ });
406emitter.on(bfj.events.end, () => { /* ... */ });
407```
408
409`walk` returns an [event emitter][eventemitter]
410and asynchronously walks
411a stream of JSON data,
412emitting events
413as it encounters
414tokens.
415
416It takes two arguments;
417a [readable stream][readable]
418from which
419the JSON
420will be read
421and an [options](#options-for-parsing-functions) object.
422
423The emitted events
424are defined
425as public properties
426of an object,
427`bfj.events`:
428
429* `bfj.events.array`
430 indicates that
431 an array context
432 has been entered
433 by encountering
434 the `[` character.
435
436* `bfj.events.endArray`
437 indicates that
438 an array context
439 has been left
440 by encountering
441 the `]` character.
442
443* `bfj.events.object`
444 indicates that
445 an object context
446 has been entered
447 by encountering
448 the `{` character.
449
450* `bfj.events.endObject`
451 indicates that
452 an object context
453 has been left
454 by encountering
455 the `}` character.
456
457* `bfj.events.property`
458 indicates that
459 a property
460 has been encountered
461 in an object.
462 The listener
463 will be passed
464 the name of the property
465 as its argument
466 and the next event
467 to be emitted
468 will represent
469 the property's value.
470
471* `bfj.events.string`
472 indicates that
473 a string
474 has been encountered.
475 The listener
476 will be passed
477 the value
478 as its argument.
479
480* `bfj.events.number`
481 indicates that
482 a number
483 has been encountered.
484 The listener
485 will be passed
486 the value
487 as its argument.
488
489* `bfj.events.literal`
490 indicates that
491 a JSON literal
492 (either `true`, `false` or `null`)
493 has been encountered.
494 The listener
495 will be passed
496 the value
497 as its argument.
498
499* `bfj.events.error`
500 indicates that
501 an error was caught
502 from one of the event handlers
503 in user code.
504 The listener
505 will be passed
506 the `Error` instance
507 as its argument.
508
509* `bfj.events.dataError`
510 indicates that
511 a syntax error was encountered
512 in the incoming JSON stream.
513 The listener
514 will be passed
515 an `Error` instance
516 decorated with `actual`, `expected`, `lineNumber` and `columnNumber` properties
517 as its argument.
518
519* `bfj.events.end`
520 indicates that
521 the end of the input
522 has been reached
523 and the stream is closed.
524
525* `bfj.events.endLine`
526 indicates that a root-level newline character
527 has been encountered in an [NDJSON](#can-it-handle-newline-delimited-json-ndjson) stream.
528 Only emitted if the `ndjson` [option](#options-for-parsing-functions) is set.
529
530If you are using `bfj.walk`
531to sequentially parse items in an array,
532you might also be interested in
533the [bfj-collections] module.
534
535### bfj.eventify (data, options)
536
537```js
538const bfj = require('bfj');
539
540const emitter = bfj.eventify(data, options);
541
542emitter.on(bfj.events.array, () => { /* ... */ });
543emitter.on(bfj.events.object, () => { /* ... */ });
544emitter.on(bfj.events.property, name => { /* ... */ });
545emitter.on(bfj.events.string, value => { /* ... */ });
546emitter.on(bfj.events.number, value => { /* ... */ });
547emitter.on(bfj.events.literal, value => { /* ... */ });
548emitter.on(bfj.events.endArray, () => { /* ... */ });
549emitter.on(bfj.events.endObject, () => { /* ... */ });
550emitter.on(bfj.events.error, error => { /* ... */ });
551emitter.on(bfj.events.dataError, error => { /* ... */ });
552emitter.on(bfj.events.end, () => { /* ... */ });
553```
554
555`eventify` returns an [event emitter][eventemitter]
556and asynchronously traverses
557a data structure depth-first,
558emitting events as it
559encounters items.
560By default it coerces
561promises, buffers and iterables
562to JSON-friendly values.
563
564It takes two arguments;
565the data structure to traverse
566and an [options](#options-for-serialisation-functions) object.
567
568The emitted events
569are defined
570as public properties
571of an object,
572`bfj.events`:
573
574* `bfj.events.array`
575 indicates that
576 an array
577 has been encountered.
578
579* `bfj.events.endArray`
580 indicates that
581 the end of an array
582 has been encountered.
583
584* `bfj.events.object`
585 indicates that
586 an object
587 has been encountered.
588
589* `bfj.events.endObject`
590 indicates that
591 the end of an object
592 has been encountered.
593
594* `bfj.events.property`
595 indicates that
596 a property
597 has been encountered
598 in an object.
599 The listener
600 will be passed
601 the name of the property
602 as its argument
603 and the next event
604 to be emitted
605 will represent
606 the property's value.
607
608* `bfj.events.string`
609 indicates that
610 a string
611 has been encountered.
612 The listener
613 will be passed
614 the value
615 as its argument.
616
617* `bfj.events.number`
618 indicates that
619 a number
620 has been encountered.
621 The listener
622 will be passed
623 the value
624 as its argument.
625
626* `bfj.events.literal`
627 indicates that
628 a JSON literal
629 (either `true`, `false` or `null`)
630 has been encountered.
631 The listener
632 will be passed
633 the value
634 as its argument.
635
636* `bfj.events.error`
637 indicates that
638 an error was caught
639 from one of the event handlers
640 in user code.
641 The listener
642 will be passed
643 the `Error` instance
644 as its argument.
645
646* `bfj.events.dataError`
647 indicates that
648 a circular reference was encountered in the data
649 and the `circular` option was not set to `'ignore'`.
650 The listener
651 will be passed
652 an `Error` instance
653 as its argument.
654
655* `bfj.events.end`
656 indicates that
657 the end of the data
658 has been reached and
659 no further events
660 will be emitted.
661
662## What options can I specify?
663
664### Options for parsing functions
665
666* `options.reviver`:
667 Transformation function,
668 invoked depth-first
669 against the parsed
670 data structure.
671 This option
672 is analagous to the
673 [reviver parameter for JSON.parse][reviver].
674
675* `options.yieldRate`:
676 The number of data items to process
677 before yielding to the event loop.
678 Smaller values yield to the event loop more frequently,
679 meaning less time will be consumed by bfj per tick
680 but the overall parsing time will be slower.
681 Larger values yield to the event loop less often,
682 meaning slower tick times but faster overall parsing time.
683 The default value is `16384`.
684
685* `options.Promise`:
686 Promise constructor that will be used
687 for promises returned by all methods.
688 If you set this option,
689 please be aware that some promise implementations
690 (including native promises)
691 may cause your process to die
692 with out-of-memory exceptions.
693 Defaults to [bluebird's implementation][promise],
694 which does not have that problem.
695
696* `options.ndjson`:
697 If set to `true`,
698 newline characters at the root level
699 will be treated as delimiters between
700 discrete chunks of JSON.
701 See [NDJSON](#can-it-handle-newline-delimited-json-ndjson) for more information.
702
703* `options.numbers`:
704 For `bfj.match` only,
705 set this to `true`
706 if you wish to match against numbers
707 with a string or regular expression
708 `selector` argument.
709
710* `options.bufferLength`:
711 For `bfj.match` only,
712 the length of the match buffer.
713 Smaller values use less memory
714 but may result in a slower parse time.
715 The default value is `1024`.
716
717* `options.highWaterMark`:
718 For `bfj.match` only,
719 set this if you would like to
720 pass a value for the `highWaterMark` option
721 to the readable stream constructor.
722
723### Options for serialisation functions
724
725* `options.space`:
726 Indentation string
727 or the number of spaces
728 to indent
729 each nested level by.
730 This option
731 is analagous to the
732 [space parameter for JSON.stringify][space].
733
734* `options.promises`:
735 By default,
736 promises are coerced
737 to their resolved value.
738 Set this property
739 to `'ignore'`
740 for improved performance
741 if you don't need
742 to coerce promises.
743
744* `options.buffers`:
745 By default,
746 buffers are coerced
747 using their `toString` method.
748 Set this property
749 to `'ignore'`
750 for improved performance
751 if you don't need
752 to coerce buffers.
753
754* `options.maps`:
755 By default,
756 maps are coerced
757 to plain objects.
758 Set this property
759 to `'ignore'`
760 for improved performance
761 if you don't need
762 to coerce maps.
763
764* `options.iterables`:
765 By default,
766 other iterables
767 (i.e. not arrays, strings or maps)
768 are coerced
769 to arrays.
770 Set this property
771 to `'ignore'`
772 for improved performance
773 if you don't need
774 to coerce iterables.
775
776* `options.circular`:
777 By default,
778 circular references
779 will cause the write
780 to fail.
781 Set this property
782 to `'ignore'`
783 if you'd prefer
784 to silently skip past
785 circular references
786 in the data.
787
788* `options.bufferLength`:
789 The length of the write buffer.
790 Smaller values use less memory
791 but may result in a slower serialisation time.
792 The default value is `1024`.
793
794* `options.highWaterMark`:
795 Set this if you would like to
796 pass a value for the `highWaterMark` option
797 to the readable stream constructor.
798
799* `options.yieldRate`:
800 The number of data items to process
801 before yielding to the event loop.
802 Smaller values yield to the event loop more frequently,
803 meaning less time will be consumed by bfj per tick
804 but the overall serialisation time will be slower.
805 Larger values yield to the event loop less often,
806 meaning slower tick times but faster overall serialisation time.
807 The default value is `16384`.
808
809* `options.Promise`:
810 Promise constructor that will be used
811 for promises returned by all methods.
812 If you set this option,
813 please be aware that some promise implementations
814 (including native promises)
815 may cause your process to die
816 with out-of-memory exceptions.
817 Defaults to [bluebird's implementation][promise],
818 which does not have that problem.
819
820## Is it possible to pause parsing or serialisation from calling code?
821
822Yes it is!
823Both [`walk`](#bfjwalk-stream-options)
824and [`eventify`](#bfjeventify-data-options)
825decorate their returned event emitters
826with a `pause` method
827that will prevent any further events being emitted.
828The `pause` method itself
829returns a `resume` function
830that you can call to indicate
831that processing should continue.
832
833For example:
834
835```js
836const bfj = require('bfj');
837const emitter = bfj.walk(fs.createReadStream(path), options);
838
839// Later, when you want to pause parsing:
840
841const resume = emitter.pause();
842
843// Then when you want to resume:
844
845resume();
846```
847
848## Can it handle [newline-delimited JSON (NDJSON)](http://ndjson.org/)?
849
850Yes.
851If you pass the `ndjson` [option](#options-for-parsing-functions)
852to `bfj.walk`, `bfj.match` or `bfj.parse`,
853newline characters at the root level
854will act as delimiters between
855discrete JSON values:
856
857* `bfj.walk` will emit a `bfj.events.endLine` event
858 each time it encounters a newline character.
859
860* `bfj.match` will just ignore the newlines
861 while it continues looking for matching items.
862
863* `bfj.parse` will resolve with the first value
864 and pause the underlying stream.
865 If it's called again with the same stream,
866 it will resume processing
867 and resolve with the second value.
868 To parse the entire stream,
869 calls should be made sequentially one-at-a-time
870 until the returned promise
871 resolves to `undefined`
872 (`undefined` is not a valid JSON token).
873
874`bfj.unpipe` and `bfj.read` will not parse NDJSON.
875
876## Why does it default to bluebird promises?
877
878Until version `4.2.4`,
879native promises were used.
880But they were found
881to cause out-of-memory errors
882when serialising large amounts of data to JSON,
883due to [well-documented problems
884with the native promise implementation](https://alexn.org/blog/2017/10/11/javascript-promise-leaks-memory.html).
885So in version `5.0.0`,
886bluebird promises were used instead.
887In version `5.1.0`,
888an option was added
889that enables callers to specify
890the promise constructor to use.
891Use it at your own risk.
892
893## Can I specify a different promise implementation?
894
895Yes.
896Just pass the `Promise` option
897to any method.
898If you get out-of-memory errors
899when using that option,
900consider changing your promise implementation.
901
902## Is there a change log?
903
904[Yes][history].
905
906## How do I set up the dev environment?
907
908The development environment
909relies on [Node.js][node],
910[ESLint],
911[Mocha],
912[Chai],
913[Proxyquire] and
914[Spooks].
915Assuming that
916you already have
917node and NPM
918set up,
919you just need
920to run
921`npm install`
922to install
923all of the dependencies
924as listed in `package.json`.
925
926You can
927lint the code
928with the command
929`npm run lint`.
930
931You can
932run the tests
933with the command
934`npm test`.
935
936## What versions of Node.js does it support?
937
938As of [version `7.0.0`](HISTORY.md#700),
939only Node.js versions 8 or greater
940are supported.
941
942Between versions [`3.0.0`](HISTORY.md#300)
943and [`6.1.2`](HISTORY.md#612),
944only Node.js versions 6 or greater
945were supported.
946
947Until [version `2.1.2`](HISTORY.md#212),
948only Node.js versions 4 or greater
949were supported.
950
951## What license is it released under?
952
953[MIT][license].
954
955[ci-image]: https://secure.travis-ci.org/philbooth/bfj.png?branch=master
956[ci-status]: http://travis-ci.org/#!/philbooth/bfj
957[sax]: http://en.wikipedia.org/wiki/Simple_API_for_XML
958[promise]: http://bluebirdjs.com/docs/api-reference.html
959[bfj-collections]: https://github.com/hash-bang/bfj-collections
960[eventemitter]: https://nodejs.org/api/events.html#events_class_eventemitter
961[readable]: https://nodejs.org/api/stream.html#stream_readable_streams
962[writable]: https://nodejs.org/api/stream.html#stream_writable_streams
963[pipe]: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
964[regexp-test]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test
965[reviver]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Using_the_reviver_parameter
966[space]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_space_argument
967[history]: HISTORY.md
968[node]: https://nodejs.org/en/
969[eslint]: http://eslint.org/
970[mocha]: https://mochajs.org/
971[chai]: http://chaijs.com/
972[proxyquire]: https://github.com/thlorenz/proxyquire
973[spooks]: https://gitlab.com/philbooth/spooks.js
974[license]: COPYING
Note: See TracBrowser for help on using the repository browser.