rouzer 5.2.1 → 5.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ndjson.d.ts CHANGED
@@ -57,5 +57,5 @@ export declare function decodeNdjson<T = unknown>(stream: ReadableStream<Uint8Ar
57
57
  * `content-type: application/x-ndjson; charset=utf-8` unless the caller supplies
58
58
  * a content type in `init.headers`.
59
59
  */
60
- export declare function ndjsonResponse<T>(source: NdjsonSource<T>, init?: ResponseInit & NdjsonEncodeOptions): Response;
60
+ export declare function ndjsonResponse<T>(source: NdjsonSource<T>, { signal, ...init }?: ResponseInit & NdjsonEncodeOptions): Response;
61
61
  export {};
package/dist/ndjson.js CHANGED
@@ -52,107 +52,184 @@ export const routerPlugin = {
52
52
  * closed.
53
53
  */
54
54
  export function encodeNdjson(source, options = {}) {
55
- const iterator = getAsyncIterator(source);
56
- const encoder = new TextEncoder();
57
- const { signal } = options;
58
- let cancelled = false;
59
- let cleanup;
60
- let abortHandler;
61
- function removeAbortHandler() {
62
- if (signal && abortHandler) {
63
- signal.removeEventListener('abort', abortHandler);
64
- abortHandler = undefined;
65
- }
66
- }
67
- function cancelIterator(reason) {
68
- cancelled = true;
69
- removeAbortHandler();
70
- cleanup ??= Promise.resolve(iterator.return?.(reason)).then(() => { });
71
- return cleanup;
55
+ return new ReadableStream(new NdjsonEncoder(source, options));
56
+ }
57
+ /**
58
+ * Decode a newline-delimited JSON byte stream.
59
+ *
60
+ * @remarks UTF-8 chunks may split JSON lines. Both `\n` and `\r\n` line endings
61
+ * are accepted, and a final line does not need a trailing newline. Malformed
62
+ * lines throw a `SyntaxError` that includes the 1-based line number.
63
+ */
64
+ export function decodeNdjson(stream) {
65
+ return new NdjsonDecoder(stream);
66
+ }
67
+ class NdjsonEncoder {
68
+ options;
69
+ iterator;
70
+ encoder = new TextEncoder();
71
+ cancelled = false;
72
+ cleanup;
73
+ abortHandler;
74
+ constructor(source, options) {
75
+ this.options = options;
76
+ this.iterator = getAsyncIterator(source);
72
77
  }
73
- return new ReadableStream({
74
- start(controller) {
75
- if (!signal) {
76
- return;
77
- }
78
- abortHandler = () => {
79
- void cancelIterator(signal.reason).catch(() => { });
78
+ start(controller) {
79
+ const { signal } = this.options;
80
+ if (signal) {
81
+ this.abortHandler = () => {
82
+ void this.cancel(signal.reason).catch(() => { });
80
83
  try {
81
84
  controller.close();
82
85
  }
83
86
  catch { }
84
87
  };
85
88
  if (signal.aborted) {
86
- abortHandler();
87
- return;
89
+ this.abortHandler();
88
90
  }
89
- signal.addEventListener('abort', abortHandler, { once: true });
90
- },
91
- async pull(controller) {
92
- if (cancelled) {
93
- controller.close();
94
- return;
91
+ else {
92
+ signal.addEventListener('abort', this.abortHandler, { once: true });
95
93
  }
96
- const { done, value } = await iterator.next();
97
- if (cancelled) {
98
- return;
94
+ }
95
+ }
96
+ async pull(controller) {
97
+ if (this.cancelled) {
98
+ controller.close();
99
+ return;
100
+ }
101
+ const { done, value } = await this.iterator.next();
102
+ if (this.cancelled) {
103
+ return;
104
+ }
105
+ if (done) {
106
+ this.removeAbortHandler();
107
+ controller.close();
108
+ return;
109
+ }
110
+ const line = JSON.stringify(value);
111
+ if (line === undefined) {
112
+ throw new TypeError('NDJSON items must serialize to a JSON text; received undefined');
113
+ }
114
+ controller.enqueue(this.encoder.encode(`${line}\n`));
115
+ }
116
+ async cancel(reason) {
117
+ if (!this.cancelled) {
118
+ this.cancelled = true;
119
+ this.removeAbortHandler();
120
+ this.cleanup ??= Promise.resolve(this.iterator.return?.(reason)).then(() => { });
121
+ }
122
+ await this.cleanup;
123
+ }
124
+ removeAbortHandler() {
125
+ const { signal } = this.options;
126
+ if (signal && this.abortHandler) {
127
+ signal.removeEventListener('abort', this.abortHandler);
128
+ this.abortHandler = undefined;
129
+ }
130
+ }
131
+ }
132
+ class NdjsonDecoder {
133
+ reader;
134
+ decoder = new TextDecoder();
135
+ buffer = '';
136
+ lineNumber = 0;
137
+ closed = false;
138
+ doneReading = false;
139
+ readerReleased = false;
140
+ constructor(stream) {
141
+ this.reader = stream.getReader();
142
+ }
143
+ [Symbol.asyncIterator]() {
144
+ return new NdjsonAsyncIterator(this);
145
+ }
146
+ releaseReader() {
147
+ if (!this.readerReleased) {
148
+ this.readerReleased = true;
149
+ this.reader.releaseLock();
150
+ }
151
+ }
152
+ async cancelReader(reason) {
153
+ if (!this.doneReading) {
154
+ await this.reader.cancel(reason).catch(() => { });
155
+ }
156
+ this.releaseReader();
157
+ }
158
+ async parseNextLine(line) {
159
+ try {
160
+ this.lineNumber += 1;
161
+ return {
162
+ done: false,
163
+ value: parseNdjsonLine(stripCarriageReturn(line), this.lineNumber),
164
+ };
165
+ }
166
+ catch (error) {
167
+ this.closed = true;
168
+ await this.cancelReader(error);
169
+ throw error;
170
+ }
171
+ }
172
+ }
173
+ class NdjsonAsyncIterator {
174
+ decoder;
175
+ closed = false;
176
+ constructor(decoder) {
177
+ this.decoder = decoder;
178
+ }
179
+ async next() {
180
+ if (this.closed || this.decoder.closed) {
181
+ return { done: true, value: undefined };
182
+ }
183
+ while (true) {
184
+ const newlineIndex = this.decoder.buffer.indexOf('\n');
185
+ if (newlineIndex !== -1) {
186
+ const line = this.decoder.buffer.slice(0, newlineIndex);
187
+ this.decoder.buffer = this.decoder.buffer.slice(newlineIndex + 1);
188
+ return this.decoder.parseNextLine(line);
99
189
  }
100
- if (done) {
101
- removeAbortHandler();
102
- controller.close();
103
- return;
190
+ if (this.decoder.doneReading) {
191
+ this.close();
192
+ this.decoder.releaseReader();
193
+ if (this.decoder.buffer.length > 0) {
194
+ const line = this.decoder.buffer;
195
+ this.decoder.buffer = '';
196
+ return this.decoder.parseNextLine(line);
197
+ }
198
+ return { done: true, value: undefined };
104
199
  }
105
- const line = JSON.stringify(value);
106
- if (line === undefined) {
107
- throw new TypeError('NDJSON items must serialize to a JSON text; received undefined');
200
+ let chunk;
201
+ try {
202
+ chunk = await this.decoder.reader.read();
108
203
  }
109
- controller.enqueue(encoder.encode(`${line}\n`));
110
- },
111
- async cancel(reason) {
112
- await cancelIterator(reason);
113
- },
114
- });
115
- }
116
- /**
117
- * Decode a newline-delimited JSON byte stream.
118
- *
119
- * @remarks UTF-8 chunks may split JSON lines. Both `\n` and `\r\n` line endings
120
- * are accepted, and a final line does not need a trailing newline. Malformed
121
- * lines throw a `SyntaxError` that includes the 1-based line number.
122
- */
123
- export async function* decodeNdjson(stream) {
124
- const reader = stream.getReader();
125
- const decoder = new TextDecoder();
126
- let buffer = '';
127
- let lineNumber = 0;
128
- let doneReading = false;
129
- try {
130
- while (true) {
131
- const { done, value } = await reader.read();
132
- if (done) {
133
- buffer += decoder.decode();
134
- doneReading = true;
135
- break;
204
+ catch (error) {
205
+ this.close();
206
+ this.decoder.releaseReader();
207
+ throw error;
208
+ }
209
+ if (this.closed || this.decoder.closed) {
210
+ return { done: true, value: undefined };
136
211
  }
137
- buffer += decoder.decode(value, { stream: true });
138
- let newlineIndex;
139
- while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
140
- const line = stripCarriageReturn(buffer.slice(0, newlineIndex));
141
- buffer = buffer.slice(newlineIndex + 1);
142
- lineNumber += 1;
143
- yield parseNdjsonLine(line, lineNumber);
212
+ if (chunk.done) {
213
+ this.decoder.buffer += this.decoder.decoder.decode();
214
+ this.decoder.doneReading = true;
215
+ }
216
+ else {
217
+ this.decoder.buffer += this.decoder.decoder.decode(chunk.value, {
218
+ stream: true,
219
+ });
144
220
  }
145
- }
146
- if (buffer.length > 0) {
147
- lineNumber += 1;
148
- yield parseNdjsonLine(stripCarriageReturn(buffer), lineNumber);
149
221
  }
150
222
  }
151
- finally {
152
- if (!doneReading) {
153
- await reader.cancel().catch(() => { });
223
+ async return(reason) {
224
+ if (!this.closed) {
225
+ this.close();
226
+ await this.decoder.cancelReader(reason);
154
227
  }
155
- reader.releaseLock();
228
+ return { done: true, value: undefined };
229
+ }
230
+ close() {
231
+ this.closed = true;
232
+ this.decoder.closed = true;
156
233
  }
157
234
  }
158
235
  /**
@@ -162,32 +239,28 @@ export async function* decodeNdjson(stream) {
162
239
  * `content-type: application/x-ndjson; charset=utf-8` unless the caller supplies
163
240
  * a content type in `init.headers`.
164
241
  */
165
- export function ndjsonResponse(source, init = {}) {
166
- const { signal, ...responseInit } = init;
242
+ export function ndjsonResponse(source, { signal, ...init } = {}) {
167
243
  const headers = new Headers(init.headers);
168
244
  if (!headers.has('content-type')) {
169
245
  headers.set('content-type', 'application/x-ndjson; charset=utf-8');
170
246
  }
171
247
  return new Response(encodeNdjson(source, { signal }), {
172
- ...responseInit,
248
+ ...init,
173
249
  headers,
174
250
  });
175
251
  }
176
252
  function getAsyncIterator(source) {
177
- const asyncIterator = source[Symbol.asyncIterator]?.();
178
- if (asyncIterator) {
179
- return asyncIterator;
253
+ if (Symbol.asyncIterator in source) {
254
+ return source[Symbol.asyncIterator]();
180
255
  }
181
- const iterator = source[Symbol.iterator]?.();
182
- if (iterator) {
256
+ if (Symbol.iterator in source) {
257
+ const iterator = source[Symbol.iterator]();
183
258
  return {
184
- next() {
185
- return Promise.resolve(iterator.next());
186
- },
187
- async return() {
188
- iterator.return?.();
189
- return { done: true, value: undefined };
190
- },
259
+ next: async (value) => iterator.next(value),
260
+ return: iterator.return
261
+ ? async (value) => iterator.return(value)
262
+ : undefined,
263
+ throw: iterator.throw ? async (error) => iterator.throw(error) : undefined,
191
264
  };
192
265
  }
193
266
  throw new TypeError('NDJSON source must be iterable');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rouzer",
3
- "version": "5.2.1",
3
+ "version": "5.2.2",
4
4
  "packageManager": "pnpm@11.5.1",
5
5
  "type": "module",
6
6
  "exports": {