newline-async-iterator
Version:
Line-by-line async iterator for the browser and node
199 lines (193 loc) • 7.38 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.newlineAsyncIterator = factory());
})(this, (function () { 'use strict';
// Feature detection - no global modifications per polyfill-removal plans
/**
* Convert a Uint8Array to a number array
* Compatible with Node 0.8+ (no Array.from)
*/ function uint8ArrayToArray(uint8Array) {
if (typeof Array.from === 'function') return Array.from(uint8Array);
// Fallback for old environments without Array.from
var arr = [];
for(var i = 0; i < uint8Array.length; i++){
arr.push(uint8Array[i]);
}
return arr;
}
/**
* Get the expected length of a UTF-8 sequence from its first byte
*/ function getUTF8SequenceLength(byte) {
if (byte < 0x80) return 1; // 0xxxxxxx - ASCII
if (byte < 0xc0) return 0; // 10xxxxxx - continuation byte (invalid as start)
if (byte < 0xe0) return 2; // 110xxxxx
if (byte < 0xf0) return 3; // 1110xxxx
if (byte < 0xf8) return 4; // 11110xxx
return 0; // Invalid UTF-8 start byte
}
/**
* Decode a single UTF-8 code point from bytes
*/ function decodeCodePoint(bytes, start, length) {
var codePoint;
switch(length){
case 1:
codePoint = bytes[start];
break;
case 2:
codePoint = (bytes[start] & 0x1f) << 6 | bytes[start + 1] & 0x3f;
break;
case 3:
codePoint = (bytes[start] & 0x0f) << 12 | (bytes[start + 1] & 0x3f) << 6 | bytes[start + 2] & 0x3f;
break;
case 4:
codePoint = (bytes[start] & 0x07) << 18 | (bytes[start + 1] & 0x3f) << 12 | (bytes[start + 2] & 0x3f) << 6 | bytes[start + 3] & 0x3f;
break;
default:
return '\ufffd'; // Replacement character for invalid sequences
}
// Handle code points outside BMP (need surrogate pairs in JavaScript)
if (codePoint > 0xffff) {
// Convert to surrogate pair
codePoint -= 0x10000;
return String.fromCharCode(0xd800 + (codePoint >> 10), 0xdc00 + (codePoint & 0x3ff));
}
return String.fromCharCode(codePoint);
}
/**
* Create a fallback UTF-8 streaming decoder with its own state
*/ function createFallbackDecoder() {
var pendingBytes = [];
return function decode(uint8Array) {
// Combine pending bytes with new input
var inputBytes = uint8ArrayToArray(uint8Array);
var bytes = pendingBytes.length > 0 ? pendingBytes.concat(inputBytes) : inputBytes;
pendingBytes = [];
var result = '';
var i = 0;
while(i < bytes.length){
var byte = bytes[i];
var sequenceLength = getUTF8SequenceLength(byte);
if (sequenceLength === 0) {
// Invalid start byte or continuation byte - emit replacement character
result += '\ufffd';
i++;
continue;
}
if (i + sequenceLength > bytes.length) {
// Incomplete sequence - save for next chunk
pendingBytes = bytes.slice(i);
break;
}
result += decodeCodePoint(bytes, i, sequenceLength);
i += sequenceLength;
}
return result;
};
}
/**
* Create a TextDecoder-based streaming decoder
*/ function createTextDecoderDecoder() {
var decoder = new TextDecoder('utf8');
return function decode(uint8Array) {
return decoder.decode(uint8Array, {
stream: true
});
};
}
/**
* Create a new UTF-8 streaming decoder instance.
* Each decoder maintains its own state for handling multi-byte
* characters that span chunk boundaries.
*/ function createUTF8Decoder() {
if (typeof TextDecoder !== 'undefined') {
return createTextDecoderDecoder();
}
return createFallbackDecoder();
}
function _define_property(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
var REGEX_NEW_LINE = /\r?\n|\r/g;
var root = typeof window === 'undefined' ? global : window;
// biome-ignore lint/suspicious/noShadowRestrictedNames: Legacy
var Symbol = typeof root.Symbol === 'undefined' ? {
asyncIterator: undefined
} : root.Symbol;
/**
* Create a newline iterator recognizing CR, LF, and CRLF using the Symbol.asyncIterator interface
*
* @param source The async iterable of Uint8Array chunks to iterate through
*
* ```typescript
* import newlineIterator from "newline-async-iterator";
*
* const iterator = newlineIterator(readableStream);
* const results = [];
* for await (const line of iterator) results.push(line);
* console.log(results); // ["some", "string", "combination"];
* ```
*/ function newlineIterator(source) {
var decodeUTF8 = createUTF8Decoder();
var lines = [];
var last = '';
var done = false;
var sourceIterator = Symbol.asyncIterator ? source[Symbol.asyncIterator]() : source;
function generateNext() {
return new Promise(function(resolve, reject) {
sourceIterator.next().then(function(next) {
if (next.done) done = true;
else last += decodeUTF8(next.value);
var end = last.length > 0 ? last[last.length - 1] : '';
if (done || end !== '\r' && end !== '\n') {
var moreLines = last.split(REGEX_NEW_LINE);
last = moreLines.pop();
moreLines.forEach(function(line) {
lines.unshift(line);
});
if (done && last.length > 0) {
lines.unshift(last);
last = '';
}
}
if (lines.length > 0) {
var value = lines.pop();
if (done && lines.length === 0 && value.length === 0) return resolve({
value: null,
done: true
});
return resolve({
value: value,
done: false
});
}
if (done) return resolve({
value: null,
done: true
});
generateNext().then(resolve).catch(reject); // get more
});
});
}
var iterator = _define_property({
next: function next() {
return generateNext();
}
}, Symbol.asyncIterator, function() {
return this;
});
return iterator;
}
return newlineIterator;
}));
//# sourceMappingURL=newline-async-iterator.cjs.map