@ant-design/x-sdk
Version:
placeholder for @ant-design/x-sdk
166 lines (150 loc) • 4.97 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
/**
* @description default separator for {@link splitStream}
*/
const DEFAULT_STREAM_SEPARATOR = '\n\n';
/**
* @description Default separator for {@link splitPart}
* @example "event: delta\ndata: {\"key\": \"value\"}"
*/
const DEFAULT_PART_SEPARATOR = '\n';
/**
* @description Default separator for key value, A colon (`:`) is used to separate keys from values
* @example "event: delta"
*/
const DEFAULT_KV_SEPARATOR = ':';
/**
* Check if a string is not empty or only contains whitespace characters
*/
const isValidString = str => (str ?? '').trim() !== '';
/**
* @description A TransformStream inst that splits a stream into parts based on {@link DEFAULT_STREAM_SEPARATOR}
* @example
*
* `event: delta
* data: { content: 'hello' }
*
* event: delta
* data: { key: 'world!' }
*
* `
*/
function splitStream() {
// Buffer to store incomplete data chunks between transformations
let buffer = '';
return new TransformStream({
transform(streamChunk, controller) {
buffer += streamChunk;
// Split the buffer based on the separator
const parts = buffer.split(DEFAULT_STREAM_SEPARATOR);
// Enqueue all complete parts except for the last incomplete one
parts.slice(0, -1).forEach(part => {
// Skip empty parts
if (isValidString(part)) {
controller.enqueue(part);
}
});
// Save the last incomplete part back to the buffer for the next chunk
buffer = parts[parts.length - 1];
},
flush(controller) {
// If there's any remaining data in the buffer, enqueue it as the final part
if (isValidString(buffer)) {
controller.enqueue(buffer);
}
}
});
}
/**
* @link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#fields
*/
/**
* @example
* const sseObject = {
* event: 'delta',
* data: '{ key: "world!" }',
* };
*/
/**
* @description A TransformStream inst that transforms a part string into {@link SSEOutput}
* @example part string
*
* "event: delta\ndata: { key: 'world!' }\n"
*
* @link https://developer.mozilla.org/en-US/docs/Web/API/EventSource
*
* When handling responses with `Content-Type: text/event-stream`, the following standard practices are commonly observed:
* - Double newline characters (`\n\n`) are used to separate individual events.
* - Single newline characters (`\n`) are employed to separate line within an event.
*/
function splitPart() {
return new TransformStream({
transform(partChunk, controller) {
// Split the chunk into key-value pairs using the partSeparator
const lines = partChunk.split(DEFAULT_PART_SEPARATOR);
const sseEvent = lines.reduce((acc, line) => {
const separatorIndex = line.indexOf(DEFAULT_KV_SEPARATOR);
if (separatorIndex === -1) {
throw new Error(`The key-value separator "${DEFAULT_KV_SEPARATOR}" is not found in the sse line chunk!`);
}
// Extract the key from the beginning of the line up to the separator
const key = line.slice(0, separatorIndex);
// The colon is used for comment lines, skip directly
if (!isValidString(key)) return acc;
// Extract the value from the line after the separator
const value = line.slice(separatorIndex + 1);
return {
...acc,
[key]: value
};
}, {});
if (Object.keys(sseEvent).length === 0) return;
// Reduce the key-value pairs into a single object and enqueue
controller.enqueue(sseEvent);
}
});
}
/**
* @description Transform Uint8Array binary stream to {@link SSEOutput} by default
* @warning The `XStream` only support the `utf-8` encoding. More encoding support maybe in the future.
*/
function XStream(options) {
const {
readableStream,
transformStream
} = options;
if (!(readableStream instanceof ReadableStream)) {
throw new Error('The options.readableStream must be an instance of ReadableStream.');
}
// Default encoding is `utf-8`
const decoderStream = new TextDecoderStream();
const stream = transformStream ?
/**
* Uint8Array binary -> string -> Output
*/
readableStream.pipeThrough(decoderStream).pipeThrough(transformStream) :
/**
* Uint8Array binary -> string -> SSE part string -> Default Output {@link SSEOutput}
*/
readableStream.pipeThrough(decoderStream).pipeThrough(splitStream()).pipeThrough(splitPart());
/** support async iterator */
stream[Symbol.asyncIterator] = async function* () {
const reader = this.getReader();
while (true) {
const {
done,
value
} = await reader.read();
if (done) break;
if (!value) continue;
// Transformed data through all transform pipes
yield value;
}
};
return stream;
}
var _default = exports.default = XStream;