Skip to content

Commit

Permalink
Merge pull request #203 from curveball/use-kernel
Browse files Browse the repository at this point in the history
Using @curveball/kernel for all general plumbing
  • Loading branch information
evert authored Sep 3, 2022
2 parents 573f6b3 + 95d6f41 commit 37488e9
Show file tree
Hide file tree
Showing 30 changed files with 965 additions and 2,359 deletions.
15 changes: 15 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
Changelog
=========

0.20.0 (2022-09-03)
------------------

* Most of the internal plumbing has moved to `@curveball/kernel`.
`@curveball/core` still contains all of the Node-specific code, but
this allows curveball to run on Bun.


0.20.0-alpha.0 (2022-09-01)
---------------------------

* Added support for `fetch()`, which lets you make requests in an a
Curveball application using the native `Request` and `Response` objects.


0.19.0 (2022-04-25)
-------------------

Expand Down
1,568 changes: 863 additions & 705 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@curveball/core",
"version": "0.20.0-alpha.0",
"version": "0.20.0",
"description": "Curveball is a framework writting in Typescript for Node.js",
"main": "dist/index.js",
"scripts": {
Expand Down Expand Up @@ -34,7 +34,6 @@
},
"homepage": "https://github.com/curveball/core#readme",
"devDependencies": {
"@types/accepts": "^1.3.5",
"@types/chai": "^4.2.15",
"@types/co-body": "^6.1.0",
"@types/mocha": "^9.0.0",
Expand All @@ -60,8 +59,8 @@
},
"dependencies": {
"@curveball/http-errors": "^0.4.0",
"@curveball/kernel": "^0.20.0-alpha.0",
"@types/ws": "^8.5.3",
"accepts": "^1.3.7",
"raw-body": "^2.4.1",
"ws": "^8.5.0"
},
Expand Down
194 changes: 7 additions & 187 deletions src/application.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import { EventEmitter } from 'events';
import * as http from 'http';
import * as WebSocket from 'ws';
import * as net from 'net';

import { isHttpError } from '@curveball/http-errors';

import { Context } from './context';
import { HeadersInterface, HeadersObject } from './headers';
import MemoryRequest from './memory-request';
import MemoryResponse from './memory-response';
import NotFoundMw from './middleware/not-found';
import {
HttpCallback,
NodeHttpRequest,
Expand All @@ -18,92 +10,20 @@ import {
} from './node/http-utils';
import NodeRequest from './node/request';
import NodeResponse from './node/response';
import { Request as CurveballRequest } from './request';
import { Response as CurveballResponse } from './response';
import {
curveballResponseToFetchResponse,
fetchRequestToCurveballRequest
} from './fetch-util';



// eslint-disable-next-line @typescript-eslint/no-var-requires
const pkg = require('../package.json');

/**
* 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 };

/**
* A function that can act as a middleware.
*/
type MiddlewareFunction = (
ctx: Context,
next: () => Promise<void>
) => Promise<void> | void;

type MiddlewareObject = {
[middlewareCall]: MiddlewareFunction;
};

export type Middleware = MiddlewareFunction | MiddlewareObject;

// Calls a series of middlewares, in order.
export async function invokeMiddlewares(
ctx: Context,
fns: Middleware[]
): Promise<void> {
if (fns.length === 0) {
return;
}

const mw: Middleware = fns[0];
let mwFunc: MiddlewareFunction;
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: Middleware): input is MiddlewareObject {
return (input as MiddlewareObject)[middlewareCall] !== undefined;
}
import {
Application as BaseApplication,
Context,
MemoryResponse,
Middleware,
} from '@curveball/kernel';

export default class Application extends EventEmitter {
export default class Application extends BaseApplication {

middlewares: Middleware[] = [];

private wss: WebSocket.Server | undefined;

/**
* Add a middleware to the application.
*
* Middlewares are called in the order they are added.
*/
use(...middleware: Middleware[]) {
this.middlewares.push(...middleware);
}

/**
* Handles a single request and calls all middleware.
*/
async handle(ctx: Context): Promise<void> {
ctx.response.headers.set('Server', 'curveball/' + pkg.version);
ctx.response.type = 'application/hal+json';
await invokeMiddlewares(ctx, [...this.middlewares, NotFoundMw]);
}


/**
* Starts a HTTP server on the specified port.
*/
Expand All @@ -114,25 +34,6 @@ export default class Application extends EventEmitter {
return server.listen(port, host);
}

/**
* 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: Request): Promise<Response> {

const response = await this.subRequest(
await fetchRequestToCurveballRequest(request, this.origin)
);
return curveballResponseToFetchResponse(response);

}

/**
* Starts a Websocket-only server on the specified port.
*
Expand Down Expand Up @@ -196,52 +97,6 @@ export default class Application extends EventEmitter {
});
}

/**
* Does a sub-request based on a Request object, and returns a Response
* object.
*/
async subRequest(
method: string,
path: string,
headers?: HeadersInterface | HeadersObject,
body?: any
): Promise<CurveballResponse>;
async subRequest(request: CurveballRequest): Promise<CurveballResponse>;
async subRequest(
arg1: string | CurveballRequest,
path?: string,
headers?: HeadersInterface | HeadersObject,
body: any = ''
): Promise<CurveballResponse> {
let request: CurveballRequest;

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: any) {
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;
}

/**
* Creates a Context object based on a node.js request and response object.
*/
Expand All @@ -257,39 +112,4 @@ export default class Application extends EventEmitter {
return context;
}

private _origin?: string;

/**
* 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(): string {

if (this._origin) {
return this._origin;
}

if (process.env.CURVEBALL_ORIGIN) {
return process.env.CURVEBALL_ORIGIN;
}

if (process.env.PUBLIC_URI) {
return new URL(process.env.PUBLIC_URI).origin;
}

const port = process.env.PORT ? +process.env.PORT : 80;
return 'http://localhost' + (port!==80?':' + port : '');

}

set origin(baseUrl: string) {

this._origin = new URL(baseUrl).origin;

}

}
Loading

0 comments on commit 37488e9

Please sign in to comment.