UNPKG

@architect/functions

Version:

Runtime utility library for Functional Web Apps (FWAs) built with Architect (https://arc.codes)

150 lines (131 loc) 4.54 kB
let read = require('./session/read') let write = require('./session/write') let bodyParser = require('./helpers/body-parser') let interpolate = require('./helpers/params') let url = require('./helpers/url') let responseFormatter = require('./_res-fmt') // Unified async / callback HTTP handler function httpHandler (isAsync, ...fns) { // Ensure we've been passed only functions fns.forEach(fn => { if (typeof fn !== 'function') throw TypeError(fn + ' not a function') }) // Override arc.http using callbacks by passing async functions isAsync = isAsync || fns[0].constructor.name === 'AsyncFunction' if (isAsync) { // Return an AWS Lambda async function signature return async function lambda (request, context) { let params let first = true for (let fn of fns) { if (fn.constructor.name !== 'AsyncFunction') { throw TypeError('All arc.http middleware functions must be async') } // Only parse the request obj of the first function if (first) { first = false let session = await read(request) let req = interpolate(Object.assign(request, { session })) req.body = bodyParser(req) request = req } // Run the function let result = await fn(request, context) let isRequest = result?.httpMethod // Function returned a modified request, continuing... if (isRequest) request = result else { if (result) { params = result // Got a response, finishing... break } // Did not get a result from, continuing... } } let isHTTPv2 = request?.version === '2.0' // Finished combined function! if (!params && !isHTTPv2) { throw new Error(`Finished all functions without returning a response.`) } return asyncResponse(request, params) } } else { // Return an AWS Lambda continuation passing function signature return function lambda (request, context, callback) { // Cache the functions let cache = fns.slice() // read the session read(request, function _read (err, session) { // Fail loudly if the session isn't set up correctly if (err) throw err // Construct a response function let req = interpolate(Object.assign(request, { session })) req.body = bodyParser(req) let res = response.bind({}, req, callback) // Loop through middleware function fail () { throw Error('next() called from last function') } (function iterator (fn) { if (fn.constructor.name !== 'Function') { throw TypeError('All arc.http middleware functions must be continuation passing (callback)') } let next = iterator.bind({}, cache.shift() || fail) fn.call({}, req, res, next) })(cache.shift()) }) } } } let http = httpHandler.bind({}, false) http.async = httpHandler.bind({}, true) // Helpers http.helpers = { bodyParser, interpolate, url } // Session http.session = { read, write } module.exports = http /** * Response formatter & session helpers */ async function asyncResponse (req, params) { // Format the response let res = responseFormatter(req, params) // Legacy 'cookie' parameter, used after direct session writes if (params && params.cookie) { res.headers['set-cookie'] = params.cookie } // Save the passed session if (params?.session) { // Tag the session data with _idx, secret, ttl, from req.session let { _idx, _secret, _ttl } = req.session let session = { _idx, _secret, _ttl, ...params.session } let cookie = await write(session) res.headers['set-cookie'] = cookie } return res } function response (req, callback, params) { // Format the response let res = responseFormatter(req, params) // Legacy 'cookie' parameter, used after direct session writes if (params?.cookie) { res.headers['set-cookie'] = params.cookie } // Save the passed session if (params?.session) { // Tag the session data with _idx, secret, ttl, from req.session let { _idx, _secret, _ttl } = req.session let session = { _idx, _secret, _ttl, ...params.session } write(session, function _write (err, cookie) { if (err) callback(err) else { res.headers['set-cookie'] = cookie callback(null, res) } }) } else { // Just pass it through callback(null, res) } }