better-sse 0.14.0 → 0.15.0

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/build/index.mjs CHANGED
@@ -1,23 +1,51 @@
1
1
  // src/Session.ts
2
2
  import {
3
+ IncomingMessage as Http1ServerRequest,
3
4
  ServerResponse as Http1ServerResponse
4
- } from "http";
5
+ } from "node:http";
6
+ import { Http2ServerRequest, Http2ServerResponse } from "node:http2";
7
+ import { setImmediate } from "node:timers";
5
8
 
6
- // src/lib/serialize.ts
7
- var serialize = (data) => JSON.stringify(data);
9
+ // src/lib/createPushFromIterable.ts
10
+ var createPushFromIterable = (push) => async (iterable, options = {}) => {
11
+ const { eventName = "iteration" } = options;
12
+ for await (const data of iterable) {
13
+ push(data, eventName);
14
+ }
15
+ };
8
16
 
9
- // src/lib/sanitize.ts
10
- var newlineVariantsRegex = /(\r\n|\r|\n)/g;
11
- var newlineTrailingRegex = /\n+$/g;
12
- var sanitize = (text) => {
13
- let sanitized = text;
14
- sanitized = sanitized.replace(newlineVariantsRegex, "\n");
15
- sanitized = sanitized.replace(newlineTrailingRegex, "");
16
- return sanitized;
17
+ // src/lib/createPushFromStream.ts
18
+ import { Readable as NodeReadableStream } from "node:stream";
19
+ var createPushFromStream = (push) => async (stream, options = {}) => {
20
+ const { eventName = "stream" } = options;
21
+ if (stream instanceof NodeReadableStream) {
22
+ return await new Promise((resolve, reject) => {
23
+ stream.on("data", (chunk) => {
24
+ let data;
25
+ if (Buffer.isBuffer(chunk)) {
26
+ data = chunk.toString();
27
+ } else {
28
+ data = chunk;
29
+ }
30
+ push(data, eventName);
31
+ });
32
+ stream.once("end", () => resolve(true));
33
+ stream.once("close", () => resolve(true));
34
+ stream.once("error", (err) => reject(err));
35
+ });
36
+ }
37
+ for await (const chunk of stream) {
38
+ if (Buffer.isBuffer(chunk)) {
39
+ push(chunk.toString(), eventName);
40
+ } else {
41
+ push(chunk, eventName);
42
+ }
43
+ }
44
+ return true;
17
45
  };
18
46
 
19
47
  // src/lib/generateId.ts
20
- import { randomUUID, randomBytes } from "crypto";
48
+ import { randomBytes, randomUUID } from "node:crypto";
21
49
  var generateId;
22
50
  if (randomUUID) {
23
51
  generateId = () => randomUUID();
@@ -25,32 +53,18 @@ if (randomUUID) {
25
53
  generateId = () => randomBytes(4).toString("hex");
26
54
  }
27
55
 
28
- // src/lib/createPushFromStream.ts
29
- var createPushFromStream = (push) => async (stream, options = {}) => {
30
- const { eventName = "stream" } = options;
31
- return await new Promise((resolve, reject) => {
32
- stream.on("data", (chunk) => {
33
- let data;
34
- if (Buffer.isBuffer(chunk)) {
35
- data = chunk.toString();
36
- } else {
37
- data = chunk;
38
- }
39
- push(data, eventName);
40
- });
41
- stream.once("end", () => resolve(true));
42
- stream.once("close", () => resolve(true));
43
- stream.once("error", (err) => reject(err));
44
- });
56
+ // src/lib/sanitize.ts
57
+ var newlineVariantsRegex = /(\r\n|\r|\n)/g;
58
+ var newlineTrailingRegex = /\n+$/g;
59
+ var sanitize = (text) => {
60
+ let sanitized = text;
61
+ sanitized = sanitized.replace(newlineVariantsRegex, "\n");
62
+ sanitized = sanitized.replace(newlineTrailingRegex, "");
63
+ return sanitized;
45
64
  };
46
65
 
47
- // src/lib/createPushFromIterable.ts
48
- var createPushFromIterable = (push) => async (iterable, options = {}) => {
49
- const { eventName = "iteration" } = options;
50
- for await (const data of iterable) {
51
- push(data, eventName);
52
- }
53
- };
66
+ // src/lib/serialize.ts
67
+ var serialize = (data) => JSON.stringify(data);
54
68
 
55
69
  // src/EventBuffer.ts
56
70
  var EventBuffer = class {
@@ -184,8 +198,180 @@ var EventBuffer = class {
184
198
  read = () => this.buffer;
185
199
  };
186
200
 
201
+ // src/lib/applyHeaders.ts
202
+ var applyHeaders = (from, to) => {
203
+ const fromMap = from instanceof Headers ? Object.fromEntries(from) : from;
204
+ for (const [key, value] of Object.entries(fromMap)) {
205
+ if (Array.isArray(value)) {
206
+ to.delete(key);
207
+ for (const item of value) {
208
+ to.append(key, item);
209
+ }
210
+ } else if (value === void 0) {
211
+ to.delete(key);
212
+ } else {
213
+ to.set(key, value);
214
+ }
215
+ }
216
+ };
217
+
218
+ // src/lib/constants.ts
219
+ var DEFAULT_REQUEST_HOST = "localhost";
220
+ var DEFAULT_REQUEST_METHOD = "GET";
221
+ var DEFAULT_RESPONSE_CODE = 200;
222
+ var DEFAULT_RESPONSE_HEADERS = {
223
+ "Content-Type": "text/event-stream",
224
+ "Cache-Control": "private, no-cache, no-store, no-transform, must-revalidate, max-age=0",
225
+ Connection: "keep-alive",
226
+ Pragma: "no-cache",
227
+ "X-Accel-Buffering": "no"
228
+ };
229
+
230
+ // src/adapters/FetchConnection.ts
231
+ var FetchConnection = class _FetchConnection {
232
+ static encoder = new TextEncoder();
233
+ writer;
234
+ url;
235
+ request;
236
+ response;
237
+ constructor(request, response, options = {}) {
238
+ this.url = new URL(request.url);
239
+ this.request = request;
240
+ const { readable, writable } = new TransformStream();
241
+ this.writer = writable.getWriter();
242
+ this.response = new Response(readable, {
243
+ status: options.statusCode ?? response?.status ?? DEFAULT_RESPONSE_CODE,
244
+ headers: DEFAULT_RESPONSE_HEADERS
245
+ });
246
+ if (response) {
247
+ applyHeaders(response.headers, this.response.headers);
248
+ }
249
+ }
250
+ sendHead = () => {
251
+ };
252
+ sendChunk = (chunk) => {
253
+ const encoded = _FetchConnection.encoder.encode(chunk);
254
+ this.writer.write(encoded);
255
+ };
256
+ cleanup = () => {
257
+ this.writer.close();
258
+ };
259
+ };
260
+
261
+ // src/adapters/NodeHttp1Connection.ts
262
+ var NodeHttp1Connection = class {
263
+ constructor(req, res, options = {}) {
264
+ this.req = req;
265
+ this.res = res;
266
+ this.url = new URL(
267
+ `http://${req.headers.host ?? DEFAULT_REQUEST_HOST}${req.url}`
268
+ );
269
+ this.controller = new AbortController();
270
+ req.once("close", this.onClose);
271
+ res.once("close", this.onClose);
272
+ this.request = new Request(this.url, {
273
+ method: req.method ?? DEFAULT_REQUEST_METHOD,
274
+ signal: this.controller.signal
275
+ });
276
+ applyHeaders(req.headers, this.request.headers);
277
+ this.response = new Response(null, {
278
+ status: options.statusCode ?? res.statusCode ?? DEFAULT_RESPONSE_CODE,
279
+ headers: DEFAULT_RESPONSE_HEADERS
280
+ });
281
+ if (res) {
282
+ applyHeaders(
283
+ res.getHeaders(),
284
+ this.response.headers
285
+ );
286
+ }
287
+ }
288
+ controller;
289
+ url;
290
+ request;
291
+ response;
292
+ onClose = () => {
293
+ this.controller.abort();
294
+ };
295
+ sendHead = () => {
296
+ this.res.writeHead(
297
+ this.response.status,
298
+ Object.fromEntries(this.response.headers)
299
+ );
300
+ };
301
+ sendChunk = (chunk) => {
302
+ this.res.write(chunk);
303
+ };
304
+ cleanup = () => {
305
+ this.req.removeListener("close", this.onClose);
306
+ this.res.removeListener("close", this.onClose);
307
+ };
308
+ };
309
+
310
+ // src/adapters/NodeHttp2CompatConnection.ts
311
+ var NodeHttp2CompatConnection = class {
312
+ constructor(req, res, options = {}) {
313
+ this.req = req;
314
+ this.res = res;
315
+ this.url = new URL(
316
+ `http://${req.headers.host ?? DEFAULT_REQUEST_HOST}${req.url}`
317
+ );
318
+ this.controller = new AbortController();
319
+ req.once("close", this.onClose);
320
+ res.once("close", this.onClose);
321
+ this.request = new Request(this.url, {
322
+ method: req.method ?? DEFAULT_REQUEST_METHOD,
323
+ signal: this.controller.signal
324
+ });
325
+ const allowedHeaders = { ...req.headers };
326
+ for (const header of Object.keys(allowedHeaders)) {
327
+ if (header.startsWith(":")) {
328
+ delete allowedHeaders[header];
329
+ }
330
+ }
331
+ applyHeaders(allowedHeaders, this.request.headers);
332
+ this.response = new Response(null, {
333
+ status: options.statusCode ?? res.statusCode ?? DEFAULT_RESPONSE_CODE,
334
+ headers: DEFAULT_RESPONSE_HEADERS
335
+ });
336
+ if (res) {
337
+ applyHeaders(
338
+ res.getHeaders(),
339
+ this.response.headers
340
+ );
341
+ }
342
+ }
343
+ controller;
344
+ url;
345
+ request;
346
+ response;
347
+ onClose = () => {
348
+ this.controller.abort();
349
+ };
350
+ sendHead = () => {
351
+ this.res.writeHead(
352
+ this.response.status,
353
+ Object.fromEntries(this.response.headers)
354
+ );
355
+ };
356
+ sendChunk = (chunk) => {
357
+ this.res.write(chunk);
358
+ };
359
+ cleanup = () => {
360
+ this.req.removeListener("close", this.onClose);
361
+ this.res.removeListener("close", this.onClose);
362
+ };
363
+ };
364
+
365
+ // src/lib/SseError.ts
366
+ var SseError = class extends Error {
367
+ constructor(message) {
368
+ super(message);
369
+ this.message = `better-sse: ${message}`;
370
+ }
371
+ };
372
+
187
373
  // src/lib/TypedEmitter.ts
188
- import { EventEmitter } from "events";
374
+ import { EventEmitter } from "node:events";
189
375
  var TypedEmitter = class extends EventEmitter {
190
376
  addListener(event, listener) {
191
377
  return super.addListener(event, listener);
@@ -213,14 +399,6 @@ var TypedEmitter = class extends EventEmitter {
213
399
  }
214
400
  };
215
401
 
216
- // src/lib/SseError.ts
217
- var SseError = class extends Error {
218
- constructor(message) {
219
- super(message);
220
- this.message = `better-sse: ${message}`;
221
- }
222
- };
223
-
224
402
  // src/Session.ts
225
403
  var Session = class extends TypedEmitter {
226
404
  /**
@@ -249,70 +427,78 @@ var Session = class extends TypedEmitter {
249
427
  */
250
428
  state;
251
429
  buffer;
252
- /**
253
- * Raw HTTP request.
254
- */
255
- req;
256
- /**
257
- * Raw HTTP response that is the minimal interface needed and forms the
258
- * intersection between the HTTP/1.1 and HTTP/2 server response interfaces.
259
- */
260
- res;
261
- serialize;
430
+ connection;
262
431
  sanitize;
263
- trustClientEventId;
432
+ serialize;
264
433
  initialRetry;
265
434
  keepAliveInterval;
266
435
  keepAliveTimer;
267
- statusCode;
268
- headers;
269
- constructor(req, res, options = {}) {
436
+ constructor(req, res, options) {
270
437
  super();
271
- this.req = req;
272
- this.res = res;
273
- const serializer = options.serializer ?? serialize;
274
- const sanitizer = options.sanitizer ?? sanitize;
275
- this.serialize = serializer;
276
- this.sanitize = sanitizer;
277
- this.buffer = new EventBuffer({ serializer, sanitizer });
278
- this.trustClientEventId = options.trustClientEventId ?? true;
279
- this.initialRetry = options.retry === null ? null : options.retry ?? 2e3;
280
- this.keepAliveInterval = options.keepAlive === null ? null : options.keepAlive ?? 1e4;
281
- this.statusCode = options.statusCode ?? 200;
282
- this.headers = options.headers ?? {};
283
- this.state = options.state ?? {};
284
- this.req.once("close", this.onDisconnected);
285
- this.res.once("close", this.onDisconnected);
286
- setImmediate(this.initialize);
287
- }
288
- initialize = () => {
289
- const url = `http://${this.req.headers.host}${this.req.url}`;
290
- const params = new URL(url).searchParams;
291
- if (this.trustClientEventId) {
292
- const givenLastEventId = this.req.headers["last-event-id"] ?? params.get("lastEventId") ?? params.get("evs_last_event_id") ?? "";
293
- this.lastId = givenLastEventId;
294
- }
295
- const headers = {};
296
- if (this.res instanceof Http1ServerResponse) {
297
- headers["Content-Type"] = "text/event-stream";
298
- headers["Cache-Control"] = "private, no-cache, no-store, no-transform, must-revalidate, max-age=0";
299
- headers["Connection"] = "keep-alive";
300
- headers["Pragma"] = "no-cache";
301
- headers["X-Accel-Buffering"] = "no";
438
+ let givenOptions = options ?? {};
439
+ if (req instanceof Request) {
440
+ let givenRes = null;
441
+ if (res) {
442
+ if (res instanceof Response) {
443
+ givenRes = res;
444
+ } else {
445
+ if (options) {
446
+ throw new SseError(
447
+ "When providing a Fetch Request object but no Response object, you may pass options as the second OR third argument to the session constructor, but not to both."
448
+ );
449
+ }
450
+ givenOptions = res;
451
+ }
452
+ }
453
+ this.connection = new FetchConnection(req, givenRes, givenOptions);
454
+ } else if (req instanceof Http1ServerRequest) {
455
+ if (res instanceof Http1ServerResponse) {
456
+ this.connection = new NodeHttp1Connection(req, res, givenOptions);
457
+ } else {
458
+ throw new SseError(
459
+ "When providing a Node IncomingMessage object, a corresponding ServerResponse object must also be provided."
460
+ );
461
+ }
462
+ } else if (req instanceof Http2ServerRequest) {
463
+ if (res instanceof Http2ServerResponse) {
464
+ this.connection = new NodeHttp2CompatConnection(req, res, givenOptions);
465
+ } else {
466
+ throw new SseError(
467
+ "When providing a Node HTTP2ServerRequest object, a corresponding HTTP2ServerResponse object must also be provided."
468
+ );
469
+ }
302
470
  } else {
303
- headers["content-type"] = "text/event-stream";
304
- headers["cache-control"] = "private, no-cache, no-store, no-transform, must-revalidate, max-age=0";
305
- headers["pragma"] = "no-cache";
306
- headers["x-accel-buffering"] = "no";
471
+ throw new SseError(
472
+ "Malformed request or response objects given to session constructor. Must be one of IncomingMessage/ServerResponse from the Node HTTP/1 API, HTTP2ServerRequest/HTTP2ServerResponse from the Node HTTP/2 Compatibility API, or Request/Response from the Fetch API."
473
+ );
474
+ }
475
+ if (givenOptions.headers) {
476
+ applyHeaders(givenOptions.headers, this.connection.response.headers);
307
477
  }
308
- for (const [name, value] of Object.entries(this.headers)) {
309
- headers[name] = value ?? "";
478
+ if (givenOptions.trustClientEventId !== false) {
479
+ this.lastId = this.connection.request.headers.get("last-event-id") ?? this.connection.url.searchParams.get("lastEventId") ?? this.connection.url.searchParams.get("evs_last_event_id") ?? "";
310
480
  }
311
- this.res.writeHead(this.statusCode, headers);
312
- if (params.has("padding")) {
481
+ this.state = givenOptions.state ?? {};
482
+ this.initialRetry = givenOptions.retry === null ? null : givenOptions.retry ?? 2e3;
483
+ this.keepAliveInterval = givenOptions.keepAlive === null ? null : givenOptions.keepAlive ?? 1e4;
484
+ this.serialize = givenOptions.serializer ?? serialize;
485
+ this.sanitize = givenOptions.sanitizer ?? sanitize;
486
+ this.buffer = new EventBuffer({
487
+ serializer: this.serialize,
488
+ sanitizer: this.sanitize
489
+ });
490
+ this.connection.request.signal.addEventListener(
491
+ "abort",
492
+ this.onDisconnected
493
+ );
494
+ setImmediate(this.initialize);
495
+ }
496
+ initialize = () => {
497
+ this.connection.sendHead();
498
+ if (this.connection.url.searchParams.has("padding")) {
313
499
  this.buffer.comment(" ".repeat(2049)).dispatch();
314
500
  }
315
- if (params.has("evs_preamble")) {
501
+ if (this.connection.url.searchParams.has("evs_preamble")) {
316
502
  this.buffer.comment(" ".repeat(2056)).dispatch();
317
503
  }
318
504
  if (this.initialRetry !== null) {
@@ -320,80 +506,57 @@ var Session = class extends TypedEmitter {
320
506
  }
321
507
  this.flush();
322
508
  if (this.keepAliveInterval !== null) {
323
- this.keepAliveTimer = setInterval(
324
- this.keepAlive,
325
- this.keepAliveInterval
326
- );
509
+ this.keepAliveTimer = setInterval(this.keepAlive, this.keepAliveInterval);
327
510
  }
328
511
  this.isConnected = true;
329
512
  this.emit("connected");
330
513
  };
331
514
  onDisconnected = () => {
332
- this.req.removeListener("close", this.onDisconnected);
333
- this.res.removeListener("close", this.onDisconnected);
515
+ this.connection.request.signal.removeEventListener(
516
+ "abort",
517
+ this.onDisconnected
518
+ );
519
+ this.connection.cleanup();
334
520
  if (this.keepAliveTimer) {
335
521
  clearInterval(this.keepAliveTimer);
336
522
  }
337
523
  this.isConnected = false;
338
524
  this.emit("disconnected");
339
525
  };
526
+ /**
527
+ * Write an empty comment and flush it to the client.
528
+ */
340
529
  keepAlive = () => {
341
530
  this.buffer.comment().dispatch();
342
531
  this.flush();
343
532
  };
344
533
  /**
345
- * @deprecated see https://github.com/MatthewWid/better-sse/issues/52
346
- */
347
- event(type) {
348
- this.buffer.event(type);
349
- return this;
350
- }
351
- /**
352
- * @deprecated see https://github.com/MatthewWid/better-sse/issues/52
353
- */
354
- data = (data) => {
355
- this.buffer.data(data);
356
- return this;
357
- };
358
- /**
359
- * @deprecated see https://github.com/MatthewWid/better-sse/issues/52
360
- */
361
- id = (id = "") => {
362
- this.buffer.id(id);
363
- this.lastId = id;
364
- return this;
365
- };
366
- /**
367
- * @deprecated see https://github.com/MatthewWid/better-sse/issues/52
368
- */
369
- retry = (time) => {
370
- this.buffer.retry(time);
371
- return this;
372
- };
373
- /**
374
- * @deprecated see https://github.com/MatthewWid/better-sse/issues/52
534
+ * Flush the contents of the internal buffer to the client and clear the buffer.
375
535
  */
376
- comment = (text) => {
377
- this.buffer.comment(text);
378
- return this;
536
+ flush = () => {
537
+ const contents = this.buffer.read();
538
+ this.buffer.clear();
539
+ this.connection.sendChunk(contents);
379
540
  };
380
541
  /**
381
- * @deprecated see https://github.com/MatthewWid/better-sse/issues/52
542
+ * Get a Request object representing the request of the underlying connection this session manages.
543
+ *
544
+ * When using the Fetch API, this will be the original Request object passed to the session constructor.
545
+ *
546
+ * When using the Node HTTP APIs, this will be a new Request object with status code and headers copied from the original request.
547
+ * When the originally given request or response is closed, the abort signal attached to this Request will be triggered.
382
548
  */
383
- dispatch = () => {
384
- this.buffer.dispatch();
385
- return this;
386
- };
549
+ getRequest = () => this.connection.request;
387
550
  /**
388
- * Flush the contents of the internal buffer to the client and clear the buffer.
551
+ * Get a Response object representing the response of the underlying connection this session manages.
389
552
  *
390
- * @deprecated see https://github.com/MatthewWid/better-sse/issues/52
553
+ * When using the Fetch API, this will be a new Response object with status code and headers copied from the original response if given.
554
+ * Its body will be a ReadableStream that should begin being consumed for the session to consider itself connected.
555
+ *
556
+ * When using the Node HTTP APIs, this will be a new Response object with status code and headers copied from the original response.
557
+ * Its body will be `null`, as data is instead written to the stream of the originally given response object.
391
558
  */
392
- flush = () => {
393
- this.res.write(this.buffer.read());
394
- this.buffer.clear();
395
- return this;
396
- };
559
+ getResponse = () => this.connection.response;
397
560
  /**
398
561
  * Push an event to the client.
399
562
  *
@@ -401,6 +564,8 @@ var Session = class extends TypedEmitter {
401
564
  *
402
565
  * If no event ID is given, the event ID (and thus the `lastId` property) is set to a unique string generated using a cryptographic pseudorandom number generator.
403
566
  *
567
+ * If the session has disconnected, an `SseError` will be thrown.
568
+ *
404
569
  * Emits the `push` event with the given data, event name and event ID in that order.
405
570
  *
406
571
  * @param data - Data to write.
@@ -409,7 +574,9 @@ var Session = class extends TypedEmitter {
409
574
  */
410
575
  push = (data, eventName = "message", eventId = generateId()) => {
411
576
  if (!this.isConnected) {
412
- throw new SseError("Cannot push data to a non-active session.");
577
+ throw new SseError(
578
+ "Cannot push data to a non-active session. Ensure the session is connected before attempting to push events. If using the Fetch API, the response stream must begin being consumed before the session is considered connected."
579
+ );
413
580
  }
414
581
  this.buffer.push(data, eventName, eventId);
415
582
  this.flush();
@@ -458,25 +625,54 @@ var Session = class extends TypedEmitter {
458
625
  */
459
626
  batch = async (batcher) => {
460
627
  if (batcher instanceof EventBuffer) {
461
- this.res.write(batcher.read());
628
+ this.connection.sendChunk(batcher.read());
462
629
  } else {
463
630
  const buffer = new EventBuffer({
464
631
  serializer: this.serialize,
465
632
  sanitizer: this.sanitize
466
633
  });
467
634
  await batcher(buffer);
468
- this.res.write(buffer.read());
635
+ this.connection.sendChunk(buffer.read());
469
636
  }
470
637
  };
471
638
  };
472
639
 
473
640
  // src/createSession.ts
474
- var createSession = (...args) => new Promise((resolve) => {
641
+ function createSession(req, res, options) {
642
+ return new Promise((resolve) => {
643
+ const session = new Session(req, res, options);
644
+ if (req instanceof Request) {
645
+ resolve(session);
646
+ } else {
647
+ session.once("connected", () => {
648
+ resolve(session);
649
+ });
650
+ }
651
+ });
652
+ }
653
+
654
+ // src/createResponse.ts
655
+ function createResponse(request, response, options, callback) {
656
+ const args = [request, response, options, callback];
657
+ let givenCallback;
658
+ for (let index = args.length - 1; index >= 0; --index) {
659
+ const arg = args.pop();
660
+ if (arg) {
661
+ givenCallback = arg;
662
+ break;
663
+ }
664
+ }
665
+ if (typeof givenCallback !== "function") {
666
+ throw new SseError(
667
+ "Last argument given to createResponse must be a callback function."
668
+ );
669
+ }
475
670
  const session = new Session(...args);
476
671
  session.once("connected", () => {
477
- resolve(session);
672
+ givenCallback(session);
478
673
  });
479
- });
674
+ return session.getResponse();
675
+ }
480
676
 
481
677
  // src/Channel.ts
482
678
  var Channel = class extends TypedEmitter {
@@ -575,5 +771,6 @@ export {
575
771
  SseError,
576
772
  createChannel,
577
773
  createEventBuffer,
774
+ createResponse,
578
775
  createSession
579
776
  };
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "better-sse",
3
- "description": "Dead simple, dependency-less, spec-compliant server-sent events implementation for Node, written in TypeScript.",
4
- "version": "0.14.0",
3
+ "description": "Dead simple, dependency-less, spec-compliant server-sent events implementation written in TypeScript.",
4
+ "version": "0.15.0",
5
5
  "license": "MIT",
6
6
  "author": "Matthew W. <matthew.widdi@gmail.com>",
7
7
  "repository": "github:MatthewWid/better-sse",
8
- "homepage": "https://github.com/MatthewWid/better-sse",
8
+ "homepage": "https://matthewwid.github.io/better-sse",
9
9
  "bugs": "https://github.com/MatthewWid/better-sse/issues",
10
10
  "keywords": [
11
11
  "server-sent-events",
@@ -34,18 +34,13 @@
34
34
  "pnpm": ">=9"
35
35
  },
36
36
  "devDependencies": {
37
+ "@biomejs/biome": "1.9.4",
37
38
  "@tsconfig/node20": "^20.1.4",
38
39
  "@types/eventsource": "^1.1.15",
39
40
  "@types/express": "^5.0.0",
40
41
  "@types/node": "^22.7.6",
41
- "@typescript-eslint/eslint-plugin": "^8.9.0",
42
- "@typescript-eslint/parser": "^8.9.0",
43
- "eslint": "^8.57.1",
44
- "eslint-plugin-tsdoc": "^0.3.0",
45
42
  "eventsource": "^2.0.2",
46
43
  "npm-run-all": "^4.1.5",
47
- "prettier": "^3.3.3",
48
- "ts-node": "^10.9.2",
49
44
  "tsup": "^8.3.0",
50
45
  "typescript": "^5.6.3",
51
46
  "vitest": "^2.1.3"
@@ -53,7 +48,8 @@
53
48
  "scripts": {
54
49
  "build": "tsup",
55
50
  "test": "vitest",
56
- "format": "prettier --write ./src/**/*.ts",
57
- "lint": "eslint \"./src/**/*.ts\""
51
+ "format": "biome check --linter-enabled=false --write",
52
+ "lint": "biome lint --write",
53
+ "ci": "biome ci"
58
54
  }
59
55
  }