@curveball/kernel
Version:
Curveball is a framework writting in Typescript for Node.js
123 lines • 4.04 kB
JavaScript
import { EventEmitter } from 'node:events';
import { isHttpError } from '@curveball/http-errors';
import { Context } from './context.js';
import MemoryRequest from './memory-request.js';
import MemoryResponse from './memory-response.js';
import NotFoundMw from './middleware/not-found.js';
import { curveballResponseToFetchResponse, fetchRequestToCurveballRequest } from './fetch-util.js';
import { getGlobalOrigin } from './global-origin.js';
/**
* Package version
*
* This line gets automatically replaced during the build phase
*/
export const VERSION = 'Curveball/1.0.0';
/**
* The middleware-call Symbol is a special symbol that might exist as a
* property on an object.
*
* If it exists, the object can be used as a middleware.
*/
const middlewareCall = Symbol('middleware-call');
export { middlewareCall };
// Calls a series of middlewares, in order.
export async function invokeMiddlewares(ctx, fns) {
if (fns.length === 0) {
return;
}
const mw = fns[0];
let mwFunc;
if (isMiddlewareObject(mw)) {
mwFunc = mw[middlewareCall].bind(fns[0]);
}
else {
mwFunc = mw;
}
return mwFunc(ctx, async () => {
await invokeMiddlewares(ctx, fns.slice(1));
});
}
function isMiddlewareObject(input) {
return input[middlewareCall] !== undefined;
}
export default class Application extends EventEmitter {
middlewares = [];
/**
* Add a middleware to the application.
*
* Middlewares are called in the order they are added.
*/
use(...middleware) {
this.middlewares.push(...middleware);
}
/**
* Handles a single request and calls all middleware.
*/
async handle(ctx) {
ctx.response.headers.set('Server', VERSION);
ctx.response.type = 'application/hal+json';
await invokeMiddlewares(ctx, [...this.middlewares, NotFoundMw]);
}
/**
* Executes a request on the server using the standard browser Request and
* Response objects from the fetch() standard.
*
* Node will probably provide these out of the box in Node 18. If you're on
* an older version, you'll need a polyfill.
*
* A use-case for this is allowing test frameworks to make fetch-like
* requests without actually having to go over the network.
*/
async fetch(request) {
const response = await this.subRequest(await fetchRequestToCurveballRequest(request, this.origin));
return curveballResponseToFetchResponse(response);
}
async subRequest(arg1, path, headers, body = '') {
let request;
if (typeof arg1 === 'string') {
request = new MemoryRequest(arg1, path, this.origin, headers, body);
}
else {
request = arg1;
}
const context = new Context(request, new MemoryResponse(this.origin));
try {
await this.handle(context);
}
catch (err) {
console.error(err);
if (this.listenerCount('error')) {
this.emit('error', err);
}
if (isHttpError(err)) {
context.response.status = err.httpStatus;
}
else {
context.response.status = 500;
}
context.response.body =
'Uncaught exception. No middleware was defined to handle it. We got the following HTTP status: ' +
context.response.status;
}
return context.response;
}
_origin;
/**
* The public base url of the application.
*
* This can be auto-detected, but will often be wrong when your server is
* running behind a reverse proxy or load balancer.
*
* To provide this, set the process.env.PUBLIC_URI property.
*/
get origin() {
if (this._origin) {
return this._origin;
}
return getGlobalOrigin();
}
set origin(baseUrl) {
this._origin = new URL(baseUrl).origin;
}
}
//# sourceMappingURL=application.js.map