@asenajs/ergenecore 3.2.1 → 5.0.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.
Files changed (31) hide show
  1. package/README.md +12 -5
  2. package/dist/lib/Ergenecore.d.ts +12 -1
  3. package/dist/lib/Ergenecore.js +320 -173
  4. package/dist/lib/Ergenecore.js.map +1 -1
  5. package/dist/lib/ErgenecoreContextWrapper.d.ts +45 -12
  6. package/dist/lib/ErgenecoreContextWrapper.js +200 -54
  7. package/dist/lib/ErgenecoreContextWrapper.js.map +1 -1
  8. package/dist/lib/defaults/index.d.ts +1 -0
  9. package/dist/lib/defaults/index.js +1 -0
  10. package/dist/lib/defaults/index.js.map +1 -1
  11. package/dist/lib/defineContext.d.ts +8 -0
  12. package/dist/lib/defineContext.js +10 -0
  13. package/dist/lib/defineContext.js.map +1 -0
  14. package/dist/lib/index.d.ts +8 -1
  15. package/dist/lib/index.js +3 -0
  16. package/dist/lib/index.js.map +1 -1
  17. package/dist/lib/middlewares/CorsMiddleware.d.ts +2 -2
  18. package/dist/lib/middlewares/CorsMiddleware.js +5 -9
  19. package/dist/lib/middlewares/CorsMiddleware.js.map +1 -1
  20. package/dist/lib/middlewares/RateLimiterMiddleware.d.ts +73 -3
  21. package/dist/lib/middlewares/RateLimiterMiddleware.js +95 -7
  22. package/dist/lib/middlewares/RateLimiterMiddleware.js.map +1 -1
  23. package/dist/lib/middlewares/WebSocketOriginMiddleware.d.ts +82 -0
  24. package/dist/lib/middlewares/WebSocketOriginMiddleware.js +81 -0
  25. package/dist/lib/middlewares/WebSocketOriginMiddleware.js.map +1 -0
  26. package/dist/lib/stream/SSEStreamWriter.js +22 -13
  27. package/dist/lib/stream/SSEStreamWriter.js.map +1 -1
  28. package/dist/lib/utils/staticFileReader.d.ts +27 -0
  29. package/dist/lib/utils/staticFileReader.js +172 -0
  30. package/dist/lib/utils/staticFileReader.js.map +1 -0
  31. package/package.json +11 -7
@@ -91,10 +91,11 @@ export interface RateLimiterOptions {
91
91
  /**
92
92
  * Custom key generator function
93
93
  *
94
- * By default, uses IP address from X-Forwarded-For header or remote address.
94
+ * By default, identifies the client by the connecting peer, or by the client hop of
95
+ * `X-Forwarded-For` when `trustProxy` trusts the peer.
95
96
  * You can customize this to use user ID, API key, or any other identifier.
96
97
  *
97
- * @default (ctx) => ctx.req.headers.get('x-forwarded-for') || 'unknown'
98
+ * @default (ctx) => ctx.getRequestIp() ?? 'unknown'
98
99
  *
99
100
  * @example
100
101
  * ```typescript
@@ -106,6 +107,38 @@ export interface RateLimiterOptions {
106
107
  * ```
107
108
  */
108
109
  keyGenerator?: (context: Context) => string;
110
+ /**
111
+ * Which connecting peers are reverse proxies whose `X-Forwarded-For` may be believed.
112
+ *
113
+ * - `false` (default): the connecting peer (`getRequestIp()`) is the client; the header is ignored.
114
+ * - `true`: one proxy layer in front of the server. The header is read and its rightmost entry - the
115
+ * one the proxy appended - is the client. Also the right setting when the server listens on a unix
116
+ * socket (no peer address to check).
117
+ * - `string[]`: exact addresses of the trusted proxies. An IPv4 address matches whether it is written
118
+ * plain (`'10.0.0.1'`) or IPv4-mapped (`'::ffff:10.0.0.1'`, how Bun reports it on a dual-stack
119
+ * listener), in the list, the peer and the forwarded hops alike. Entries are walked from the right;
120
+ * the first one not in the list is the client.
121
+ * - `(address) => boolean`: same walk with a predicate (CIDR checks live here).
122
+ *
123
+ * A hop the client wrote itself (`X-Forwarded-For: <forged>, <real>`) is never selected: selection
124
+ * starts from the right and stops at the first untrusted address.
125
+ *
126
+ * Both misconfigurations below fail closed - every client lands in one bucket:
127
+ * - `true` behind two or more proxy layers keys on the outer proxy (the rightmost hop); list every
128
+ * proxy, or use a predicate, instead.
129
+ * - A trusted peer that sends no usable `X-Forwarded-For` keys on the peer itself; logged once.
130
+ *
131
+ * @default false
132
+ */
133
+ trustProxy?: boolean | string[] | ((address: string) => boolean);
134
+ /**
135
+ * Upper bound on distinct client buckets held in memory. When a new client arrives at the cap, the
136
+ * least recently seen bucket is evicted - that client starts over with a full bucket.
137
+ * Must be a positive integer; the constructor throws otherwise.
138
+ *
139
+ * @default 10000
140
+ */
141
+ maxBuckets?: number;
109
142
  /**
110
143
  * Custom message for rate limit exceeded response
111
144
  *
@@ -232,6 +265,14 @@ export declare class RateLimiterMiddleware extends MiddlewareService {
232
265
  * Client key generator function
233
266
  */
234
267
  private readonly keyGenerator;
268
+ /**
269
+ * Trusted reverse proxies whose `X-Forwarded-For` may be believed
270
+ */
271
+ private readonly trustProxy;
272
+ /**
273
+ * Upper bound on distinct client buckets held in memory
274
+ */
275
+ private readonly maxBuckets;
235
276
  /**
236
277
  * Rate limit exceeded message
237
278
  */
@@ -256,6 +297,7 @@ export declare class RateLimiterMiddleware extends MiddlewareService {
256
297
  * Bucket TTL in milliseconds
257
298
  */
258
299
  private readonly bucketTTL;
300
+ private warnedNoForwardedFor;
259
301
  /**
260
302
  * Creates a new rate limiter middleware instance
261
303
  *
@@ -321,12 +363,40 @@ export declare class RateLimiterMiddleware extends MiddlewareService {
321
363
  */
322
364
  clearBuckets(): void;
323
365
  /**
324
- * Default key generator - uses X-Forwarded-For or falls back to 'unknown'
366
+ * Default key generator - the connecting peer, see `trustProxy`
325
367
  *
326
368
  * @param context - Ergenecore context
327
369
  * @returns Client identifier
328
370
  */
329
371
  private defaultKeyGenerator;
372
+ /**
373
+ * Resolve the client identifier: the connecting peer, or the client hop of `X-Forwarded-For`
374
+ * when the peer is a trusted proxy
375
+ *
376
+ * @param context - Ergenecore context
377
+ * @returns Client identifier
378
+ */
379
+ private resolveClientKey;
380
+ /**
381
+ * Warn, once per instance, that a trusted peer forwarded no usable `X-Forwarded-For`
382
+ *
383
+ * @param peer - The trusted peer address
384
+ */
385
+ private warnNoForwardedFor;
386
+ /**
387
+ * Whether an address is a reverse proxy whose forwarded hops may be believed
388
+ *
389
+ * @param address - Peer or forwarded hop address
390
+ * @returns True when the address is trusted
391
+ */
392
+ private trusts;
393
+ /**
394
+ * Parse `X-Forwarded-For` into its hops, left to right
395
+ *
396
+ * @param context - Ergenecore context
397
+ * @returns The forwarded hops, or an empty list when the header is absent or over-long
398
+ */
399
+ private forwardedHops;
330
400
  /**
331
401
  * Setup automatic cleanup of inactive buckets
332
402
  *
@@ -57,6 +57,9 @@ var __metadata = (this && this.__metadata) || function (k, v) {
57
57
  };
58
58
  import { MiddlewareService } from '../defaults';
59
59
  import { OnStop } from '@asenajs/asena/decorators/ioc';
60
+ const MAX_FORWARDED_HEADER_LENGTH = 1024;
61
+ const IPV4_MAPPED_PREFIX = /^::ffff:(?=\d{1,3}(?:\.\d{1,3}){3}$)/i;
62
+ const unmapIPv4 = (address) => address.replace(IPV4_MAPPED_PREFIX, '');
60
63
  /**
61
64
  * Rate Limiter Middleware
62
65
  *
@@ -122,6 +125,14 @@ export class RateLimiterMiddleware extends MiddlewareService {
122
125
  * Client key generator function
123
126
  */
124
127
  keyGenerator;
128
+ /**
129
+ * Trusted reverse proxies whose `X-Forwarded-For` may be believed
130
+ */
131
+ trustProxy;
132
+ /**
133
+ * Upper bound on distinct client buckets held in memory
134
+ */
135
+ maxBuckets;
125
136
  /**
126
137
  * Rate limit exceeded message
127
138
  */
@@ -146,6 +157,7 @@ export class RateLimiterMiddleware extends MiddlewareService {
146
157
  * Bucket TTL in milliseconds
147
158
  */
148
159
  bucketTTL;
160
+ warnedNoForwardedFor = false;
149
161
  /**
150
162
  * Creates a new rate limiter middleware instance
151
163
  *
@@ -173,6 +185,11 @@ export class RateLimiterMiddleware extends MiddlewareService {
173
185
  this.capacity = options.capacity ?? 100;
174
186
  this.refillRate = options.refillRate ?? 10;
175
187
  this.keyGenerator = options.keyGenerator ?? this.defaultKeyGenerator;
188
+ this.trustProxy = options.trustProxy ?? false;
189
+ this.maxBuckets = options.maxBuckets ?? 10_000;
190
+ if (!Number.isInteger(this.maxBuckets) || this.maxBuckets < 1) {
191
+ throw new Error(`RateLimiterMiddleware: maxBuckets must be a positive integer, got ${this.maxBuckets}`);
192
+ }
176
193
  this.message = options.message ?? 'Rate limit exceeded. Please try again later.';
177
194
  this.statusCode = options.statusCode ?? 429;
178
195
  this.cost = options.cost ?? 1;
@@ -208,13 +225,19 @@ export class RateLimiterMiddleware extends MiddlewareService {
208
225
  const key = this.keyGenerator(context);
209
226
  // Get or create bucket
210
227
  let bucket = this.buckets.get(key);
211
- if (!bucket) {
228
+ if (bucket) {
229
+ this.buckets.delete(key);
230
+ }
231
+ else {
212
232
  bucket = {
213
233
  tokens: this.capacity,
214
234
  lastRefill: Date.now(),
215
235
  };
216
- this.buckets.set(key, bucket);
236
+ if (this.buckets.size >= this.maxBuckets) {
237
+ this.buckets.delete(this.buckets.keys().next().value);
238
+ }
217
239
  }
240
+ this.buckets.set(key, bucket);
218
241
  // Refill tokens based on elapsed time
219
242
  const now = Date.now();
220
243
  const elapsedSeconds = (now - bucket.lastRefill) / 1000;
@@ -284,17 +307,82 @@ export class RateLimiterMiddleware extends MiddlewareService {
284
307
  this.buckets.clear();
285
308
  }
286
309
  /**
287
- * Default key generator - uses X-Forwarded-For or falls back to 'unknown'
310
+ * Default key generator - the connecting peer, see `trustProxy`
288
311
  *
289
312
  * @param context - Ergenecore context
290
313
  * @returns Client identifier
291
314
  */
292
315
  defaultKeyGenerator = (context) => {
293
- return (context.req.headers.get('x-forwarded-for') ||
294
- context.req.headers.get('cf-connecting-ip') ||
295
- context.getRequestIp?.() ||
296
- 'unknown');
316
+ return this.resolveClientKey(context);
297
317
  };
318
+ /**
319
+ * Resolve the client identifier: the connecting peer, or the client hop of `X-Forwarded-For`
320
+ * when the peer is a trusted proxy
321
+ *
322
+ * @param context - Ergenecore context
323
+ * @returns Client identifier
324
+ */
325
+ resolveClientKey(context) {
326
+ const peer = context.getRequestIp();
327
+ if (!this.trusts(peer)) {
328
+ return peer ?? 'unknown';
329
+ }
330
+ const hops = this.forwardedHops(context);
331
+ if (!hops.length) {
332
+ this.warnNoForwardedFor(peer);
333
+ return peer ?? 'unknown';
334
+ }
335
+ for (let i = hops.length - 1; i >= 0; i--) {
336
+ if (!this.trusts(hops[i])) {
337
+ return hops[i];
338
+ }
339
+ }
340
+ return hops.at(-1);
341
+ }
342
+ /**
343
+ * Warn, once per instance, that a trusted peer forwarded no usable `X-Forwarded-For`
344
+ *
345
+ * @param peer - The trusted peer address
346
+ */
347
+ warnNoForwardedFor(peer) {
348
+ if (this.warnedNoForwardedFor)
349
+ return;
350
+ this.warnedNoForwardedFor = true;
351
+ console.warn(`RateLimiterMiddleware: trusted proxy ${peer ?? '(no peer address)'} sent no usable X-Forwarded-For; ` +
352
+ 'every client behind it shares one bucket. Make the proxy append X-Forwarded-For or narrow trustProxy.');
353
+ }
354
+ /**
355
+ * Whether an address is a reverse proxy whose forwarded hops may be believed
356
+ *
357
+ * @param address - Peer or forwarded hop address
358
+ * @returns True when the address is trusted
359
+ */
360
+ trusts(address) {
361
+ if (this.trustProxy === true)
362
+ return true;
363
+ if (!this.trustProxy || address === null)
364
+ return false;
365
+ if (!Array.isArray(this.trustProxy))
366
+ return this.trustProxy(address);
367
+ const normalized = unmapIPv4(address);
368
+ return this.trustProxy.some((entry) => unmapIPv4(entry) === normalized);
369
+ }
370
+ /**
371
+ * Parse `X-Forwarded-For` into its hops, left to right
372
+ *
373
+ * @param context - Ergenecore context
374
+ * @returns The forwarded hops, or an empty list when the header is absent or over-long
375
+ */
376
+ forwardedHops(context) {
377
+ const header = context.req.headers.get('x-forwarded-for');
378
+ if (!header || header.length > MAX_FORWARDED_HEADER_LENGTH) {
379
+ return [];
380
+ }
381
+ return header
382
+ .split(',')
383
+ .map((hop) => hop.trim())
384
+ .filter(Boolean);
385
+ }
298
386
  /**
299
387
  * Setup automatic cleanup of inactive buckets
300
388
  *
@@ -1 +1 @@
1
- {"version":3,"file":"RateLimiterMiddleware.js","sourceRoot":"","sources":["../../../lib/middlewares/RateLimiterMiddleware.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"RateLimiterMiddleware.js","sourceRoot":"","sources":["../../../lib/middlewares/RateLimiterMiddleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * WebSocket Origin Middleware for Ergenecore
3
+ *
4
+ * Closes the cross-site WebSocket hijacking hole by:
5
+ * - Rejecting a handshake whose `Origin` is not allowed, before `server.upgrade()`
6
+ * - Deciding explicitly what happens when there is no `Origin` at all
7
+ * - Zero external dependencies (uses Bun's native APIs)
8
+ *
9
+ * @module defaults/WebSocketOriginMiddleware
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { Middleware, WebSocket } from '@asenajs/asena/decorators';
14
+ * import { WebSocketOriginMiddleware } from '@asenajs/ergenecore';
15
+ *
16
+ * @Middleware()
17
+ * export class ChatOrigin extends WebSocketOriginMiddleware {
18
+ * constructor() {
19
+ * super({ allowedOrigins: ['https://app.example.com'] });
20
+ * }
21
+ * }
22
+ *
23
+ * @WebSocket({ path: 'chat', middlewares: [ChatOrigin, SessionAuth] })
24
+ * export class ChatSocket { ... }
25
+ * ```
26
+ */
27
+ import type { Context } from '../ErgenecoreContextWrapper';
28
+ import { MiddlewareService } from '../defaults';
29
+ /**
30
+ * WebSocket origin policy options
31
+ */
32
+ export interface WebSocketOriginOptions {
33
+ /** Origins allowed to open a WebSocket: exact `Origin` header values, or a predicate. */
34
+ allowedOrigins: string[] | ((origin: string) => boolean);
35
+ /**
36
+ * Accept handshakes that carry no `Origin` header (non-browser clients). Browsers always send one,
37
+ * so the default rejects: a missing header is not evidence of a first-party page.
38
+ *
39
+ * @default false
40
+ */
41
+ allowMissingOrigin?: boolean;
42
+ }
43
+ /**
44
+ * WebSocket Origin Middleware
45
+ *
46
+ * Use it as a `@WebSocket({ middlewares: [...] })` middleware, listed **before** any cookie-based
47
+ * auth middleware: the browser attaches cookies to a cross-site handshake, so a session is already
48
+ * authenticated by the time an auth middleware looks at it.
49
+ *
50
+ * `CorsMiddleware` is not a WebSocket origin policy - it sets response headers a browser applies to
51
+ * `fetch`/XHR, and a disallowed origin still upgrades.
52
+ *
53
+ * The `Origin` header is compared exactly: `'https://app.example.com'`, scheme included, no trailing
54
+ * slash.
55
+ *
56
+ * It is an ordinary middleware, so on an HTTP route it would reject every request without an
57
+ * `Origin` - which is why the name says WebSocket.
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * // A predicate covers wildcard subdomains; there is no '*' special value
62
+ * new WebSocketOriginMiddleware({ allowedOrigins: (origin) => origin.endsWith('.example.com') });
63
+ * ```
64
+ */
65
+ export declare class WebSocketOriginMiddleware extends MiddlewareService {
66
+ private readonly allowedOrigins;
67
+ private readonly allowMissingOrigin;
68
+ /**
69
+ * Creates a new WebSocket origin middleware instance
70
+ *
71
+ * @param options - Origin policy options
72
+ */
73
+ constructor(options: WebSocketOriginOptions);
74
+ /**
75
+ * Reject the handshake unless its `Origin` is allowed
76
+ *
77
+ * @param context - Ergenecore context wrapper
78
+ * @param next - Function to call next middleware or handler
79
+ * @returns A 403 response, or the result of next() for an allowed origin
80
+ */
81
+ handle(context: Context, next: () => Promise<void>): Promise<any>;
82
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * WebSocket Origin Middleware for Ergenecore
3
+ *
4
+ * Closes the cross-site WebSocket hijacking hole by:
5
+ * - Rejecting a handshake whose `Origin` is not allowed, before `server.upgrade()`
6
+ * - Deciding explicitly what happens when there is no `Origin` at all
7
+ * - Zero external dependencies (uses Bun's native APIs)
8
+ *
9
+ * @module defaults/WebSocketOriginMiddleware
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { Middleware, WebSocket } from '@asenajs/asena/decorators';
14
+ * import { WebSocketOriginMiddleware } from '@asenajs/ergenecore';
15
+ *
16
+ * @Middleware()
17
+ * export class ChatOrigin extends WebSocketOriginMiddleware {
18
+ * constructor() {
19
+ * super({ allowedOrigins: ['https://app.example.com'] });
20
+ * }
21
+ * }
22
+ *
23
+ * @WebSocket({ path: 'chat', middlewares: [ChatOrigin, SessionAuth] })
24
+ * export class ChatSocket { ... }
25
+ * ```
26
+ */
27
+ import { MiddlewareService } from '../defaults';
28
+ /**
29
+ * WebSocket Origin Middleware
30
+ *
31
+ * Use it as a `@WebSocket({ middlewares: [...] })` middleware, listed **before** any cookie-based
32
+ * auth middleware: the browser attaches cookies to a cross-site handshake, so a session is already
33
+ * authenticated by the time an auth middleware looks at it.
34
+ *
35
+ * `CorsMiddleware` is not a WebSocket origin policy - it sets response headers a browser applies to
36
+ * `fetch`/XHR, and a disallowed origin still upgrades.
37
+ *
38
+ * The `Origin` header is compared exactly: `'https://app.example.com'`, scheme included, no trailing
39
+ * slash.
40
+ *
41
+ * It is an ordinary middleware, so on an HTTP route it would reject every request without an
42
+ * `Origin` - which is why the name says WebSocket.
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * // A predicate covers wildcard subdomains; there is no '*' special value
47
+ * new WebSocketOriginMiddleware({ allowedOrigins: (origin) => origin.endsWith('.example.com') });
48
+ * ```
49
+ */
50
+ export class WebSocketOriginMiddleware extends MiddlewareService {
51
+ allowedOrigins;
52
+ allowMissingOrigin;
53
+ /**
54
+ * Creates a new WebSocket origin middleware instance
55
+ *
56
+ * @param options - Origin policy options
57
+ */
58
+ constructor(options) {
59
+ super();
60
+ this.allowedOrigins = options.allowedOrigins;
61
+ this.allowMissingOrigin = options.allowMissingOrigin ?? false;
62
+ }
63
+ /**
64
+ * Reject the handshake unless its `Origin` is allowed
65
+ *
66
+ * @param context - Ergenecore context wrapper
67
+ * @param next - Function to call next middleware or handler
68
+ * @returns A 403 response, or the result of next() for an allowed origin
69
+ */
70
+ async handle(context, next) {
71
+ const origin = context.req.headers.get('Origin');
72
+ if (!origin) {
73
+ return this.allowMissingOrigin ? await next() : new Response('Forbidden', { status: 403 });
74
+ }
75
+ const allowed = Array.isArray(this.allowedOrigins)
76
+ ? this.allowedOrigins.includes(origin)
77
+ : this.allowedOrigins(origin);
78
+ return allowed ? await next() : new Response('Forbidden', { status: 403 });
79
+ }
80
+ }
81
+ //# sourceMappingURL=WebSocketOriginMiddleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketOriginMiddleware.js","sourceRoot":"","sources":["../../../lib/middlewares/WebSocketOriginMiddleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAkBhD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,OAAO,yBAA0B,SAAQ,iBAAiB;IAC7C,cAAc,CAA2C;IAEzD,kBAAkB,CAAU;IAE7C;;;;OAIG;IACH,YAAmB,OAA+B;QAChD,KAAK,EAAE,CAAC;QAER,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;QAC7C,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,IAAI,KAAK,CAAC;IAChE,CAAC;IAED;;;;;;OAMG;IACI,KAAK,CAAC,MAAM,CAAC,OAAgB,EAAE,IAAyB;QAC7D,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAEjD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QAC7F,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC;YAChD,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC;YACtC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QAEhC,OAAO,OAAO,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;IAC7E,CAAC;CACF"}
@@ -5,19 +5,28 @@ import { StreamWriter } from './StreamWriter';
5
5
  */
6
6
  export class SSEStreamWriter extends StreamWriter {
7
7
  async writeSSE(message) {
8
- const dataLines = message.data
9
- .split(/\r\n|\r|\n/)
10
- .map((line) => `data: ${line}`)
11
- .join('\n');
12
- const sseData = [
13
- message.event && `event: ${message.event}`,
14
- dataLines,
15
- message.id && `id: ${message.id}`,
16
- message.retry && `retry: ${message.retry}`,
17
- ]
18
- .filter(Boolean)
19
- .join('\n') + '\n\n';
20
- await this.write(sseData);
8
+ if (message.data === undefined && message.comment === undefined) {
9
+ throw new Error('writeSSE: message needs data or comment');
10
+ }
11
+ const lines = [];
12
+ for (const line of message.comment?.split(/\r\n|\r|\n/) ?? []) {
13
+ lines.push(`: ${line}`);
14
+ }
15
+ if (message.event) {
16
+ lines.push(`event: ${message.event}`);
17
+ }
18
+ if (message.data !== undefined) {
19
+ for (const line of message.data.split(/\r\n|\r|\n/)) {
20
+ lines.push(`data: ${line}`);
21
+ }
22
+ }
23
+ if (message.id) {
24
+ lines.push(`id: ${message.id}`);
25
+ }
26
+ if (message.retry) {
27
+ lines.push(`retry: ${message.retry}`);
28
+ }
29
+ await this.write(lines.join('\n') + '\n\n');
21
30
  }
22
31
  }
23
32
  //# sourceMappingURL=SSEStreamWriter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SSEStreamWriter.js","sourceRoot":"","sources":["../../../lib/stream/SSEStreamWriter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C;;;GAGG;AACH,MAAM,OAAO,eAAgB,SAAQ,YAAY;IACxC,KAAK,CAAC,QAAQ,CAAC,OAAmB;QACvC,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI;aAC3B,KAAK,CAAC,YAAY,CAAC;aACnB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,IAAI,EAAE,CAAC;aAC9B,IAAI,CAAC,IAAI,CAAC,CAAC;QAEd,MAAM,OAAO,GACX;YACE,OAAO,CAAC,KAAK,IAAI,UAAU,OAAO,CAAC,KAAK,EAAE;YAC1C,SAAS;YACT,OAAO,CAAC,EAAE,IAAI,OAAO,OAAO,CAAC,EAAE,EAAE;YACjC,OAAO,CAAC,KAAK,IAAI,UAAU,OAAO,CAAC,KAAK,EAAE;SAC3C;aACE,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC;QAEzB,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;CACF"}
1
+ {"version":3,"file":"SSEStreamWriter.js","sourceRoot":"","sources":["../../../lib/stream/SSEStreamWriter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C;;;GAGG;AACH,MAAM,OAAO,eAAgB,SAAQ,YAAY;IACxC,KAAK,CAAC,QAAQ,CAAC,OAAmB;QACvC,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAChE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;QAC7D,CAAC;QAED,MAAM,KAAK,GAAa,EAAE,CAAC;QAE3B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC;YAC9D,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QAC1B,CAAC;QAED,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,KAAK,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACxC,CAAC;QAED,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;gBACpD,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAED,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,OAAO,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;QAClC,CAAC;QAED,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,KAAK,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACxC,CAAC;QAED,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;IAC9C,CAAC;CACF"}
@@ -0,0 +1,27 @@
1
+ import { type Stats } from 'fs';
2
+ export declare function staticFileHeaders(type: string, etag: string, lastModified: string): Headers;
3
+ export interface StaticFileRead {
4
+ readonly bytes: Uint8Array<ArrayBuffer> | undefined;
5
+ readonly type: string;
6
+ readonly etag: string;
7
+ readonly lastModified: string;
8
+ }
9
+ export declare function regularFileStats(filePath: string): Stats | undefined;
10
+ /**
11
+ * Reads small regular files from disk on every call through a bounded set of open descriptors.
12
+ * Nothing but the descriptor and derived metadata outlives a call; the bytes are always fresh.
13
+ */
14
+ export declare class StaticFileReader {
15
+ private readonly maxFileBytes;
16
+ private readonly maxOpen;
17
+ private readonly files;
18
+ constructor(maxFileBytes?: number, maxOpen?: number);
19
+ read(filePath: string, stats: Stats, withBody: boolean): StaticFileRead;
20
+ respond(filePath: string, stats: Stats, withBody: boolean): Response;
21
+ private refresh;
22
+ release(filePath: string): void;
23
+ close(): void;
24
+ private readBytes;
25
+ private entry;
26
+ private forget;
27
+ }