// operator/split.ts
/*
* Copyright (c) 2021-2026 Check Digit, LLC
*
* This code is licensed under the MIT license (see LICENSE.txt for details).
*/
import type { Asyncerator } from '../asyncerator.ts';
import type { Operator } from './index.ts';
/**
* Equivalent of the JavaScript string split method. Matches its behavior/corner cases, which is why the
* implementation is more funky than you may expect.
*
* @param separator
* @param limit
*/
export default function string }>(
separator: string,
// isolated declarations require an explicit type for an Infinity default.
// eslint-disable-next-line @typescript-eslint/no-inferrable-types
limit: number = Infinity,
): Operator {
return async function* (iterator: Asyncerator) {
// this behavior dealing with fractional and negative limits is unique, but matches string.split
const actualLimit =
limit <= -1 ? Infinity : limit <= 0 ? 0 : Math.floor(limit);
if (actualLimit === 0) {
return;
}
let previous = '';
let count = 0;
let hasReceivedChunks = false;
for await (const chunk of iterator) {
if (
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
chunk === undefined ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
chunk === null ||
typeof (chunk as { toString: () => string }).toString !== 'function'
) {
throw new Error(`${JSON.stringify(chunk)} not convertible to a string`);
}
hasReceivedChunks = true;
previous += chunk.toString();
let index;
while (
previous.length > 0 &&
(index = separator === '' ? 1 : previous.indexOf(separator)) >= 0
) {
const line = previous.slice(0, index);
yield line;
if (++count >= actualLimit) {
return;
}
previous = previous.slice(index + separator.length);
}
}
if (
(separator !== '' && hasReceivedChunks) ||
(previous.length > 0 && count < actualLimit)
) {
yield previous;
}
};
}