Skip to content

Commit

Permalink
migrate to class
Browse files Browse the repository at this point in the history
  • Loading branch information
sirenkovladd committed Oct 28, 2023
1 parent 1e4530d commit 5ce8a03
Show file tree
Hide file tree
Showing 7 changed files with 479 additions and 455 deletions.
2 changes: 1 addition & 1 deletion benchmark/benchmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ suite
new Request(mockReq) // eslint-disable-line no-new
})
.add('Custom Request', function () {
new Request.CustomRequest(mockCustomReq) // eslint-disable-line no-new
new (Request.getCustomRequest(mockCustomReq.Request))(mockCustomReq) // eslint-disable-line no-new
})
.add('Request With Cookies', function () {
new Request(mockReqCookies) // eslint-disable-line no-new
Expand Down
144 changes: 2 additions & 142 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
'use strict'

const assert = require('node:assert')
const Request = require('./lib/request')
const Response = require('./lib/response')

const errorMessage = 'The dispatch function has already been invoked'

const optsValidator = require('./lib/config-validator')
const Chain = require('./lib/chain')
const doInject = require('./lib/do-inject')

function inject (dispatchFunc, options, callback) {
if (typeof callback === 'undefined') {
Expand All @@ -16,143 +13,6 @@ function inject (dispatchFunc, options, callback) {
}
}

function makeRequest (dispatchFunc, server, req, res) {
req.once('error', function (err) {
if (this.destroyed) res.destroy(err)
})

req.once('close', function () {
if (this.destroyed && !this._error) res.destroy()
})

return req.prepare(() => dispatchFunc.call(server, req, res))
}

function doInject (dispatchFunc, options, callback) {
options = (typeof options === 'string' ? { url: options } : options)

if (options.validate !== false) {
assert(typeof dispatchFunc === 'function', 'dispatchFunc should be a function')
const isOptionValid = optsValidator(options)
if (!isOptionValid) {
throw new Error(optsValidator.errors.map(e => e.message))
}
}

const server = options.server || {}

const RequestConstructor = options.Request
? Request.CustomRequest
: Request

// Express.js detection
if (dispatchFunc.request && dispatchFunc.request.app === dispatchFunc) {
Object.setPrototypeOf(Object.getPrototypeOf(dispatchFunc.request), RequestConstructor.prototype)
Object.setPrototypeOf(Object.getPrototypeOf(dispatchFunc.response), Response.prototype)
}

if (typeof callback === 'function') {
const req = new RequestConstructor(options)
const res = new Response(req, callback)

return makeRequest(dispatchFunc, server, req, res)
} else {
return new Promise((resolve, reject) => {
const req = new RequestConstructor(options)
const res = new Response(req, resolve, reject)

makeRequest(dispatchFunc, server, req, res)
})
}
}

function Chain (dispatch, option) {
if (typeof option === 'string') {
this.option = { url: option }
} else {
this.option = Object.assign({}, option)
}

this.dispatch = dispatch
this._hasInvoked = false
this._promise = null

if (this.option.autoStart !== false) {
process.nextTick(() => {
if (!this._hasInvoked) {
this.end()
}
})
}
}

const httpMethods = [
'delete',
'get',
'head',
'options',
'patch',
'post',
'put',
'trace'
]

httpMethods.forEach(method => {
Chain.prototype[method] = function (url) {
if (this._hasInvoked === true || this._promise) {
throw new Error(errorMessage)
}
this.option.url = url
this.option.method = method.toUpperCase()
return this
}
})

const chainMethods = [
'body',
'cookies',
'headers',
'payload',
'query'
]

chainMethods.forEach(method => {
Chain.prototype[method] = function (value) {
if (this._hasInvoked === true || this._promise) {
throw new Error(errorMessage)
}
this.option[method] = value
return this
}
})

Chain.prototype.end = function (callback) {
if (this._hasInvoked === true || this._promise) {
throw new Error(errorMessage)
}
this._hasInvoked = true
if (typeof callback === 'function') {
doInject(this.dispatch, this.option, callback)
} else {
this._promise = doInject(this.dispatch, this.option)
return this._promise
}
}

Object.getOwnPropertyNames(Promise.prototype).forEach(method => {
if (method === 'constructor') return
Chain.prototype[method] = function (...args) {
if (!this._promise) {
if (this._hasInvoked === true) {
throw new Error(errorMessage)
}
this._hasInvoked = true
this._promise = doInject(this.dispatch, this.option)
}
return this._promise[method](...args)
}
})

function isInjection (obj) {
return (
obj instanceof Request ||
Expand Down
107 changes: 107 additions & 0 deletions lib/chain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
'use strict'

const doInject = require('./do-inject')

const errorMessage = 'The dispatch function has already been invoked'

class Chain {
_hasInvoked = false
_promise = null
option
dispatch

constructor (dispatch, option) {
this.dispatch = dispatch
if (typeof option === 'string') {
this.option = { url: option }
} else {
this.option = Object.assign({}, option)
}

if (this.option.autoStart !== false) {
process.nextTick(() => {
if (!this._hasInvoked) {
this.end()
}
})
}
}

/**
* @private
* @param {string} method
* @param {string} url
*/
wrapHttpMethod (method, url) {
if (this._hasInvoked === true || this._promise) {
throw new Error(errorMessage)
}
this.option.url = url
this.option.method = method.toUpperCase()
return this
}

delete (url) { return this.wrapHttpMethod('delete', url) }
get (url) { return this.wrapHttpMethod('get', url) }
head (url) { return this.wrapHttpMethod('head', url) }
options (url) { return this.wrapHttpMethod('options', url) }
patch (url) { return this.wrapHttpMethod('patch', url) }
post (url) { return this.wrapHttpMethod('post', url) }
put (url) { return this.wrapHttpMethod('put', url) }
trace (url) { return this.wrapHttpMethod('trace', url) }

/**
* @private
* @param {string} method
* @param {string} url
*/
wrapChainMethod (method, value) {
if (this._hasInvoked === true || this._promise) {
throw new Error(errorMessage)
}
this.option[method] = value
return this
}

body (url) { return this.wrapChainMethod('body', url) }
cookies (url) { return this.wrapChainMethod('cookies', url) }
headers (url) { return this.wrapChainMethod('headers', url) }
payload (url) { return this.wrapChainMethod('payload', url) }
query (url) { return this.wrapChainMethod('query', url) }

end (callback) {
if (this._hasInvoked === true || this._promise) {
throw new Error(errorMessage)
}
this._hasInvoked = true
if (typeof callback === 'function') {
doInject(this.dispatch, this.option, callback)
} else {
this._promise = doInject(this.dispatch, this.option)
return this._promise
}
}

/**
* @private
* @template {keyof Promise} T
* @param {T} method
* @param {Parameters<Promise[T]>} args
*/
promisify (method, args) {
if (!this._promise) {
if (this._hasInvoked === true) {
throw new Error(errorMessage)
}
this._hasInvoked = true
this._promise = doInject(this.dispatch, this.option)
}
return this._promise[method](...args)
}

then (...args) { return this.promisify('then', args) }
catch (...args) { return this.promisify('catch', args) }
finally (...args) { return this.promisify('finally', args) }
}

module.exports = Chain
63 changes: 63 additions & 0 deletions lib/do-inject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use strict'

const assert = require('node:assert')
const optsValidator = require('./config-validator')
const Request = require('./request')
const Response = require('./response')

function makeRequest (dispatchFunc, server, req, res) {
req.once('error', function (err) {
if (this.destroyed) res.destroy(err)
})

req.once('close', function () {
if (this.destroyed && !this._error) res.destroy()
})

return req.prepare(() => dispatchFunc.call(server, req, res))
}

function doInject (dispatchFunc, options, callback) {
options = (typeof options === 'string' ? { url: options } : options)

if (options.validate !== false) {
assert(typeof dispatchFunc === 'function', 'dispatchFunc should be a function')
const isOptionValid = optsValidator(options)
if (!isOptionValid) {
throw new Error(optsValidator.errors.map(e => e.message))
}
}

const server = options.server || {}

const RequestConstructor = options.Request
? Request.getCustomRequest(options.Request)
: Request

// Express.js detection
if (dispatchFunc.request && dispatchFunc.request.app === dispatchFunc) {
Object.setPrototypeOf(Object.getPrototypeOf(dispatchFunc.request), RequestConstructor.prototype)
Object.setPrototypeOf(Object.getPrototypeOf(dispatchFunc.response), Response.prototype)
}

const req = new RequestConstructor(options)
if (typeof callback === 'function') {
const res = new Response(req, callback)

return makeRequest(dispatchFunc, server, req, res)
} else {
return new Promise((resolve, reject) => {
const res = new Response(req, (err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})

makeRequest(dispatchFunc, server, req, res)
})
}
}

module.exports = doInject
Loading

0 comments on commit 5ce8a03

Please sign in to comment.