koa 3.0.0-alpha.0 → 3.0.0-alpha.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/Readme.md +1 -1
- package/lib/application.js +14 -14
- package/lib/context.js +3 -3
- package/lib/only.js +9 -0
- package/lib/request.js +7 -8
- package/lib/response.js +68 -14
- package/lib/search-params.js +33 -0
- package/package.json +16 -22
package/Readme.md
CHANGED
package/lib/application.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
'use strict'
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -17,7 +16,7 @@ const Emitter = require('events')
|
|
|
17
16
|
const util = require('util')
|
|
18
17
|
const Stream = require('stream')
|
|
19
18
|
const http = require('http')
|
|
20
|
-
const only = require('only')
|
|
19
|
+
const only = require('./only.js')
|
|
21
20
|
const { HttpError } = require('http-errors')
|
|
22
21
|
|
|
23
22
|
/** @typedef {typeof import ('./context') & {
|
|
@@ -55,6 +54,8 @@ module.exports = class Application extends Emitter {
|
|
|
55
54
|
* @param {number} [options.subdomainOffset] Subdomain offset
|
|
56
55
|
* @param {string} [options.proxyIpHeader] Proxy IP header, defaults to X-Forwarded-For
|
|
57
56
|
* @param {number} [options.maxIpsCount] Max IPs read from proxy IP header, default to 0 (means infinity)
|
|
57
|
+
* @param {function} [options.compose] Function to handle middleware composition
|
|
58
|
+
* @param {boolean} [options.asyncLocalStorage] Max IPs read from proxy IP header, default to 0 (means infinity)
|
|
58
59
|
*
|
|
59
60
|
*/
|
|
60
61
|
|
|
@@ -81,7 +82,6 @@ module.exports = class Application extends Emitter {
|
|
|
81
82
|
const { AsyncLocalStorage } = require('async_hooks')
|
|
82
83
|
assert(AsyncLocalStorage, 'Requires node 12.17.0 or higher to enable asyncLocalStorage')
|
|
83
84
|
this.ctxStorage = new AsyncLocalStorage()
|
|
84
|
-
this.use(this.createAsyncCtxStorageMiddleware())
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -160,7 +160,12 @@ module.exports = class Application extends Emitter {
|
|
|
160
160
|
|
|
161
161
|
const handleRequest = (req, res) => {
|
|
162
162
|
const ctx = this.createContext(req, res)
|
|
163
|
-
|
|
163
|
+
if (!this.ctxStorage) {
|
|
164
|
+
return this.handleRequest(ctx, fn)
|
|
165
|
+
}
|
|
166
|
+
return this.ctxStorage.run(ctx, async () => {
|
|
167
|
+
return await this.handleRequest(ctx, fn)
|
|
168
|
+
})
|
|
164
169
|
}
|
|
165
170
|
|
|
166
171
|
return handleRequest
|
|
@@ -243,15 +248,6 @@ module.exports = class Application extends Emitter {
|
|
|
243
248
|
static get default () {
|
|
244
249
|
return Application
|
|
245
250
|
}
|
|
246
|
-
|
|
247
|
-
createAsyncCtxStorageMiddleware () {
|
|
248
|
-
const app = this
|
|
249
|
-
return async function asyncCtxStorage (ctx, next) {
|
|
250
|
-
await app.ctxStorage.run(ctx, async () => {
|
|
251
|
-
return await next()
|
|
252
|
-
})
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
251
|
}
|
|
256
252
|
|
|
257
253
|
/**
|
|
@@ -284,7 +280,7 @@ function respond (ctx) {
|
|
|
284
280
|
}
|
|
285
281
|
|
|
286
282
|
// status body
|
|
287
|
-
if (body
|
|
283
|
+
if (body === null || body === undefined) {
|
|
288
284
|
if (ctx.response._explicitNullBody) {
|
|
289
285
|
ctx.response.remove('Content-Type')
|
|
290
286
|
ctx.response.remove('Transfer-Encoding')
|
|
@@ -304,9 +300,13 @@ function respond (ctx) {
|
|
|
304
300
|
}
|
|
305
301
|
|
|
306
302
|
// responses
|
|
303
|
+
|
|
307
304
|
if (Buffer.isBuffer(body)) return res.end(body)
|
|
308
305
|
if (typeof body === 'string') return res.end(body)
|
|
309
306
|
if (body instanceof Stream) return body.pipe(res)
|
|
307
|
+
if (body instanceof Blob) return Stream.Readable.from(body.stream()).pipe(res)
|
|
308
|
+
if (body instanceof ReadableStream) return Stream.Readable.from(body).pipe(res)
|
|
309
|
+
if (body instanceof Response) return Stream.Readable.from(body?.body || '').pipe(res)
|
|
310
310
|
|
|
311
311
|
// body: json
|
|
312
312
|
body = JSON.stringify(body)
|
package/lib/context.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
'use strict'
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -155,10 +154,10 @@ const proto = module.exports = {
|
|
|
155
154
|
if (err.code === 'ENOENT') statusCode = 404
|
|
156
155
|
|
|
157
156
|
// default to 500
|
|
158
|
-
if (typeof statusCode !== 'number' || !statuses[statusCode]) statusCode = 500
|
|
157
|
+
if (typeof statusCode !== 'number' || !statuses.message[statusCode]) statusCode = 500
|
|
159
158
|
|
|
160
159
|
// respond
|
|
161
|
-
const code = statuses[statusCode]
|
|
160
|
+
const code = statuses.message[statusCode]
|
|
162
161
|
const msg = err.expose ? err.message : code
|
|
163
162
|
this.status = err.status = statusCode
|
|
164
163
|
this.length = Buffer.byteLength(msg)
|
|
@@ -205,6 +204,7 @@ delegate(proto, 'response')
|
|
|
205
204
|
.method('set')
|
|
206
205
|
.method('append')
|
|
207
206
|
.method('flushHeaders')
|
|
207
|
+
.method('back')
|
|
208
208
|
.access('status')
|
|
209
209
|
.access('message')
|
|
210
210
|
.access('body')
|
package/lib/only.js
ADDED
package/lib/request.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
'use strict'
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -11,10 +10,11 @@ const accepts = require('accepts')
|
|
|
11
10
|
const contentType = require('content-type')
|
|
12
11
|
const stringify = require('url').format
|
|
13
12
|
const parse = require('parseurl')
|
|
14
|
-
const
|
|
13
|
+
const sp = require('./search-params.js')
|
|
14
|
+
|
|
15
15
|
const typeis = require('type-is')
|
|
16
16
|
const fresh = require('fresh')
|
|
17
|
-
const only = require('only')
|
|
17
|
+
const only = require('./only.js')
|
|
18
18
|
const util = require('util')
|
|
19
19
|
|
|
20
20
|
const IP = Symbol('context#ip')
|
|
@@ -172,7 +172,7 @@ module.exports = {
|
|
|
172
172
|
get query () {
|
|
173
173
|
const str = this.querystring
|
|
174
174
|
const c = this._querycache = this._querycache || {}
|
|
175
|
-
return c[str] || (c[str] =
|
|
175
|
+
return c[str] || (c[str] = sp.parse(str))
|
|
176
176
|
},
|
|
177
177
|
|
|
178
178
|
/**
|
|
@@ -183,7 +183,7 @@ module.exports = {
|
|
|
183
183
|
*/
|
|
184
184
|
|
|
185
185
|
set query (obj) {
|
|
186
|
-
this.querystring =
|
|
186
|
+
this.querystring = sp.stringify(obj)
|
|
187
187
|
},
|
|
188
188
|
|
|
189
189
|
/**
|
|
@@ -211,7 +211,6 @@ module.exports = {
|
|
|
211
211
|
|
|
212
212
|
url.search = str
|
|
213
213
|
url.path = null
|
|
214
|
-
|
|
215
214
|
this.url = stringify(url)
|
|
216
215
|
},
|
|
217
216
|
|
|
@@ -376,7 +375,7 @@ module.exports = {
|
|
|
376
375
|
/**
|
|
377
376
|
* Return parsed Content-Length when present.
|
|
378
377
|
*
|
|
379
|
-
* @return {Number}
|
|
378
|
+
* @return {Number|void}
|
|
380
379
|
* @api public
|
|
381
380
|
*/
|
|
382
381
|
|
|
@@ -503,7 +502,7 @@ module.exports = {
|
|
|
503
502
|
/**
|
|
504
503
|
* Set accept object.
|
|
505
504
|
*
|
|
506
|
-
* @param {Object}
|
|
505
|
+
* @param {Object} obj
|
|
507
506
|
* @api private
|
|
508
507
|
*/
|
|
509
508
|
|
package/lib/response.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
'use strict'
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -15,10 +14,11 @@ const destroy = require('destroy')
|
|
|
15
14
|
const assert = require('assert')
|
|
16
15
|
const extname = require('path').extname
|
|
17
16
|
const vary = require('vary')
|
|
18
|
-
const only = require('only')
|
|
17
|
+
const only = require('./only.js')
|
|
19
18
|
const util = require('util')
|
|
20
19
|
const encodeUrl = require('encodeurl')
|
|
21
20
|
const Stream = require('stream')
|
|
21
|
+
const deprecate = require('depd')('koa')
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Prototype.
|
|
@@ -87,7 +87,7 @@ module.exports = {
|
|
|
87
87
|
assert(code >= 100 && code <= 999, `invalid status code: ${code}`)
|
|
88
88
|
this._explicitStatus = true
|
|
89
89
|
this.res.statusCode = code
|
|
90
|
-
if (this.req.httpVersionMajor < 2) this.res.statusMessage = statuses[code]
|
|
90
|
+
if (this.req.httpVersionMajor < 2) this.res.statusMessage = statuses.message[code]
|
|
91
91
|
if (this.body && statuses.empty[code]) this.body = null
|
|
92
92
|
},
|
|
93
93
|
|
|
@@ -99,7 +99,7 @@ module.exports = {
|
|
|
99
99
|
*/
|
|
100
100
|
|
|
101
101
|
get message () {
|
|
102
|
-
return this.res.statusMessage || statuses[this.status]
|
|
102
|
+
return this.res.statusMessage || statuses.message[this.status]
|
|
103
103
|
},
|
|
104
104
|
|
|
105
105
|
/**
|
|
@@ -127,15 +127,15 @@ module.exports = {
|
|
|
127
127
|
/**
|
|
128
128
|
* Set response body.
|
|
129
129
|
*
|
|
130
|
-
* @param {String|Buffer|Object|Stream} val
|
|
130
|
+
* @param {String|Buffer|Object|Stream|ReadableStream|Blob|Response} val
|
|
131
131
|
* @api public
|
|
132
132
|
*/
|
|
133
133
|
|
|
134
134
|
set body (val) {
|
|
135
135
|
const original = this._body
|
|
136
136
|
this._body = val
|
|
137
|
-
|
|
138
137
|
// no content
|
|
138
|
+
|
|
139
139
|
if (val == null) {
|
|
140
140
|
if (!statuses.empty[this.status]) {
|
|
141
141
|
if (this.type === 'application/json') {
|
|
@@ -184,6 +184,31 @@ module.exports = {
|
|
|
184
184
|
return
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
// ReadableStream
|
|
188
|
+
if (val instanceof ReadableStream) {
|
|
189
|
+
if (setType) this.type = 'bin'
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// blob
|
|
194
|
+
if (val instanceof Blob) {
|
|
195
|
+
if (setType) this.type = 'bin'
|
|
196
|
+
this.length = val.size
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Response
|
|
201
|
+
if (val instanceof Response) {
|
|
202
|
+
this.status = val.status
|
|
203
|
+
if (setType) this.type = 'bin'
|
|
204
|
+
const headers = val.headers
|
|
205
|
+
for (const key of headers.keys()) {
|
|
206
|
+
this.set(key, headers.get(key))
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
|
|
187
212
|
// json
|
|
188
213
|
this.remove('Content-Length')
|
|
189
214
|
this.type = 'json'
|
|
@@ -235,7 +260,7 @@ module.exports = {
|
|
|
235
260
|
/**
|
|
236
261
|
* Vary on `field`.
|
|
237
262
|
*
|
|
238
|
-
* @param {String} field
|
|
263
|
+
* @param {String|String[]} field
|
|
239
264
|
* @api public
|
|
240
265
|
*/
|
|
241
266
|
|
|
@@ -266,7 +291,15 @@ module.exports = {
|
|
|
266
291
|
|
|
267
292
|
redirect (url, alt) {
|
|
268
293
|
// location
|
|
269
|
-
if (url === 'back')
|
|
294
|
+
if (url === 'back') {
|
|
295
|
+
deprecate('Special-cased string "back" through redirect will be removed in v3, ' +
|
|
296
|
+
'consider migrating usage to ctx.back() instead.')
|
|
297
|
+
url = this.ctx.get('Referrer') || alt || '/'
|
|
298
|
+
}
|
|
299
|
+
if (/^https?:\/\//i.test(url)) {
|
|
300
|
+
// formatting url again avoid security escapes
|
|
301
|
+
url = new URL(url).toString()
|
|
302
|
+
}
|
|
270
303
|
this.set('Location', encodeUrl(url))
|
|
271
304
|
|
|
272
305
|
// status
|
|
@@ -285,10 +318,31 @@ module.exports = {
|
|
|
285
318
|
this.body = `Redirecting to ${url}.`
|
|
286
319
|
},
|
|
287
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Perform a special-cased "back" to provide Referrer support.
|
|
323
|
+
* When Referrer is not present, `alt` or "/" is used.
|
|
324
|
+
*
|
|
325
|
+
* Examples:
|
|
326
|
+
*
|
|
327
|
+
* ctx.back()
|
|
328
|
+
* ctx.back('/index.html')
|
|
329
|
+
*
|
|
330
|
+
* @param {String} [alt]
|
|
331
|
+
* @api public
|
|
332
|
+
*/
|
|
333
|
+
|
|
334
|
+
back (alt) {
|
|
335
|
+
const url = this.ctx.get('Referrer') || alt || '/'
|
|
336
|
+
this.redirect(url)
|
|
337
|
+
},
|
|
338
|
+
|
|
288
339
|
/**
|
|
289
340
|
* Set Content-Disposition header to "attachment" with optional `filename`.
|
|
290
341
|
*
|
|
291
|
-
* @param {String} filename
|
|
342
|
+
* @param {String} [filename]
|
|
343
|
+
* @param {object} [options]
|
|
344
|
+
* @param {string} [options.type=attachment]
|
|
345
|
+
* @param {string|boolean} [options.fallback=true]
|
|
292
346
|
* @api public
|
|
293
347
|
*/
|
|
294
348
|
|
|
@@ -328,7 +382,7 @@ module.exports = {
|
|
|
328
382
|
* this.response.lastModified = new Date();
|
|
329
383
|
* this.response.lastModified = '2013-09-13';
|
|
330
384
|
*
|
|
331
|
-
* @param {String|Date}
|
|
385
|
+
* @param {String|Date} val
|
|
332
386
|
* @api public
|
|
333
387
|
*/
|
|
334
388
|
|
|
@@ -357,7 +411,7 @@ module.exports = {
|
|
|
357
411
|
* this.response.etag = '"md5hashsum"';
|
|
358
412
|
* this.response.etag = 'W/"123456789"';
|
|
359
413
|
*
|
|
360
|
-
* @param {String}
|
|
414
|
+
* @param {String} val
|
|
361
415
|
* @api public
|
|
362
416
|
*/
|
|
363
417
|
|
|
@@ -460,7 +514,7 @@ module.exports = {
|
|
|
460
514
|
* this.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
|
|
461
515
|
*
|
|
462
516
|
* @param {String|Object|Array} field
|
|
463
|
-
* @param {
|
|
517
|
+
* @param {*} val
|
|
464
518
|
* @api public
|
|
465
519
|
*/
|
|
466
520
|
|
|
@@ -490,7 +544,7 @@ module.exports = {
|
|
|
490
544
|
* ```
|
|
491
545
|
*
|
|
492
546
|
* @param {String} field
|
|
493
|
-
* @param {
|
|
547
|
+
* @param {*} val
|
|
494
548
|
* @api public
|
|
495
549
|
*/
|
|
496
550
|
|
|
@@ -509,7 +563,7 @@ module.exports = {
|
|
|
509
563
|
/**
|
|
510
564
|
* Remove header `field`.
|
|
511
565
|
*
|
|
512
|
-
* @param {String}
|
|
566
|
+
* @param {String} field
|
|
513
567
|
* @api public
|
|
514
568
|
*/
|
|
515
569
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const URLSearchParams = require('url').URLSearchParams
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
stringify: (obj) => {
|
|
5
|
+
const searchParams = new URLSearchParams()
|
|
6
|
+
const addKey = (k, v, params) => {
|
|
7
|
+
const val = typeof v === 'string' || typeof v === 'number' ? v : ''
|
|
8
|
+
params.append(k, val)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
12
|
+
if (Array.isArray(value)) {
|
|
13
|
+
const lgth = value.length
|
|
14
|
+
for (let i = 0; i < lgth; i++) {
|
|
15
|
+
addKey(key, value[i], searchParams)
|
|
16
|
+
}
|
|
17
|
+
} else {
|
|
18
|
+
addKey(key, value, searchParams)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return searchParams.toString()
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
parse: (str) => {
|
|
25
|
+
const searchParams = new URLSearchParams(str)
|
|
26
|
+
const obj = {}
|
|
27
|
+
for (const key of searchParams.keys()) {
|
|
28
|
+
const values = searchParams.getAll(key)
|
|
29
|
+
obj[key] = values.length <= 1 ? values[0] : values
|
|
30
|
+
}
|
|
31
|
+
return obj
|
|
32
|
+
}
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koa",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"tag": "experimental"
|
|
6
6
|
},
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
11
|
"require": "./lib/application.js",
|
|
12
|
-
"import": "./dist/koa.mjs"
|
|
12
|
+
"import": "./dist/koa.mjs",
|
|
13
|
+
"default": "./dist/koa.mjs"
|
|
13
14
|
},
|
|
14
15
|
"./*": "./*.js",
|
|
15
16
|
"./*.js": "./*.js",
|
|
@@ -17,8 +18,10 @@
|
|
|
17
18
|
"./package.json": "./package.json"
|
|
18
19
|
},
|
|
19
20
|
"scripts": {
|
|
20
|
-
"test": "
|
|
21
|
-
"
|
|
21
|
+
"test": "node --test",
|
|
22
|
+
"test:coverage": "node --test --experimental-test-coverage",
|
|
23
|
+
"lint": "standard",
|
|
24
|
+
"lint:pretty": "standard | snazzy",
|
|
22
25
|
"authors": "git log --format='%aN <%aE>' | sort -u > AUTHORS",
|
|
23
26
|
"build": "gen-esm-wrapper . ./dist/koa.mjs",
|
|
24
27
|
"prepare": "npm run build"
|
|
@@ -39,42 +42,33 @@
|
|
|
39
42
|
"cache-content-type": "^1.0.0",
|
|
40
43
|
"content-disposition": "~0.5.2",
|
|
41
44
|
"content-type": "^1.0.4",
|
|
42
|
-
"cookies": "~0.
|
|
45
|
+
"cookies": "~0.9.1",
|
|
43
46
|
"debug": "^4.3.2",
|
|
44
47
|
"delegates": "^1.0.0",
|
|
45
48
|
"destroy": "^1.0.4",
|
|
46
|
-
"encodeurl": "^
|
|
49
|
+
"encodeurl": "^2.0.0",
|
|
47
50
|
"escape-html": "^1.0.3",
|
|
48
51
|
"fresh": "~0.5.2",
|
|
49
52
|
"http-assert": "^1.3.0",
|
|
50
|
-
"http-errors": "^
|
|
53
|
+
"http-errors": "^2.0.0",
|
|
51
54
|
"koa-compose": "^4.1.0",
|
|
52
55
|
"on-finished": "^2.3.0",
|
|
53
|
-
"only": "~0.0.2",
|
|
54
56
|
"parseurl": "^1.3.2",
|
|
55
|
-
"statuses": "^
|
|
57
|
+
"statuses": "^2.0.1",
|
|
56
58
|
"type-is": "^1.6.16",
|
|
57
59
|
"vary": "^1.1.2"
|
|
58
60
|
},
|
|
59
61
|
"devDependencies": {
|
|
60
|
-
"eslint": "^7.32.0",
|
|
61
|
-
"eslint-config-standard": "^16.0.3",
|
|
62
|
-
"eslint-plugin-import": "^2.18.2",
|
|
63
|
-
"eslint-plugin-node": "^11.1.0",
|
|
64
|
-
"eslint-plugin-promise": "^5.1.0",
|
|
65
|
-
"eslint-plugin-standard": "^5.0.0",
|
|
66
62
|
"gen-esm-wrapper": "^1.0.6",
|
|
67
|
-
"
|
|
68
|
-
"
|
|
63
|
+
"snazzy": "^9.0.0",
|
|
64
|
+
"standard": "^17.1.0",
|
|
65
|
+
"supertest": "^7.0.0"
|
|
69
66
|
},
|
|
70
67
|
"engines": {
|
|
71
|
-
"node": ">=
|
|
68
|
+
"node": ">= 18"
|
|
72
69
|
},
|
|
73
70
|
"files": [
|
|
74
71
|
"dist",
|
|
75
72
|
"lib"
|
|
76
|
-
]
|
|
77
|
-
"jest": {
|
|
78
|
-
"testEnvironment": "node"
|
|
79
|
-
}
|
|
73
|
+
]
|
|
80
74
|
}
|