@swarmmachina/swm-core 1.1.3 → 1.1.5

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 CHANGED
@@ -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).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmmachina/swm-core",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Zero-dependency high-performance HTTP/WebSocket server built on uWebSockets.js",
5
5
  "keywords": [
6
6
  "http",
@@ -8,12 +8,14 @@ export default class HttpContext {
8
8
  #url = ''
9
9
  #headersCached = false
10
10
  #headers = {}
11
- #fullQuery = null
11
+ #fullQuery = ''
12
+ #fullQueryCached = false
12
13
  #fullQueryParsed = false
13
14
  #query = {}
14
15
  #params = {}
15
16
  #statusOverride = null
16
17
  #contentLength = undefined
18
+ #pendingHeaders = new Map()
17
19
  #bodyParser = new BodyParser()
18
20
  #resStreamer = new ResStreamer()
19
21
 
@@ -114,13 +116,15 @@ export default class HttpContext {
114
116
 
115
117
  this.#statusOverride = null
116
118
  this.#contentLength = undefined
119
+ this.#pendingHeaders.clear()
117
120
 
118
121
  this.#ip = ''
119
122
  this.#url = ''
120
123
  this.#method = ''
121
124
  this.#headersCached = false
122
125
  this.#headers = {}
123
- this.#fullQuery = null
126
+ this.#fullQuery = ''
127
+ this.#fullQueryCached = false
124
128
  this.#fullQueryParsed = false
125
129
  this.#query = {}
126
130
  this.#params = {}
@@ -147,12 +151,14 @@ export default class HttpContext {
147
151
 
148
152
  this.#statusOverride = null
149
153
  this.#contentLength = undefined
154
+ this.#pendingHeaders.clear()
150
155
  this.#ip = ''
151
156
  this.#url = ''
152
157
  this.#method = ''
153
158
  this.#headersCached = false
154
159
  this.#headers = {}
155
- this.#fullQuery = null
160
+ this.#fullQuery = ''
161
+ this.#fullQueryCached = false
156
162
  this.#fullQueryParsed = false
157
163
  this.#query = {}
158
164
  this.#params = {}
@@ -197,13 +203,14 @@ export default class HttpContext {
197
203
  }
198
204
 
199
205
  cacheQuery() {
200
- if (this.#fullQuery !== null || !this.req) {
206
+ if (this.#fullQueryCached || !this.req) {
201
207
  return
202
208
  }
203
209
 
204
210
  const fullQuery = this.req.getQuery()
205
211
 
206
212
  this.#fullQuery = typeof fullQuery === 'string' ? fullQuery : ''
213
+ this.#fullQueryCached = true
207
214
  this.#fullQueryParsed = false
208
215
  }
209
216
 
@@ -288,6 +295,27 @@ export default class HttpContext {
288
295
  return this.#url
289
296
  }
290
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
+
291
319
  /**
292
320
  * @param {string} name
293
321
  * @returns {string|undefined}
@@ -297,7 +325,7 @@ export default class HttpContext {
297
325
  return this.#query[name]
298
326
  }
299
327
 
300
- if (this.#fullQuery !== null) {
328
+ if (this.#fullQueryCached) {
301
329
  this.#parseFullQuery()
302
330
 
303
331
  if (name in this.#query) {
@@ -396,7 +424,11 @@ export default class HttpContext {
396
424
  * @returns {HttpContext}
397
425
  */
398
426
  setHeader(key, value) {
399
- this.res.writeHeader(key, value)
427
+ if (this.replied || this.aborted) {
428
+ return this
429
+ }
430
+
431
+ this.#stageHeader(key, value)
400
432
  return this
401
433
  }
402
434
 
@@ -404,32 +436,83 @@ export default class HttpContext {
404
436
  * @param {Record<string, string> | null | undefined} headers
405
437
  */
406
438
  setHeaders(headers) {
439
+ if (this.replied || this.aborted) {
440
+ return
441
+ }
442
+
443
+ this.#stageHeaders(headers)
444
+ }
445
+
446
+ /**
447
+ * @param {Record<string, string> | null | undefined} headers
448
+ */
449
+ flushHeaders(headers = null) {
450
+ this.#flushPendingHeaders(headers)
451
+ }
452
+
453
+ /**
454
+ * @param {string} key
455
+ * @param {string} value
456
+ */
457
+ #stageHeader(key, value) {
458
+ if (value === undefined || value === null) {
459
+ return
460
+ }
461
+
462
+ const headerName = String(key)
463
+ const headerValue = String(value)
464
+
465
+ this.#pendingHeaders.set(headerName.toLowerCase(), [headerName, headerValue])
466
+ }
467
+
468
+ /**
469
+ * @param {Record<string, string> | null | undefined} headers
470
+ */
471
+ #stageHeaders(headers) {
407
472
  if (!headers) {
408
473
  return
409
474
  }
410
475
 
411
476
  if (headers === TEXT_PLAIN_HEADER) {
412
- this.res.writeHeader('content-type', 'text/plain; charset=utf-8')
477
+ this.#stageHeader('content-type', 'text/plain; charset=utf-8')
413
478
  return
414
479
  }
415
480
 
416
481
  if (headers === JSON_HEADER) {
417
- this.res.writeHeader('content-type', 'application/json; charset=utf-8')
482
+ this.#stageHeader('content-type', 'application/json; charset=utf-8')
418
483
  return
419
484
  }
420
485
 
421
486
  if (headers === OCTET_STREAM_HEADER) {
422
- this.res.writeHeader('content-type', 'application/octet-stream')
487
+ this.#stageHeader('content-type', 'application/octet-stream')
423
488
  return
424
489
  }
425
490
 
426
491
  for (const key in headers) {
427
492
  const value = headers[key]
428
493
 
429
- if (value !== undefined && value !== null) {
430
- this.res.writeHeader(key, value)
431
- }
494
+ this.#stageHeader(key, value)
495
+ }
496
+ }
497
+
498
+ /**
499
+ * @param {Record<string, string> | null | undefined} headers
500
+ */
501
+ #flushPendingHeaders(headers = null) {
502
+ if (!this.res) {
503
+ this.#pendingHeaders.clear()
504
+ return
505
+ }
506
+
507
+ if (headers) {
508
+ this.#stageHeaders(headers)
432
509
  }
510
+
511
+ for (const [, [key, value]] of this.#pendingHeaders) {
512
+ this.res.writeHeader(key, value)
513
+ }
514
+
515
+ this.#pendingHeaders.clear()
433
516
  }
434
517
 
435
518
  /**
@@ -515,7 +598,7 @@ export default class HttpContext {
515
598
  }
516
599
 
517
600
  this.res.writeStatus(this.getStatus(status))
518
- this.setHeaders(headers)
601
+ this.#flushPendingHeaders(headers)
519
602
 
520
603
  if (body != null) {
521
604
  this.res.end(body)
@@ -107,7 +107,7 @@ export default class ResStreamer {
107
107
 
108
108
  res.cork(() => {
109
109
  res.writeStatus(typeof status === 'string' ? status : this.#ctx.getStatus(status))
110
- this.#ctx.setHeaders(headers)
110
+ this.#ctx.flushHeaders(headers)
111
111
  })
112
112
 
113
113
  this.#started = true