├── .github ├── dependabot.yml ├── release-drafter.yml └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── SECURITY.md ├── bench.js ├── index.js ├── package.json └── test.js /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: tap 10 | versions: 11 | - ">= 13.a, < 14" 12 | - dependency-name: tap 13 | versions: 14 | - ">= 14.a, < 15" 15 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | template: | 2 | ## What’s Changed 3 | 4 | $CHANGES 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | # This allows a subsequently queued workflow run to interrupt previous runs 6 | concurrency: 7 | group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | 14 | permissions: 15 | contents: read 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x, 14.x, 16.x, 18.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | with: 24 | persist-credentials: false 25 | 26 | - name: Use Node.js 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | 31 | - name: Install 32 | run: | 33 | npm install 34 | 35 | - name: Run tests 36 | run: | 37 | npm run test 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | libleveldb.so 4 | libleveldb.a 5 | test-data/ 6 | _benchdb_* 7 | *.sw* 8 | .nyc_output 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | libleveldb.so 4 | libleveldb.a 5 | test-data/ 6 | _benchdb_* 7 | *.sw* 8 | .travis.yml 9 | .github 10 | .nyc_output 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2018, Matteo Collina 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 13 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Split2(matcher, mapper, options) 2 | 3 | ![ci](https://github.com/mcollina/split2/workflows/ci/badge.svg) 4 | 5 | Break up a stream and reassemble it so that each line is a chunk. 6 | `split2` is inspired by [@dominictarr](https://github.com/dominictarr) [`split`](https://github.com/dominictarr/split) module, 7 | and it is totally API compatible with it. 8 | However, it is based on Node.js core [`Transform`](https://nodejs.org/api/stream.html#stream_new_stream_transform_options). 9 | 10 | `matcher` may be a `String`, or a `RegExp`. Example, read every line in a file ... 11 | 12 | ``` js 13 | fs.createReadStream(file) 14 | .pipe(split2()) 15 | .on('data', function (line) { 16 | //each chunk now is a separate line! 17 | }) 18 | 19 | ``` 20 | 21 | `split` takes the same arguments as `string.split` except it defaults to '/\r?\n/', and the optional `limit` paremeter is ignored. 22 | [String#split](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/split) 23 | 24 | `split` takes an optional options object on it's third argument, which 25 | is directly passed as a 26 | [Transform](https://nodejs.org/api/stream.html#stream_new_stream_transform_options) 27 | option. 28 | 29 | Additionally, the `.maxLength` and `.skipOverflow` options are implemented, which set limits on the internal 30 | buffer size and the stream's behavior when the limit is exceeded. There is no limit unless `maxLength` is set. When 31 | the internal buffer size exceeds `maxLength`, the stream emits an error by default. You may also set `skipOverflow` to 32 | true to suppress the error and instead skip past any lines that cause the internal buffer to exceed `maxLength`. 33 | 34 | Calling `.destroy` will make the stream emit `close`. Use this to perform cleanup logic 35 | 36 | ``` js 37 | var splitFile = function(filename) { 38 | var file = fs.createReadStream(filename) 39 | 40 | return file 41 | .pipe(split2()) 42 | .on('close', function() { 43 | // destroy the file stream in case the split stream was destroyed 44 | file.destroy() 45 | }) 46 | } 47 | 48 | var stream = splitFile('my-file.txt') 49 | 50 | stream.destroy() // will destroy the input file stream 51 | ``` 52 | 53 | # NDJ - Newline Delimited Json 54 | 55 | `split2` accepts a function which transforms each line. 56 | 57 | ``` js 58 | fs.createReadStream(file) 59 | .pipe(split2(JSON.parse)) 60 | .on('data', function (obj) { 61 | //each chunk now is a js object 62 | }) 63 | .on("error", function(error) { 64 | //handling parsing errors 65 | }) 66 | ``` 67 | 68 | However, in [@dominictarr](https://github.com/dominictarr) [`split`](https://github.com/dominictarr/split) the mapper 69 | is wrapped in a try-catch, while here it is not: if your parsing logic can throw, wrap it yourself. Otherwise, you can also use the stream error handling when mapper function throw. 70 | 71 | # License 72 | 73 | Copyright (c) 2014-2021, Matteo Collina 74 | 75 | Permission to use, copy, modify, and/or distribute this software for any 76 | purpose with or without fee is hereby granted, provided that the above 77 | copyright notice and this permission notice appear in all copies. 78 | 79 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 80 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 81 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 82 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 83 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 84 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 85 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 86 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 4.x.x | :white_check_mark: | 11 | | 3.x.x | :white_check_mark: | 12 | | 2.x.x | :white_check_mark: | 13 | | 1.x.x | :white_check_mark: | 14 | | < 1.0.0 | :x: | 15 | 16 | ## Reporting a Vulnerability 17 | 18 | Please report them at [https://github.com/mcollina/split2/security](https://github.com/mcollina/split2/security). 19 | -------------------------------------------------------------------------------- /bench.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const split = require('./') 4 | const bench = require('fastbench') 5 | const binarySplit = require('binary-split') 6 | const fs = require('fs') 7 | 8 | function benchSplit (cb) { 9 | fs.createReadStream('package.json') 10 | .pipe(split()) 11 | .on('end', cb) 12 | .resume() 13 | } 14 | 15 | function benchBinarySplit (cb) { 16 | fs.createReadStream('package.json') 17 | .pipe(binarySplit()) 18 | .on('end', cb) 19 | .resume() 20 | } 21 | 22 | const run = bench([ 23 | benchSplit, 24 | benchBinarySplit 25 | ], 10000) 26 | 27 | run(run) 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2014-2021, Matteo Collina 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 14 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | 'use strict' 18 | 19 | const { Transform } = require('stream') 20 | const { StringDecoder } = require('string_decoder') 21 | const kLast = Symbol('last') 22 | const kDecoder = Symbol('decoder') 23 | 24 | function transform (chunk, enc, cb) { 25 | let list 26 | if (this.overflow) { // Line buffer is full. Skip to start of next line. 27 | const buf = this[kDecoder].write(chunk) 28 | list = buf.split(this.matcher) 29 | 30 | if (list.length === 1) return cb() // Line ending not found. Discard entire chunk. 31 | 32 | // Line ending found. Discard trailing fragment of previous line and reset overflow state. 33 | list.shift() 34 | this.overflow = false 35 | } else { 36 | this[kLast] += this[kDecoder].write(chunk) 37 | list = this[kLast].split(this.matcher) 38 | } 39 | 40 | this[kLast] = list.pop() 41 | 42 | for (let i = 0; i < list.length; i++) { 43 | try { 44 | push(this, this.mapper(list[i])) 45 | } catch (error) { 46 | return cb(error) 47 | } 48 | } 49 | 50 | this.overflow = this[kLast].length > this.maxLength 51 | if (this.overflow && !this.skipOverflow) { 52 | cb(new Error('maximum buffer reached')) 53 | return 54 | } 55 | 56 | cb() 57 | } 58 | 59 | function flush (cb) { 60 | // forward any gibberish left in there 61 | this[kLast] += this[kDecoder].end() 62 | 63 | if (this[kLast]) { 64 | try { 65 | push(this, this.mapper(this[kLast])) 66 | } catch (error) { 67 | return cb(error) 68 | } 69 | } 70 | 71 | cb() 72 | } 73 | 74 | function push (self, val) { 75 | if (val !== undefined) { 76 | self.push(val) 77 | } 78 | } 79 | 80 | function noop (incoming) { 81 | return incoming 82 | } 83 | 84 | function split (matcher, mapper, options) { 85 | // Set defaults for any arguments not supplied. 86 | matcher = matcher || /\r?\n/ 87 | mapper = mapper || noop 88 | options = options || {} 89 | 90 | // Test arguments explicitly. 91 | switch (arguments.length) { 92 | case 1: 93 | // If mapper is only argument. 94 | if (typeof matcher === 'function') { 95 | mapper = matcher 96 | matcher = /\r?\n/ 97 | // If options is only argument. 98 | } else if (typeof matcher === 'object' && !(matcher instanceof RegExp) && !matcher[Symbol.split]) { 99 | options = matcher 100 | matcher = /\r?\n/ 101 | } 102 | break 103 | 104 | case 2: 105 | // If mapper and options are arguments. 106 | if (typeof matcher === 'function') { 107 | options = mapper 108 | mapper = matcher 109 | matcher = /\r?\n/ 110 | // If matcher and options are arguments. 111 | } else if (typeof mapper === 'object') { 112 | options = mapper 113 | mapper = noop 114 | } 115 | } 116 | 117 | options = Object.assign({}, options) 118 | options.autoDestroy = true 119 | options.transform = transform 120 | options.flush = flush 121 | options.readableObjectMode = true 122 | 123 | const stream = new Transform(options) 124 | 125 | stream[kLast] = '' 126 | stream[kDecoder] = new StringDecoder('utf8') 127 | stream.matcher = matcher 128 | stream.mapper = mapper 129 | stream.maxLength = options.maxLength 130 | stream.skipOverflow = options.skipOverflow || false 131 | stream.overflow = false 132 | stream._destroy = function (err, cb) { 133 | // Weird Node v12 bug that we need to work around 134 | this._writableState.errorEmitted = false 135 | cb(err) 136 | } 137 | 138 | return stream 139 | } 140 | 141 | module.exports = split 142 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "split2", 3 | "version": "4.2.0", 4 | "description": "split a Text Stream into a Line Stream, using Stream 3", 5 | "main": "index.js", 6 | "scripts": { 7 | "lint": "standard --verbose", 8 | "unit": "nyc --lines 100 --branches 100 --functions 100 --check-coverage --reporter=text tape test.js", 9 | "coverage": "nyc --reporter=html --reporter=cobertura --reporter=text tape test/test.js", 10 | "test:report": "npm run lint && npm run unit:report", 11 | "test": "npm run lint && npm run unit", 12 | "legacy": "tape test.js" 13 | }, 14 | "pre-commit": [ 15 | "test" 16 | ], 17 | "website": "https://github.com/mcollina/split2", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/mcollina/split2.git" 21 | }, 22 | "bugs": { 23 | "url": "http://github.com/mcollina/split2/issues" 24 | }, 25 | "engines": { 26 | "node": ">= 12.x" 27 | }, 28 | "author": "Matteo Collina ", 29 | "license": "ISC", 30 | "devDependencies": { 31 | "binary-split": "^1.0.3", 32 | "callback-stream": "^1.1.0", 33 | "fastbench": "^1.0.0", 34 | "nyc": "^15.0.1", 35 | "pre-commit": "^1.1.2", 36 | "standard": "^17.0.0", 37 | "tape": "^5.0.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('tape') 4 | const split = require('./') 5 | const callback = require('callback-stream') 6 | const strcb = callback.bind(null, { decodeStrings: false }) 7 | const objcb = callback.bind(null, { objectMode: true }) 8 | 9 | test('split two lines on end', function (t) { 10 | t.plan(2) 11 | 12 | const input = split() 13 | 14 | input.pipe(strcb(function (err, list) { 15 | t.error(err) 16 | t.deepEqual(list, ['hello', 'world']) 17 | })) 18 | 19 | input.end('hello\nworld') 20 | }) 21 | 22 | test('split two lines on two writes', function (t) { 23 | t.plan(2) 24 | 25 | const input = split() 26 | 27 | input.pipe(strcb(function (err, list) { 28 | t.error(err) 29 | t.deepEqual(list, ['hello', 'world']) 30 | })) 31 | 32 | input.write('hello') 33 | input.write('\nworld') 34 | input.end() 35 | }) 36 | 37 | test('split four lines on three writes', function (t) { 38 | t.plan(2) 39 | 40 | const input = split() 41 | 42 | input.pipe(strcb(function (err, list) { 43 | t.error(err) 44 | t.deepEqual(list, ['hello', 'world', 'bye', 'world']) 45 | })) 46 | 47 | input.write('hello\nwor') 48 | input.write('ld\nbye\nwo') 49 | input.write('rld') 50 | input.end() 51 | }) 52 | 53 | test('accumulate multiple writes', function (t) { 54 | t.plan(2) 55 | 56 | const input = split() 57 | 58 | input.pipe(strcb(function (err, list) { 59 | t.error(err) 60 | t.deepEqual(list, ['helloworld']) 61 | })) 62 | 63 | input.write('hello') 64 | input.write('world') 65 | input.end() 66 | }) 67 | 68 | test('split using a custom string matcher', function (t) { 69 | t.plan(2) 70 | 71 | const input = split('~') 72 | 73 | input.pipe(strcb(function (err, list) { 74 | t.error(err) 75 | t.deepEqual(list, ['hello', 'world']) 76 | })) 77 | 78 | input.end('hello~world') 79 | }) 80 | 81 | test('split using a custom regexp matcher', function (t) { 82 | t.plan(2) 83 | 84 | const input = split(/~/) 85 | 86 | input.pipe(strcb(function (err, list) { 87 | t.error(err) 88 | t.deepEqual(list, ['hello', 'world']) 89 | })) 90 | 91 | input.end('hello~world') 92 | }) 93 | 94 | test('support an option argument', function (t) { 95 | t.plan(2) 96 | 97 | const input = split({ highWaterMark: 2 }) 98 | 99 | input.pipe(strcb(function (err, list) { 100 | t.error(err) 101 | t.deepEqual(list, ['hello', 'world']) 102 | })) 103 | 104 | input.end('hello\nworld') 105 | }) 106 | 107 | test('support a mapper function', function (t) { 108 | t.plan(2) 109 | 110 | const a = { a: '42' } 111 | const b = { b: '24' } 112 | 113 | const input = split(JSON.parse) 114 | 115 | input.pipe(objcb(function (err, list) { 116 | t.error(err) 117 | t.deepEqual(list, [a, b]) 118 | })) 119 | 120 | input.write(JSON.stringify(a)) 121 | input.write('\n') 122 | input.end(JSON.stringify(b)) 123 | }) 124 | 125 | test('split lines windows-style', function (t) { 126 | t.plan(2) 127 | 128 | const input = split() 129 | 130 | input.pipe(strcb(function (err, list) { 131 | t.error(err) 132 | t.deepEqual(list, ['hello', 'world']) 133 | })) 134 | 135 | input.end('hello\r\nworld') 136 | }) 137 | 138 | test('splits a buffer', function (t) { 139 | t.plan(2) 140 | 141 | const input = split() 142 | 143 | input.pipe(strcb(function (err, list) { 144 | t.error(err) 145 | t.deepEqual(list, ['hello', 'world']) 146 | })) 147 | 148 | input.end(Buffer.from('hello\nworld')) 149 | }) 150 | 151 | test('do not end on undefined', function (t) { 152 | t.plan(2) 153 | 154 | const input = split(function (line) { }) 155 | 156 | input.pipe(strcb(function (err, list) { 157 | t.error(err) 158 | t.deepEqual(list, []) 159 | })) 160 | 161 | input.end(Buffer.from('hello\nworld')) 162 | }) 163 | 164 | test('has destroy method', function (t) { 165 | t.plan(1) 166 | 167 | const input = split(function (line) { }) 168 | 169 | input.on('close', function () { 170 | t.ok(true, 'close emitted') 171 | t.end() 172 | }) 173 | 174 | input.destroy() 175 | }) 176 | 177 | test('support custom matcher and mapper', function (t) { 178 | t.plan(4) 179 | 180 | const a = { a: '42' } 181 | const b = { b: '24' } 182 | const input = split('~', JSON.parse) 183 | 184 | t.equal(input.matcher, '~') 185 | t.equal(typeof input.mapper, 'function') 186 | 187 | input.pipe(objcb(function (err, list) { 188 | t.notOk(err, 'no errors') 189 | t.deepEqual(list, [a, b]) 190 | })) 191 | 192 | input.write(JSON.stringify(a)) 193 | input.write('~') 194 | input.end(JSON.stringify(b)) 195 | }) 196 | 197 | test('support custom matcher and options', function (t) { 198 | t.plan(6) 199 | 200 | const input = split('~', { highWaterMark: 1024 }) 201 | 202 | t.equal(input.matcher, '~') 203 | t.equal(typeof input.mapper, 'function') 204 | t.equal(input._readableState.highWaterMark, 1024) 205 | t.equal(input._writableState.highWaterMark, 1024) 206 | 207 | input.pipe(strcb(function (err, list) { 208 | t.error(err) 209 | t.deepEqual(list, ['hello', 'world']) 210 | })) 211 | 212 | input.end('hello~world') 213 | }) 214 | 215 | test('support mapper and options', function (t) { 216 | t.plan(6) 217 | 218 | const a = { a: '42' } 219 | const b = { b: '24' } 220 | const input = split(JSON.parse, { highWaterMark: 1024 }) 221 | 222 | t.ok(input.matcher instanceof RegExp, 'matcher is RegExp') 223 | t.equal(typeof input.mapper, 'function') 224 | t.equal(input._readableState.highWaterMark, 1024) 225 | t.equal(input._writableState.highWaterMark, 1024) 226 | 227 | input.pipe(objcb(function (err, list) { 228 | t.error(err) 229 | t.deepEqual(list, [a, b]) 230 | })) 231 | 232 | input.write(JSON.stringify(a)) 233 | input.write('\n') 234 | input.end(JSON.stringify(b)) 235 | }) 236 | 237 | test('split utf8 chars', function (t) { 238 | t.plan(2) 239 | 240 | const input = split() 241 | 242 | input.pipe(strcb(function (err, list) { 243 | t.error(err) 244 | t.deepEqual(list, ['烫烫烫', '锟斤拷']) 245 | })) 246 | 247 | const buf = Buffer.from('烫烫烫\r\n锟斤拷', 'utf8') 248 | for (let i = 0; i < buf.length; ++i) { 249 | input.write(buf.slice(i, i + 1)) 250 | } 251 | input.end() 252 | }) 253 | 254 | test('split utf8 chars 2by2', function (t) { 255 | t.plan(2) 256 | 257 | const input = split() 258 | 259 | input.pipe(strcb(function (err, list) { 260 | t.error(err) 261 | t.deepEqual(list, ['烫烫烫', '烫烫烫']) 262 | })) 263 | 264 | const str = '烫烫烫\r\n烫烫烫' 265 | const buf = Buffer.from(str, 'utf8') 266 | for (let i = 0; i < buf.length; i += 2) { 267 | input.write(buf.slice(i, i + 2)) 268 | } 269 | input.end() 270 | }) 271 | 272 | test('split lines when the \n comes at the end of a chunk', function (t) { 273 | t.plan(2) 274 | 275 | const input = split() 276 | 277 | input.pipe(strcb(function (err, list) { 278 | t.error(err) 279 | t.deepEqual(list, ['hello', 'world']) 280 | })) 281 | 282 | input.write('hello\n') 283 | input.end('world') 284 | }) 285 | 286 | test('truncated utf-8 char', function (t) { 287 | t.plan(2) 288 | 289 | const input = split() 290 | 291 | input.pipe(strcb(function (err, list) { 292 | t.error(err) 293 | t.deepEqual(list, ['烫' + Buffer.from('e7', 'hex').toString()]) 294 | })) 295 | 296 | const str = '烫烫' 297 | const buf = Buffer.from(str, 'utf8') 298 | 299 | input.write(buf.slice(0, 3)) 300 | input.end(buf.slice(3, 4)) 301 | }) 302 | 303 | test('maximum buffer limit', function (t) { 304 | t.plan(1) 305 | 306 | const input = split({ maxLength: 2 }) 307 | input.on('error', function (err) { 308 | t.ok(err) 309 | }) 310 | 311 | input.resume() 312 | 313 | input.write('hey') 314 | }) 315 | 316 | test('readable highWaterMark', function (t) { 317 | const input = split() 318 | t.equal(input._readableState.highWaterMark, 16) 319 | t.end() 320 | }) 321 | 322 | test('maxLength < chunk size', function (t) { 323 | t.plan(2) 324 | 325 | const input = split({ maxLength: 2 }) 326 | 327 | input.pipe(strcb(function (err, list) { 328 | t.error(err) 329 | t.deepEqual(list, ['a', 'b']) 330 | })) 331 | 332 | input.end('a\nb') 333 | }) 334 | 335 | test('maximum buffer limit w/skip', function (t) { 336 | t.plan(2) 337 | 338 | const input = split({ maxLength: 2, skipOverflow: true }) 339 | 340 | input.pipe(strcb(function (err, list) { 341 | t.error(err) 342 | t.deepEqual(list, ['a', 'b', 'c']) 343 | })) 344 | 345 | input.write('a\n123') 346 | input.write('456') 347 | input.write('789\nb\nc') 348 | input.end() 349 | }) 350 | 351 | test("don't modify the options object", function (t) { 352 | t.plan(2) 353 | 354 | const options = {} 355 | const input = split(options) 356 | 357 | input.pipe(strcb(function (err, list) { 358 | t.error(err) 359 | t.same(options, {}) 360 | })) 361 | 362 | input.end() 363 | }) 364 | 365 | test('mapper throws flush', function (t) { 366 | t.plan(1) 367 | const error = new Error() 368 | const input = split(function () { 369 | throw error 370 | }) 371 | 372 | input.on('error', (err, list) => { 373 | t.same(err, error) 374 | }) 375 | input.end('hello') 376 | }) 377 | 378 | test('mapper throws on transform', function (t) { 379 | t.plan(1) 380 | 381 | const error = new Error() 382 | const input = split(function (l) { 383 | throw error 384 | }) 385 | 386 | input.on('error', (err) => { 387 | t.same(err, error) 388 | }) 389 | input.write('a') 390 | input.write('\n') 391 | input.end('b') 392 | }) 393 | 394 | test('supports Symbol.split', function (t) { 395 | t.plan(2) 396 | 397 | const input = split({ 398 | [Symbol.split] (str) { 399 | return str.split('~') 400 | } 401 | }) 402 | 403 | input.pipe(strcb(function (err, list) { 404 | t.error(err) 405 | t.deepEqual(list, ['hello', 'world']) 406 | })) 407 | 408 | input.end('hello~world') 409 | }) 410 | --------------------------------------------------------------------------------