-
Notifications
You must be signed in to change notification settings - Fork 0
/
poodle.ts
137 lines (117 loc) · 4.25 KB
/
poodle.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import {
Server,
ServerResponse,
createServer,
} from 'http';
import {
DEFAULT_STATUS_CODE_UNCAUGHT,
RouteHandlers,
getHandlerForErr,
getHandlerForReq,
} from './lib/handle.js';
import {
RequestContextDefault,
RequestContextGetter,
ServerRequest,
} from './lib/request.js';
export {
RequestHandler,
RouteHandlers,
} from './lib/handle.js';
export {
RequestContextDefault,
RequestContextGetter,
ServerRequest,
} from './lib/request.js';
export {
RouteType,
initializeRoutes,
} from './lib/route.js';
export {
ResponseOptions,
ResponseWriter,
file,
html,
json,
status,
text,
} from './lib/responses.js';
export interface ServeOptions<TRequestContext = RequestContextDefault> {
/** Gets the request context object which will be passed to request handlers. */
getRequestContext?: RequestContextGetter<TRequestContext>;
/** A hook which will be called whenever a request handler throws an error. */
onErrorHandling?: (context: TRequestContext, error: Error) => void;
/** A hook which will be called whenever a response writer throws an error. */
onErrorWriting?: (context: TRequestContext, error: Error) => void;
/** A hook which will be called whenever a response is fully sent to the user. */
onResponded?: (context: TRequestContext, response: ServerResponse) => void;
/** The http server instance to add request-handling to. */
server?: Server;
}
const getDefaultRequestContext: RequestContextGetter<RequestContextDefault>
= (request) => ({ request });
/** Adds request handling to an http server for the given poodle route-handlers. */
export function serve(
routes: RouteHandlers<RequestContextDefault>,
options?: Omit<ServeOptions<RequestContextDefault>, 'getRequestContext'>,
): Server;
/**
* Adds request handling to an http server for the given poodle route-handlers with a
* non-default request context type.
* The `getRequestContext` argument is therefore required so that the correct request
* context type can be known.
*/
export function serve<TRequestContext>(
routes: RouteHandlers<TRequestContext>,
options: ServeOptions<TRequestContext> & {
// make this non-optional when TRequestContext is non-default
getRequestContext: RequestContextGetter<TRequestContext>;
},
): Server;
export function serve<
TRequestContext = RequestContextDefault
>(routes: RouteHandlers<TRequestContext>, {
// @ts-expect-error -- due to type signature of overloads, we will only use this default
// if TRequestContext _is_ the default request context type
getRequestContext = getDefaultRequestContext,
onErrorHandling = ( ) => null,
onErrorWriting = ( ) => null,
onResponded = ( ) => null,
server = createServer( ),
}: ServeOptions<TRequestContext> = { }): Server {
server.on('request', function handleRequest(req: ServerRequest, res: ServerResponse) {
const context = getRequestContext(req);
Promise.resolve( ).then(function handleRequest( ) {
// get the appropriate request handler
const handler = getHandlerForReq<TRequestContext>(routes, req);
// and call it to get a response writer
return handler(context);
}).catch(function handleHandlerError(err) {
// if the request handler fails for some reason, first notify
onErrorHandling(context, err);
// then get an error handler and call it to get a new reponse writer
const errorHandler = getHandlerForErr(routes, req);
return errorHandler(context);
}).then(function writeResponse(writer) {
// once we have a response writer, validate it
if (typeof writer !== 'function') throw new TypeError('Invalid response');
// then write the response
return writer(res);
}).catch(function handleWriteError(err) {
// if the response writer fails for some reason, first notify
onErrorWriting(context, err);
// then attempt to respond with a plain 500 if possible
// this will fail if the headers have already been written by the original response
// writer, but at that point there is nothing left to do but die
res.writeHead(DEFAULT_STATUS_CODE_UNCAUGHT, {
'Content-Type': 'text/plain',
'Content-Length': Buffer.byteLength('Internal Server Error'),
});
res.end('Internal Server Error');
}).finally(function handleCompletion( ) {
// once we've finished dealing with a request, notify
onResponded(context, res);
});
});
return server;
}