Comparing version 0.1.0-alpha.5 to 0.1.0-alpha.6
@@ -79,7 +79,10 @@ import { createSecureServer, createServer, } from 'node:http2'; | ||
if (!this.server) { | ||
if (this.invokedOptions.ssl && | ||
((this.invokedOptions.ssl.certificatePath || this.invokedOptions.ssl.certificate) && | ||
if (!this.invokedOptions.ssl) { | ||
this.server = createServer(this.mainHandler); | ||
} | ||
else if (this.invokedOptions.ssl && | ||
(((this.invokedOptions.ssl.certificatePath || this.invokedOptions.ssl.certificate) && | ||
(this.invokedOptions.ssl.key || this.invokedOptions.ssl.keyPath)) || | ||
((this.invokedOptions.ssl.pfx || this.invokedOptions.ssl.pfxPath) && | ||
this.invokedOptions.ssl.passphrase)) { | ||
((this.invokedOptions.ssl.pfx || this.invokedOptions.ssl.pfxPath) && | ||
this.invokedOptions.ssl.passphrase))) { | ||
if (this.invokedOptions.ssl.pfx || this.invokedOptions.ssl.pfxPath) { | ||
@@ -107,3 +110,3 @@ this.server = createSecureServer({ | ||
else { | ||
this.server = createServer(this.mainHandler); | ||
throw Error('An unknown error occurred; please review the creation of the application'); | ||
} | ||
@@ -110,0 +113,0 @@ } |
@@ -14,3 +14,2 @@ /// <reference types="node" /> | ||
readonly headers: Readonly<IncomingHttpHeaders>; | ||
readonly rawHeaders: Readonly<string[]>; | ||
readonly payload: any; | ||
@@ -25,7 +24,9 @@ readonly rawRequest: Http2ServerRequest | IncomingMessage; | ||
readonly rawRequest: IncomingMessage; | ||
readonly httpVersion: '1.1'; | ||
} | ||
interface Http2Request extends BaseRequest { | ||
readonly rawRequest: Http2ServerRequest; | ||
readonly httpVersion: '2.0'; | ||
} | ||
type Request = Http1Request | Http2Request; | ||
export type { Request, }; |
@@ -14,3 +14,2 @@ import { constants } from 'node:http2'; | ||
const headers = Object.freeze(Object.assign({}, rawRequest.headers)); | ||
const rawHeaders = Object.freeze(rawRequest.rawHeaders.slice()); | ||
Object.defineProperty(req, 'params', { get() { return params; } }); | ||
@@ -22,3 +21,2 @@ Object.defineProperty(req, 'query', { get() { return query; } }); | ||
Object.defineProperty(req, 'hash', { get() { return url.hash; } }); | ||
Object.defineProperty(req, 'rawHeaders', { get() { rawHeaders; } }); | ||
return req; | ||
@@ -25,0 +23,0 @@ } |
@@ -21,25 +21,28 @@ /// <reference types="node" /> | ||
status(statusCode: number): void; | ||
/** getter for the current eventSource value (default: false) */ | ||
asEventSource(): boolean; | ||
/** setter for the eventSource value */ | ||
asEventSource(bool: boolean): void; | ||
/** pass-thru for http2Stream.write */ | ||
write(data: string | Buffer): void; | ||
/** pass-thru for http2Stream.end */ | ||
end(data?: string | Buffer): void; | ||
/** the simplest way to respond with or without data */ | ||
/** DEFAULT MODE ONLY: the simplest way to respond with or without data */ | ||
send(data?: any): void; | ||
/** ideal for object data to be consumed in a browser */ | ||
json(data: object): void; | ||
/** DEFAULT MODE ONLY: pass-thru for http2Stream.end */ | ||
end(data?: string | Buffer): void; | ||
/** EVENT STREAM MODE ONLY: when the response is set as an event stream, this sends the event; errors otherwise */ | ||
sendEvent(data?: object | string): void; | ||
/** ideal for needing to respond with files */ | ||
stream(stream: Readable, options?: SendStreamOptions): Promise<void>; | ||
stream(stream: Readable): void; | ||
} | ||
interface Http1Response extends BaseResponse { | ||
httpVersion: '1.1'; | ||
readonly httpVersion: '1.1'; | ||
readonly rawResponse: ServerResponse; | ||
} | ||
interface Http2Response extends BaseResponse { | ||
httpVersion: '2.0'; | ||
readonly httpVersion: '2.0'; | ||
readonly rawResponse: Http2ServerResponse; | ||
} | ||
type Response = Http1Response | Http2Response; | ||
interface SendStreamOptions { | ||
noCache: boolean; | ||
} | ||
export type { Response, }; |
@@ -0,1 +1,2 @@ | ||
import { randomUUID } from 'node:crypto'; | ||
import { constants } from 'node:http2'; | ||
@@ -14,3 +15,3 @@ import { Readable } from 'node:stream'; | ||
}, | ||
ownKeys(target) { | ||
ownKeys() { | ||
return rawResponse.getHeaderNames(); | ||
@@ -29,7 +30,3 @@ }, | ||
Object.defineProperty(res, 'httpVersion', { get() { return rawResponse.req.httpVersion; } }); | ||
Object.defineProperty(res, 'rawResponse', { | ||
get() { | ||
return rawResponse; | ||
}, | ||
}); | ||
Object.defineProperty(res, 'rawResponse', { get() { return rawResponse; } }); | ||
Object.defineProperty(res, 'headers', { | ||
@@ -42,2 +39,3 @@ get() { | ||
res._status = 200; | ||
res._eventSource = false; | ||
return res; | ||
@@ -59,4 +57,29 @@ } | ||
} | ||
if (this.rawResponse.headersSent) { | ||
throw TypeError('An attempt to set the status code failed because the response headers have already been sent'); | ||
} | ||
this._status = statusCode; | ||
}, | ||
asEventSource(value) { | ||
if (typeof value != 'boolean') { | ||
return this._eventSource; | ||
} | ||
if (value) { | ||
this._eventSourceId = randomUUID(); | ||
// "connection" is only necessary for http/1.x responses and are ignored by Node for http/2 responses | ||
this.headers['connection'] = 'keep-alive'; | ||
this.headers['content-type'] = 'text/event-stream'; | ||
this.headers['cache-control'] = 'no-cache'; | ||
} | ||
else if (this._eventSourceId) { | ||
if (this.headers['connection'] == 'keep-alive') { | ||
this.headers['connection'] = undefined; | ||
} | ||
if (this.headers['content-type'] == 'text/event-stream') { | ||
this.headers['content-type'] = undefined; | ||
} | ||
this._eventSourceId = null; | ||
} | ||
this._eventSource = value; | ||
}, | ||
write(data) { | ||
@@ -66,7 +89,13 @@ this.rawResponse.write(data); | ||
end(data) { | ||
if (this._eventSource) { | ||
throw TypeError('An attempt to close the response stream failed because the response is set up to be an event source and only clients are allowed to close such streams'); | ||
} | ||
this.rawResponse.end(data); | ||
}, | ||
send(data) { | ||
if (this._eventSource) { | ||
throw TypeError('An attempt to send a singular response failed because the response is set up to be an event source; use sendEvent instead'); | ||
} | ||
if (this.responseSent) { | ||
throw TypeError('attempted to send another response though the response stream is closed'); | ||
throw TypeError('An attempt to send a response failed because the response stream is closed'); | ||
} | ||
@@ -98,6 +127,27 @@ if (data instanceof Readable) { | ||
}, | ||
sendEvent(event) { | ||
if (!this._eventSource) { | ||
throw TypeError('An attempt to send an event failed because this response is not set up to be an event source; must use the asEventSource() setter first'); | ||
} | ||
if (this.rawResponse.headersSent && this.rawResponse.closed) { | ||
throw TypeError('An attempt to send an event failed because the stream is currently closed'); | ||
} | ||
if (!this.rawResponse.headersSent) { | ||
if (this.httpVersion == '1.1') { | ||
this.rawResponse.writeHead(this._status, { | ||
...this.headers | ||
}); | ||
} | ||
else { | ||
this.rawResponse.stream.respond({ | ||
[constants.HTTP2_HEADER_STATUS]: this._status, | ||
...this.headers, | ||
}); | ||
} | ||
} | ||
const preparedData = typeof event == 'string' ? event : JSON.stringify(event); | ||
this.rawResponse.write('id: ' + this._eventSourceId + '\n' + | ||
'data: ' + preparedData + '\n\n'); | ||
}, | ||
json(data) { | ||
if (this.responseSent) { | ||
throw TypeError('attempted to send another response though the response stream is closed'); | ||
} | ||
if (data && typeof data != 'object') { | ||
@@ -109,15 +159,3 @@ throw TypeError('An attempt to send a response as JSON failed as the response was not an object or array'); | ||
} | ||
if (this.httpVersion == '1.1') { | ||
this.rawResponse.writeHead(this._status); | ||
} | ||
else { | ||
this.rawResponse.stream.respond({ | ||
...this.headers, | ||
[constants.HTTP2_HEADER_STATUS]: this._status, | ||
}); | ||
} | ||
if (data) { | ||
this.rawResponse.write(data); | ||
} | ||
this.rawResponse.end(); | ||
this.send(data); | ||
}, | ||
@@ -124,0 +162,0 @@ async stream(sourceStream) { |
@@ -83,3 +83,3 @@ import { type PathString, type PathMatcher } from './path-matching.js'; | ||
interface RequestHandler { | ||
(req: Request, res: Response): void | 'next' | Promise<void | 'next'>; | ||
(req: Request, res: Response): void | 'next' | 'route' | Promise<void | 'next' | 'route'>; | ||
} | ||
@@ -86,0 +86,0 @@ /** |
@@ -142,2 +142,5 @@ import { getRouteParams, getQueryParams, } from './path-matching.js'; | ||
for (const handler of handlers) { | ||
if (handlerState.end) { | ||
break; | ||
} | ||
try { | ||
@@ -155,3 +158,3 @@ if (typeof handler == 'function') { | ||
} | ||
if (!latestResult) { | ||
if (latestResult != 'next') { | ||
handlerState.end = true; | ||
@@ -175,2 +178,8 @@ if (handlerState.error) { | ||
} | ||
if (latestResult == 'route') { | ||
latestResult = 'next'; | ||
} | ||
else if (latestResult != 'next') { | ||
latestResult = undefined; | ||
} | ||
} | ||
@@ -177,0 +186,0 @@ if (handlerState.error) { |
{ | ||
"name": "fluvial", | ||
"version": "0.1.0-alpha.5", | ||
"version": "0.1.0-alpha.6", | ||
"description": "Fluvial: A light http/2 server framework, similar to Express", | ||
@@ -48,4 +48,5 @@ "main": "dist/index.js", | ||
"test": "vitest", | ||
"test:coverage": "pnpm test --coverage" | ||
"test:coverage": "pnpm test --coverage", | ||
"prepublish": "pnpm test && pnpm compile" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
103532
1262