@swarmmachina/swm-core 1.1.4 → 1.2.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/README.md +97 -6
- package/package.json +3 -1
- package/src/http-context.js +170 -25
- package/src/res-streamer.js +1 -1
package/README.md
CHANGED
|
@@ -291,7 +291,7 @@ The `ctx` object passed to the router function:
|
|
|
291
291
|
|
|
292
292
|
Get request lowercased method.
|
|
293
293
|
|
|
294
|
-
```
|
|
294
|
+
```javascript
|
|
295
295
|
const method = ctx.method()
|
|
296
296
|
```
|
|
297
297
|
|
|
@@ -327,6 +327,16 @@ const page = ctx.query('page') // ?page=1
|
|
|
327
327
|
|
|
328
328
|
**Returns:** `string`
|
|
329
329
|
|
|
330
|
+
##### `ctx.fullQuery()`
|
|
331
|
+
|
|
332
|
+
Get full raw query string.
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
335
|
+
const q = ctx.fullQuery() // page=1&limit=20
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Returns:** `string`
|
|
339
|
+
|
|
330
340
|
##### `ctx.param(indexOrName)`
|
|
331
341
|
|
|
332
342
|
Get URL parameter by index or name (for pattern matching in native routing).
|
|
@@ -398,7 +408,7 @@ ctx.status(201).send({ created: true })
|
|
|
398
408
|
|
|
399
409
|
##### `ctx.setHeader(key, value)`
|
|
400
410
|
|
|
401
|
-
Set a response header.
|
|
411
|
+
Set or replace a staged response header. Header names are case-insensitive. Repeated `setHeader()` calls replace previously staged values for the same header. Null or undefined values are silently ignored.
|
|
402
412
|
|
|
403
413
|
```javascript
|
|
404
414
|
ctx.setHeader('x-header-any', 'string-value').status(201).send({ created: true })
|
|
@@ -406,6 +416,37 @@ ctx.setHeader('x-header-any', 'string-value').status(201).send({ created: true }
|
|
|
406
416
|
|
|
407
417
|
**Returns:** `HttpContext`
|
|
408
418
|
|
|
419
|
+
##### `ctx.appendHeader(key, value)`
|
|
420
|
+
|
|
421
|
+
Append another staged response header line without replacing existing values. Useful for repeated headers such as `Set-Cookie`. Null or undefined values are silently ignored.
|
|
422
|
+
|
|
423
|
+
```javascript
|
|
424
|
+
ctx.appendHeader('set-cookie', 'access=...; Path=/; HttpOnly')
|
|
425
|
+
ctx.appendHeader('set-cookie', 'refresh=...; Path=/refresh; HttpOnly')
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
**Returns:** `HttpContext`
|
|
429
|
+
|
|
430
|
+
##### `ctx.setHeaders(headers)`
|
|
431
|
+
|
|
432
|
+
Set multiple response headers at once. Equivalent to calling `setHeader()` for each key. Header values may be strings or arrays of strings.
|
|
433
|
+
|
|
434
|
+
```javascript
|
|
435
|
+
ctx.setHeaders({
|
|
436
|
+
'x-request-id': '123',
|
|
437
|
+
'cache-control': 'no-cache',
|
|
438
|
+
'set-cookie': ['a=1; Path=/', 'b=2; Path=/refresh']
|
|
439
|
+
})
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
##### `ctx.flushHeaders([headers])`
|
|
443
|
+
|
|
444
|
+
Flush all staged headers (and optionally stage additional ones) to the underlying response. Called automatically by `reply()` and `startStreaming()` — only needed for advanced use cases.
|
|
445
|
+
|
|
446
|
+
```javascript
|
|
447
|
+
ctx.flushHeaders({ 'x-extra': 'value' })
|
|
448
|
+
```
|
|
449
|
+
|
|
409
450
|
##### `ctx.send(data)`
|
|
410
451
|
|
|
411
452
|
Send response with automatic content-type detection.
|
|
@@ -419,12 +460,59 @@ ctx.send(null) // 204 No Content
|
|
|
419
460
|
|
|
420
461
|
**Supported types:** Object, String, Buffer, null, undefined
|
|
421
462
|
|
|
463
|
+
##### `ctx.sendJson(data, [status])`
|
|
464
|
+
|
|
465
|
+
Send a JSON response with explicit status code. Defaults to `200`.
|
|
466
|
+
|
|
467
|
+
```javascript
|
|
468
|
+
ctx.sendJson({ users: [] })
|
|
469
|
+
ctx.sendJson({ error: 'Not found' }, 404)
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
##### `ctx.sendText(text, [status])`
|
|
473
|
+
|
|
474
|
+
Send a plain text response with explicit status code. Defaults to `200`.
|
|
475
|
+
|
|
476
|
+
```javascript
|
|
477
|
+
ctx.sendText('OK')
|
|
478
|
+
ctx.sendText('Created', 201)
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
##### `ctx.sendBuffer(buffer, [status])`
|
|
482
|
+
|
|
483
|
+
Send a binary response with explicit status code. Defaults to `200`.
|
|
484
|
+
|
|
485
|
+
```javascript
|
|
486
|
+
ctx.sendBuffer(Buffer.from('data'))
|
|
487
|
+
ctx.sendBuffer(imageBuffer, 201)
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
##### `ctx.sendError(error)`
|
|
491
|
+
|
|
492
|
+
Send an error response. If `error.status` is a finite number, uses that as the HTTP status with `error.message` as the body. Otherwise responds with `500 Internal Server Error`.
|
|
493
|
+
|
|
494
|
+
```javascript
|
|
495
|
+
ctx.sendError(new Error('Something broke'))
|
|
496
|
+
|
|
497
|
+
// With custom status
|
|
498
|
+
const err = new Error('Not found')
|
|
499
|
+
err.status = 404
|
|
500
|
+
ctx.sendError(err)
|
|
501
|
+
```
|
|
502
|
+
|
|
422
503
|
##### `ctx.reply(status, headers, body)`
|
|
423
504
|
|
|
424
|
-
Send response with full control over status, headers, and body.
|
|
505
|
+
Send response with full control over status, headers, and body. Header values may be strings or arrays of strings. Array values are written as separate header lines.
|
|
425
506
|
|
|
426
507
|
```javascript
|
|
427
|
-
ctx.reply(
|
|
508
|
+
ctx.reply(
|
|
509
|
+
200,
|
|
510
|
+
{
|
|
511
|
+
'content-type': 'application/json',
|
|
512
|
+
'set-cookie': ['a=1; Path=/', 'b=2; Path=/refresh']
|
|
513
|
+
},
|
|
514
|
+
'{"ok":true}'
|
|
515
|
+
)
|
|
428
516
|
```
|
|
429
517
|
|
|
430
518
|
##### `ctx.stream(readable, [status], [headers])`
|
|
@@ -442,10 +530,13 @@ await ctx.stream(stream, 200, { 'content-type': 'video/mp4' })
|
|
|
442
530
|
|
|
443
531
|
##### `ctx.startStreaming([status], [headers])`
|
|
444
532
|
|
|
445
|
-
Start streaming response manually (for advanced use cases).
|
|
533
|
+
Start streaming response manually (for advanced use cases). Header values may be strings or arrays of strings.
|
|
446
534
|
|
|
447
535
|
```javascript
|
|
448
|
-
ctx.startStreaming(200, {
|
|
536
|
+
ctx.startStreaming(200, {
|
|
537
|
+
'content-type': 'text/plain',
|
|
538
|
+
'set-cookie': ['a=1; Path=/', 'b=2; Path=/refresh']
|
|
539
|
+
})
|
|
449
540
|
```
|
|
450
541
|
|
|
451
542
|
##### `ctx.write(chunk)`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmmachina/swm-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Zero-dependency high-performance HTTP/WebSocket server built on uWebSockets.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"http",
|
|
@@ -44,6 +44,8 @@
|
|
|
44
44
|
"test:e2e:ws": "node --test tests/e2e/ws/*.test.js",
|
|
45
45
|
"bench": "node ./benchmark/bench.js --test base --runs 1 --warmup 10 --sample-ms 250 --v8prof true",
|
|
46
46
|
"bench:core": "node ./benchmark/bench.js --test base --runs 1 --warmup 10 --sample-ms 250 --v8prof true --fw core",
|
|
47
|
+
"bench:headers": "node ./benchmark/bench.js --test headers --runs 1 --warmup 10 --sample-ms 250 --v8prof true",
|
|
48
|
+
"bench:headers:core": "node ./benchmark/bench.js --test headers --runs 1 --warmup 10 --sample-ms 250 --v8prof true --fw core",
|
|
47
49
|
"prepublishOnly": "npm run fix && npm test",
|
|
48
50
|
"release": "npm run check && npm test && npm publish"
|
|
49
51
|
},
|
package/src/http-context.js
CHANGED
|
@@ -8,7 +8,8 @@ export default class HttpContext {
|
|
|
8
8
|
#url = ''
|
|
9
9
|
#headersCached = false
|
|
10
10
|
#headers = {}
|
|
11
|
-
#fullQuery =
|
|
11
|
+
#fullQuery = ''
|
|
12
|
+
#fullQueryCached = false
|
|
12
13
|
#fullQueryParsed = false
|
|
13
14
|
#query = {}
|
|
14
15
|
#params = {}
|
|
@@ -122,7 +123,8 @@ export default class HttpContext {
|
|
|
122
123
|
this.#method = ''
|
|
123
124
|
this.#headersCached = false
|
|
124
125
|
this.#headers = {}
|
|
125
|
-
this.#fullQuery =
|
|
126
|
+
this.#fullQuery = ''
|
|
127
|
+
this.#fullQueryCached = false
|
|
126
128
|
this.#fullQueryParsed = false
|
|
127
129
|
this.#query = {}
|
|
128
130
|
this.#params = {}
|
|
@@ -155,7 +157,8 @@ export default class HttpContext {
|
|
|
155
157
|
this.#method = ''
|
|
156
158
|
this.#headersCached = false
|
|
157
159
|
this.#headers = {}
|
|
158
|
-
this.#fullQuery =
|
|
160
|
+
this.#fullQuery = ''
|
|
161
|
+
this.#fullQueryCached = false
|
|
159
162
|
this.#fullQueryParsed = false
|
|
160
163
|
this.#query = {}
|
|
161
164
|
this.#params = {}
|
|
@@ -200,13 +203,14 @@ export default class HttpContext {
|
|
|
200
203
|
}
|
|
201
204
|
|
|
202
205
|
cacheQuery() {
|
|
203
|
-
if (this.#
|
|
206
|
+
if (this.#fullQueryCached || !this.req) {
|
|
204
207
|
return
|
|
205
208
|
}
|
|
206
209
|
|
|
207
210
|
const fullQuery = this.req.getQuery()
|
|
208
211
|
|
|
209
212
|
this.#fullQuery = typeof fullQuery === 'string' ? fullQuery : ''
|
|
213
|
+
this.#fullQueryCached = true
|
|
210
214
|
this.#fullQueryParsed = false
|
|
211
215
|
}
|
|
212
216
|
|
|
@@ -291,6 +295,27 @@ export default class HttpContext {
|
|
|
291
295
|
return this.#url
|
|
292
296
|
}
|
|
293
297
|
|
|
298
|
+
/**
|
|
299
|
+
* @returns {string}
|
|
300
|
+
*/
|
|
301
|
+
fullQuery() {
|
|
302
|
+
if (this.#fullQueryCached) {
|
|
303
|
+
return this.#fullQuery
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!this.req) {
|
|
307
|
+
return ''
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const fullQuery = this.req.getQuery()
|
|
311
|
+
|
|
312
|
+
this.#fullQuery = typeof fullQuery === 'string' ? fullQuery : ''
|
|
313
|
+
this.#fullQueryCached = true
|
|
314
|
+
this.#fullQueryParsed = false
|
|
315
|
+
|
|
316
|
+
return this.#fullQuery
|
|
317
|
+
}
|
|
318
|
+
|
|
294
319
|
/**
|
|
295
320
|
* @param {string} name
|
|
296
321
|
* @returns {string|undefined}
|
|
@@ -300,7 +325,7 @@ export default class HttpContext {
|
|
|
300
325
|
return this.#query[name]
|
|
301
326
|
}
|
|
302
327
|
|
|
303
|
-
if (this.#
|
|
328
|
+
if (this.#fullQueryCached) {
|
|
304
329
|
this.#parseFullQuery()
|
|
305
330
|
|
|
306
331
|
if (name in this.#query) {
|
|
@@ -403,12 +428,61 @@ export default class HttpContext {
|
|
|
403
428
|
return this
|
|
404
429
|
}
|
|
405
430
|
|
|
406
|
-
|
|
431
|
+
if (typeof key !== 'string') {
|
|
432
|
+
throw new TypeError('Header name must be a string')
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (value === undefined || value === null) {
|
|
436
|
+
return this
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const headerKey = key.toLowerCase()
|
|
440
|
+
|
|
441
|
+
this.#pendingHeaders.set(headerKey, [key, `${value}`])
|
|
442
|
+
return this
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* @param {string} key
|
|
447
|
+
* @param {string} value
|
|
448
|
+
* @returns {HttpContext}
|
|
449
|
+
*/
|
|
450
|
+
appendHeader(key, value) {
|
|
451
|
+
if (this.replied || this.aborted) {
|
|
452
|
+
return this
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (typeof key !== 'string') {
|
|
456
|
+
throw new TypeError('Header name must be a string')
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (value === undefined || value === null) {
|
|
460
|
+
return this
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const headerKey = key.toLowerCase()
|
|
464
|
+
const headerValue = `${value}`
|
|
465
|
+
const pendingHeader = this.#pendingHeaders.get(headerKey)
|
|
466
|
+
|
|
467
|
+
if (!pendingHeader) {
|
|
468
|
+
this.#pendingHeaders.set(headerKey, [key, headerValue])
|
|
469
|
+
return this
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
pendingHeader[0] = key
|
|
473
|
+
const cur = pendingHeader[1]
|
|
474
|
+
|
|
475
|
+
if (typeof cur === 'string') {
|
|
476
|
+
pendingHeader[1] = [cur, headerValue]
|
|
477
|
+
} else {
|
|
478
|
+
cur[cur.length] = headerValue
|
|
479
|
+
}
|
|
480
|
+
|
|
407
481
|
return this
|
|
408
482
|
}
|
|
409
483
|
|
|
410
484
|
/**
|
|
411
|
-
* @param {Record<string, string> | null | undefined} headers
|
|
485
|
+
* @param {Record<string, string | string[]> | null | undefined} headers
|
|
412
486
|
*/
|
|
413
487
|
setHeaders(headers) {
|
|
414
488
|
if (this.replied || this.aborted) {
|
|
@@ -419,7 +493,7 @@ export default class HttpContext {
|
|
|
419
493
|
}
|
|
420
494
|
|
|
421
495
|
/**
|
|
422
|
-
* @param {Record<string, string> | null | undefined} headers
|
|
496
|
+
* @param {Record<string, string | string[]> | null | undefined} headers
|
|
423
497
|
*/
|
|
424
498
|
flushHeaders(headers = null) {
|
|
425
499
|
this.#flushPendingHeaders(headers)
|
|
@@ -427,21 +501,83 @@ export default class HttpContext {
|
|
|
427
501
|
|
|
428
502
|
/**
|
|
429
503
|
* @param {string} key
|
|
430
|
-
* @param {string} value
|
|
504
|
+
* @param {string | string[] | null | undefined} value
|
|
505
|
+
* @param {boolean} append
|
|
431
506
|
*/
|
|
432
|
-
#
|
|
507
|
+
#stagePendingHeader(key, value, append) {
|
|
433
508
|
if (value === undefined || value === null) {
|
|
434
509
|
return
|
|
435
510
|
}
|
|
436
511
|
|
|
437
|
-
const
|
|
438
|
-
|
|
512
|
+
const headerKey = key.toLowerCase()
|
|
513
|
+
|
|
514
|
+
if (!Array.isArray(value)) {
|
|
515
|
+
const headerValue = `${value}`
|
|
516
|
+
|
|
517
|
+
if (!append) {
|
|
518
|
+
this.#pendingHeaders.set(headerKey, [key, headerValue])
|
|
519
|
+
return
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const pendingHeader = this.#pendingHeaders.get(headerKey)
|
|
523
|
+
|
|
524
|
+
if (!pendingHeader) {
|
|
525
|
+
this.#pendingHeaders.set(headerKey, [key, headerValue])
|
|
526
|
+
return
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
pendingHeader[0] = key
|
|
530
|
+
const cur = pendingHeader[1]
|
|
531
|
+
|
|
532
|
+
if (typeof cur === 'string') {
|
|
533
|
+
pendingHeader[1] = [cur, headerValue]
|
|
534
|
+
} else {
|
|
535
|
+
cur[cur.length] = headerValue
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
this.#stagePendingHeaderArray(key, headerKey, value, append)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* @param {string} key
|
|
546
|
+
* @param {string} headerKey
|
|
547
|
+
* @param {string[]} value
|
|
548
|
+
* @param {boolean} append
|
|
549
|
+
*/
|
|
550
|
+
#stagePendingHeaderArray(key, headerKey, value, append) {
|
|
551
|
+
let pendingHeader = append ? this.#pendingHeaders.get(headerKey) : null
|
|
552
|
+
|
|
553
|
+
for (let i = 0, len = value.length; i < len; i++) {
|
|
554
|
+
const entry = value[i]
|
|
555
|
+
|
|
556
|
+
if (entry === undefined || entry === null) {
|
|
557
|
+
continue
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const headerValue = `${entry}`
|
|
561
|
+
|
|
562
|
+
if (!pendingHeader) {
|
|
563
|
+
pendingHeader = [key, headerValue]
|
|
564
|
+
this.#pendingHeaders.set(headerKey, pendingHeader)
|
|
565
|
+
continue
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
pendingHeader[0] = key
|
|
569
|
+
const cur = pendingHeader[1]
|
|
439
570
|
|
|
440
|
-
|
|
571
|
+
if (typeof cur === 'string') {
|
|
572
|
+
pendingHeader[1] = [cur, headerValue]
|
|
573
|
+
} else {
|
|
574
|
+
cur[cur.length] = headerValue
|
|
575
|
+
}
|
|
576
|
+
}
|
|
441
577
|
}
|
|
442
578
|
|
|
443
579
|
/**
|
|
444
|
-
* @param {Record<string, string> | null | undefined} headers
|
|
580
|
+
* @param {Record<string, string | string[]> | null | undefined} headers
|
|
445
581
|
*/
|
|
446
582
|
#stageHeaders(headers) {
|
|
447
583
|
if (!headers) {
|
|
@@ -449,29 +585,27 @@ export default class HttpContext {
|
|
|
449
585
|
}
|
|
450
586
|
|
|
451
587
|
if (headers === TEXT_PLAIN_HEADER) {
|
|
452
|
-
this.#
|
|
588
|
+
this.#pendingHeaders.set('content-type', ['content-type', 'text/plain; charset=utf-8'])
|
|
453
589
|
return
|
|
454
590
|
}
|
|
455
591
|
|
|
456
592
|
if (headers === JSON_HEADER) {
|
|
457
|
-
this.#
|
|
593
|
+
this.#pendingHeaders.set('content-type', ['content-type', 'application/json; charset=utf-8'])
|
|
458
594
|
return
|
|
459
595
|
}
|
|
460
596
|
|
|
461
597
|
if (headers === OCTET_STREAM_HEADER) {
|
|
462
|
-
this.#
|
|
598
|
+
this.#pendingHeaders.set('content-type', ['content-type', 'application/octet-stream'])
|
|
463
599
|
return
|
|
464
600
|
}
|
|
465
601
|
|
|
466
602
|
for (const key in headers) {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
this.#stageHeader(key, value)
|
|
603
|
+
this.#stagePendingHeader(key, headers[key], false)
|
|
470
604
|
}
|
|
471
605
|
}
|
|
472
606
|
|
|
473
607
|
/**
|
|
474
|
-
* @param {Record<string, string> | null | undefined} headers
|
|
608
|
+
* @param {Record<string, string | string[]> | null | undefined} headers
|
|
475
609
|
*/
|
|
476
610
|
#flushPendingHeaders(headers = null) {
|
|
477
611
|
if (!this.res) {
|
|
@@ -483,8 +617,19 @@ export default class HttpContext {
|
|
|
483
617
|
this.#stageHeaders(headers)
|
|
484
618
|
}
|
|
485
619
|
|
|
486
|
-
for (const [,
|
|
487
|
-
|
|
620
|
+
for (const [, pendingHeader] of this.#pendingHeaders) {
|
|
621
|
+
const headerValue = pendingHeader[1]
|
|
622
|
+
|
|
623
|
+
if (typeof headerValue === 'string') {
|
|
624
|
+
this.res.writeHeader(pendingHeader[0], headerValue)
|
|
625
|
+
continue
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const name = pendingHeader[0]
|
|
629
|
+
|
|
630
|
+
for (let i = 0, len = headerValue.length; i < len; i++) {
|
|
631
|
+
this.res.writeHeader(name, headerValue[i])
|
|
632
|
+
}
|
|
488
633
|
}
|
|
489
634
|
|
|
490
635
|
this.#pendingHeaders.clear()
|
|
@@ -557,7 +702,7 @@ export default class HttpContext {
|
|
|
557
702
|
|
|
558
703
|
/**
|
|
559
704
|
* @param {number} status
|
|
560
|
-
* @param {Record<string,string>} headers
|
|
705
|
+
* @param {Record<string, string | string[]>} headers
|
|
561
706
|
* @param {string|ArrayBuffer|Uint8Array|Buffer|null|undefined} body
|
|
562
707
|
*/
|
|
563
708
|
reply(status = 200, headers = null, body = null) {
|
|
@@ -585,7 +730,7 @@ export default class HttpContext {
|
|
|
585
730
|
|
|
586
731
|
/**
|
|
587
732
|
* @param {number} status
|
|
588
|
-
* @param {Record<string,string>} headers
|
|
733
|
+
* @param {Record<string, string | string[]>} headers
|
|
589
734
|
* @returns {HttpContext}
|
|
590
735
|
*/
|
|
591
736
|
startStreaming(status = 200, headers = null) {
|
package/src/res-streamer.js
CHANGED
|
@@ -82,7 +82,7 @@ export default class ResStreamer {
|
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
84
|
* @param {number|string} status
|
|
85
|
-
* @param {Record<string,string>|null} headers
|
|
85
|
+
* @param {Record<string, string | string[]>|null} headers
|
|
86
86
|
* @returns {ResStreamer}
|
|
87
87
|
*/
|
|
88
88
|
begin(status = 200, headers = null) {
|