@fncts/http 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Body/api.d.ts +63 -0
- package/Body/definition.d.ts +53 -0
- package/Body.d.ts +2 -0
- package/BodyError.d.ts +30 -0
- package/Headers.d.ts +42 -0
- package/HttpApp.d.ts +46 -0
- package/IncomingMessage/api.d.ts +19 -0
- package/IncomingMessage/definition.d.ts +21 -0
- package/IncomingMessage.d.ts +2 -0
- package/Method.d.ts +2 -0
- package/Middleware.d.ts +24 -0
- package/QueryParams.d.ts +7 -0
- package/RequestError.d.ts +10 -0
- package/ResponseError.d.ts +14 -0
- package/Route/api.d.ts +37 -0
- package/Route/definition.d.ts +35 -0
- package/Route/internal.d.ts +17 -0
- package/Route.d.ts +2 -0
- package/RouteNotFound.d.ts +8 -0
- package/Router/api.d.ts +96 -0
- package/Router/definition.d.ts +28 -0
- package/Router/internal.d.ts +26 -0
- package/Router.d.ts +2 -0
- package/Server.d.ts +53 -0
- package/ServerError.d.ts +9 -0
- package/ServerRequest/api.d.ts +6 -0
- package/ServerRequest/definition.d.ts +30 -0
- package/ServerRequest/internal.d.ts +33 -0
- package/ServerRequest.d.ts +2 -0
- package/ServerResponse/api.d.ts +69 -0
- package/ServerResponse/definition.d.ts +30 -0
- package/ServerResponse.d.ts +2 -0
- package/Socket.d.ts +107 -0
- package/UrlParams.d.ts +21 -0
- package/_cjs/Body/api.cjs +93 -0
- package/_cjs/Body/api.cjs.map +1 -0
- package/_cjs/Body/definition.cjs +71 -0
- package/_cjs/Body/definition.cjs.map +1 -0
- package/_cjs/Body.cjs +28 -0
- package/_cjs/Body.cjs.map +1 -0
- package/_cjs/BodyError.cjs +43 -0
- package/_cjs/BodyError.cjs.map +1 -0
- package/_cjs/Headers.cjs +87 -0
- package/_cjs/Headers.cjs.map +1 -0
- package/_cjs/HttpApp.cjs +109 -0
- package/_cjs/HttpApp.cjs.map +1 -0
- package/_cjs/IncomingMessage/api.cjs +35 -0
- package/_cjs/IncomingMessage/api.cjs.map +1 -0
- package/_cjs/IncomingMessage/definition.cjs +20 -0
- package/_cjs/IncomingMessage/definition.cjs.map +1 -0
- package/_cjs/IncomingMessage.cjs +28 -0
- package/_cjs/IncomingMessage.cjs.map +1 -0
- package/_cjs/Method.cjs +10 -0
- package/_cjs/Method.cjs.map +1 -0
- package/_cjs/Middleware.cjs +16 -0
- package/_cjs/Middleware.cjs.map +1 -0
- package/_cjs/QueryParams.cjs +6 -0
- package/_cjs/QueryParams.cjs.map +1 -0
- package/_cjs/RequestError.cjs +19 -0
- package/_cjs/RequestError.cjs.map +1 -0
- package/_cjs/ResponseError.cjs +27 -0
- package/_cjs/ResponseError.cjs.map +1 -0
- package/_cjs/Route/api.cjs +61 -0
- package/_cjs/Route/api.cjs.map +1 -0
- package/_cjs/Route/definition.cjs +35 -0
- package/_cjs/Route/definition.cjs.map +1 -0
- package/_cjs/Route/internal.cjs +31 -0
- package/_cjs/Route/internal.cjs.map +1 -0
- package/_cjs/Route.cjs +28 -0
- package/_cjs/Route.cjs.map +1 -0
- package/_cjs/RouteNotFound.cjs +18 -0
- package/_cjs/RouteNotFound.cjs.map +1 -0
- package/_cjs/Router/api.cjs +141 -0
- package/_cjs/Router/api.cjs.map +1 -0
- package/_cjs/Router/definition.cjs +22 -0
- package/_cjs/Router/definition.cjs.map +1 -0
- package/_cjs/Router/internal.cjs +85 -0
- package/_cjs/Router/internal.cjs.map +1 -0
- package/_cjs/Router.cjs +28 -0
- package/_cjs/Router.cjs.map +1 -0
- package/_cjs/Server.cjs +53 -0
- package/_cjs/Server.cjs.map +1 -0
- package/_cjs/ServerError.cjs +21 -0
- package/_cjs/ServerError.cjs.map +1 -0
- package/_cjs/ServerRequest/api.cjs +14 -0
- package/_cjs/ServerRequest/api.cjs.map +1 -0
- package/_cjs/ServerRequest/definition.cjs +29 -0
- package/_cjs/ServerRequest/definition.cjs.map +1 -0
- package/_cjs/ServerRequest/internal.cjs +77 -0
- package/_cjs/ServerRequest/internal.cjs.map +1 -0
- package/_cjs/ServerRequest.cjs +28 -0
- package/_cjs/ServerRequest.cjs.map +1 -0
- package/_cjs/ServerResponse/api.cjs +157 -0
- package/_cjs/ServerResponse/api.cjs.map +1 -0
- package/_cjs/ServerResponse/definition.cjs +44 -0
- package/_cjs/ServerResponse/definition.cjs.map +1 -0
- package/_cjs/ServerResponse.cjs +28 -0
- package/_cjs/ServerResponse.cjs.map +1 -0
- package/_cjs/Socket.cjs +221 -0
- package/_cjs/Socket.cjs.map +1 -0
- package/_cjs/UrlParams.cjs +34 -0
- package/_cjs/UrlParams.cjs.map +1 -0
- package/_cjs/global.cjs +6 -0
- package/_cjs/global.cjs.map +1 -0
- package/_mjs/Body/api.mjs +78 -0
- package/_mjs/Body/api.mjs.map +1 -0
- package/_mjs/Body/definition.mjs +58 -0
- package/_mjs/Body/definition.mjs.map +1 -0
- package/_mjs/Body.mjs +5 -0
- package/_mjs/Body.mjs.map +1 -0
- package/_mjs/BodyError.mjs +33 -0
- package/_mjs/BodyError.mjs.map +1 -0
- package/_mjs/Headers.mjs +75 -0
- package/_mjs/Headers.mjs.map +1 -0
- package/_mjs/HttpApp.mjs +96 -0
- package/_mjs/HttpApp.mjs.map +1 -0
- package/_mjs/IncomingMessage/api.mjs +25 -0
- package/_mjs/IncomingMessage/api.mjs.map +1 -0
- package/_mjs/IncomingMessage/definition.mjs +13 -0
- package/_mjs/IncomingMessage/definition.mjs.map +1 -0
- package/_mjs/IncomingMessage.mjs +5 -0
- package/_mjs/IncomingMessage.mjs.map +1 -0
- package/_mjs/Method.mjs +4 -0
- package/_mjs/Method.mjs.map +1 -0
- package/_mjs/Middleware.mjs +9 -0
- package/_mjs/Middleware.mjs.map +1 -0
- package/_mjs/QueryParams.mjs +2 -0
- package/_mjs/QueryParams.mjs.map +1 -0
- package/_mjs/RequestError.mjs +12 -0
- package/_mjs/RequestError.mjs.map +1 -0
- package/_mjs/ResponseError.mjs +20 -0
- package/_mjs/ResponseError.mjs.map +1 -0
- package/_mjs/Route/api.mjs +48 -0
- package/_mjs/Route/api.mjs.map +1 -0
- package/_mjs/Route/definition.mjs +25 -0
- package/_mjs/Route/definition.mjs.map +1 -0
- package/_mjs/Route/internal.mjs +21 -0
- package/_mjs/Route/internal.mjs.map +1 -0
- package/_mjs/Route.mjs +5 -0
- package/_mjs/Route.mjs.map +1 -0
- package/_mjs/RouteNotFound.mjs +11 -0
- package/_mjs/RouteNotFound.mjs.map +1 -0
- package/_mjs/Router/api.mjs +123 -0
- package/_mjs/Router/api.mjs.map +1 -0
- package/_mjs/Router/definition.mjs +15 -0
- package/_mjs/Router/definition.mjs.map +1 -0
- package/_mjs/Router/internal.mjs +76 -0
- package/_mjs/Router/internal.mjs.map +1 -0
- package/_mjs/Router.mjs +5 -0
- package/_mjs/Router.mjs.map +1 -0
- package/_mjs/Server.mjs +41 -0
- package/_mjs/Server.mjs.map +1 -0
- package/_mjs/ServerError.mjs +14 -0
- package/_mjs/ServerError.mjs.map +1 -0
- package/_mjs/ServerRequest/api.mjs +8 -0
- package/_mjs/ServerRequest/api.mjs.map +1 -0
- package/_mjs/ServerRequest/definition.mjs +20 -0
- package/_mjs/ServerRequest/definition.mjs.map +1 -0
- package/_mjs/ServerRequest/internal.mjs +68 -0
- package/_mjs/ServerRequest/internal.mjs.map +1 -0
- package/_mjs/ServerRequest.mjs +5 -0
- package/_mjs/ServerRequest.mjs.map +1 -0
- package/_mjs/ServerResponse/api.mjs +138 -0
- package/_mjs/ServerResponse/api.mjs.map +1 -0
- package/_mjs/ServerResponse/definition.mjs +34 -0
- package/_mjs/ServerResponse/definition.mjs.map +1 -0
- package/_mjs/ServerResponse.mjs +5 -0
- package/_mjs/ServerResponse.mjs.map +1 -0
- package/_mjs/Socket.mjs +202 -0
- package/_mjs/Socket.mjs.map +1 -0
- package/_mjs/UrlParams.mjs +25 -0
- package/_mjs/UrlParams.mjs.map +1 -0
- package/_mjs/global.mjs +2 -0
- package/_mjs/global.mjs.map +1 -0
- package/_src/Body/api.ts +106 -0
- package/_src/Body/definition.ts +74 -0
- package/_src/Body.ts +5 -0
- package/_src/BodyError.ts +38 -0
- package/_src/Headers.ts +84 -0
- package/_src/HttpApp.ts +129 -0
- package/_src/IncomingMessage/api.ts +25 -0
- package/_src/IncomingMessage/definition.ts +20 -0
- package/_src/IncomingMessage.ts +5 -0
- package/_src/Method.ts +5 -0
- package/_src/Middleware.ts +29 -0
- package/_src/QueryParams.ts +7 -0
- package/_src/RequestError.ts +13 -0
- package/_src/ResponseError.ts +25 -0
- package/_src/Route/api.ts +53 -0
- package/_src/Route/definition.ts +40 -0
- package/_src/Route/internal.ts +25 -0
- package/_src/Route.ts +5 -0
- package/_src/RouteNotFound.ts +14 -0
- package/_src/Router/api.ts +161 -0
- package/_src/Router/definition.ts +29 -0
- package/_src/Router/internal.ts +95 -0
- package/_src/Router.ts +5 -0
- package/_src/Server.ts +88 -0
- package/_src/ServerError.ts +14 -0
- package/_src/ServerRequest/api.ts +10 -0
- package/_src/ServerRequest/definition.ts +33 -0
- package/_src/ServerRequest/internal.ts +106 -0
- package/_src/ServerRequest.ts +5 -0
- package/_src/ServerResponse/api.ts +177 -0
- package/_src/ServerResponse/definition.ts +51 -0
- package/_src/ServerResponse.ts +5 -0
- package/_src/Socket.ts +294 -0
- package/_src/UrlParams.ts +28 -0
- package/_src/global.ts +2 -0
- package/global.d.ts +1 -0
- package/package.json +27 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { HttpApp } from "../HttpApp.js";
|
|
2
|
+
import type { ServerResponse } from "../ServerResponse.js";
|
|
3
|
+
|
|
4
|
+
import { IO } from "@fncts/io/IO";
|
|
5
|
+
import * as FindMyWay from "find-my-way-ts";
|
|
6
|
+
|
|
7
|
+
import { type PathInput, type Route, RouteContext } from "../Route.js";
|
|
8
|
+
import { RouteContextImpl, RouteImpl } from "../Route/internal.js";
|
|
9
|
+
import { RouteNotFound } from "../RouteNotFound.js";
|
|
10
|
+
import { ServerRequest } from "../ServerRequest.js";
|
|
11
|
+
import { Router } from "./definition.js";
|
|
12
|
+
|
|
13
|
+
export class RouterInternal<R, E> extends Router<R, E> {
|
|
14
|
+
readonly httpApp: IO<Exclude<R, RouteContext>, E | RouteNotFound, ServerResponse>;
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
readonly routes: Conc<Route<R, E>>,
|
|
18
|
+
readonly mounts: Conc<
|
|
19
|
+
Readonly<{ prefix: string; httpApp: HttpApp.Default<R, E>; options?: { readonly inclduePrefix?: boolean } }>
|
|
20
|
+
>,
|
|
21
|
+
) {
|
|
22
|
+
super();
|
|
23
|
+
this.httpApp = toHttpApp(this) as any;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get toIO(): IO<Exclude<R, RouteContext>, RouteNotFound | E, ServerResponse> {
|
|
27
|
+
return this.httpApp;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function toHttpApp<R, E>(self: Router<R, E>): HttpApp.Default<R, E | RouteNotFound> {
|
|
32
|
+
const router = FindMyWay.make<Route<R, E>>();
|
|
33
|
+
|
|
34
|
+
const mounts = self.mounts.map(
|
|
35
|
+
({ prefix, httpApp, options }) =>
|
|
36
|
+
[
|
|
37
|
+
prefix,
|
|
38
|
+
new RouteContextImpl(
|
|
39
|
+
new RouteImpl(
|
|
40
|
+
"*",
|
|
41
|
+
options?.inclduePrefix ? (`${prefix}/*` as PathInput) : "/*",
|
|
42
|
+
httpApp,
|
|
43
|
+
options?.inclduePrefix ? Nothing() : Just(prefix),
|
|
44
|
+
),
|
|
45
|
+
{},
|
|
46
|
+
{},
|
|
47
|
+
) as RouteContext,
|
|
48
|
+
options,
|
|
49
|
+
] as const,
|
|
50
|
+
);
|
|
51
|
+
const mountsLen = mounts.length;
|
|
52
|
+
self.routes.forEach((route) => {
|
|
53
|
+
if (route.method === "*") {
|
|
54
|
+
router.all(route.path, route);
|
|
55
|
+
} else {
|
|
56
|
+
router.on(route.method, route.path, route);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return IO.withFiberRuntime<R | ServerRequest, E | RouteNotFound, ServerResponse>((fiber) => {
|
|
60
|
+
let context = fiber.getFiberRef(FiberRef.currentEnvironment);
|
|
61
|
+
const request = context.unsafeGet(ServerRequest.Tag);
|
|
62
|
+
if (mountsLen > 0) {
|
|
63
|
+
for (let i = 0; i < mountsLen; i++) {
|
|
64
|
+
const [path, routeContext, options] = mounts[i];
|
|
65
|
+
if (request.url.startsWith(path)) {
|
|
66
|
+
context = context.add(routeContext, RouteContext.Tag);
|
|
67
|
+
if (options?.inclduePrefix !== true) {
|
|
68
|
+
context = context.add(sliceRequestUrl(request, path), ServerRequest.Tag);
|
|
69
|
+
}
|
|
70
|
+
return FiberRef.currentEnvironment.locally(context)(routeContext.route.handler as HttpApp.Default<R, E>);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let result = router.find(request.method, request.url);
|
|
76
|
+
if (result === undefined && request.method === "HEAD") {
|
|
77
|
+
result = router.find("GET", request.url);
|
|
78
|
+
}
|
|
79
|
+
if (result === undefined) {
|
|
80
|
+
return IO.failNow(new RouteNotFound(request));
|
|
81
|
+
}
|
|
82
|
+
const route = result.handler;
|
|
83
|
+
if (route.prefix.isJust()) {
|
|
84
|
+
context = context.add(sliceRequestUrl(request, route.prefix.value), ServerRequest.Tag);
|
|
85
|
+
}
|
|
86
|
+
return FiberRef.currentEnvironment.locally(context)(
|
|
87
|
+
route.handler as IO<Router.ExcludeProvided<R>, E, ServerResponse>,
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function sliceRequestUrl(request: ServerRequest, prefix: string) {
|
|
93
|
+
const prefixLen = prefix.length;
|
|
94
|
+
return request.modify({ url: request.url.length <= prefixLen ? "/" : request.url.slice(prefixLen) });
|
|
95
|
+
}
|
package/_src/Router.ts
ADDED
package/_src/Server.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { HttpApp } from "@fncts/http/HttpApp";
|
|
2
|
+
import type { Middleware } from "@fncts/http/Middleware";
|
|
3
|
+
import type { ServerRequest } from "@fncts/http/ServerRequest";
|
|
4
|
+
|
|
5
|
+
export const ServerTypeId = Symbol.for("fncts.http.Server");
|
|
6
|
+
export type ServerTypeId = typeof ServerTypeId;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @tsplus type fncts.http.Server
|
|
10
|
+
* @tsplus companion fncts.http.ServerOps
|
|
11
|
+
*/
|
|
12
|
+
export abstract class Server {
|
|
13
|
+
readonly [ServerTypeId]: ServerTypeId = ServerTypeId;
|
|
14
|
+
abstract serve<R, E>(httpApp: HttpApp.Default<R, E>): IO<Exclude<R, ServerRequest> | Scope, never, void>;
|
|
15
|
+
abstract serve<R, E, App extends HttpApp.Default<any, any>>(
|
|
16
|
+
httpApp: HttpApp.Default<R, E>,
|
|
17
|
+
middleware: Middleware.Applied<R, E, App>,
|
|
18
|
+
): IO<Exclude<R, ServerRequest> | Scope, never, void>;
|
|
19
|
+
abstract readonly address: Address;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @tsplus static fncts.http.ServerOps Tag
|
|
24
|
+
*/
|
|
25
|
+
export const ServerTag = Tag<Server>();
|
|
26
|
+
|
|
27
|
+
export type Address = TcpAddress | UnixAddress;
|
|
28
|
+
|
|
29
|
+
export interface TcpAddress {
|
|
30
|
+
readonly _tag: "TcpAddress";
|
|
31
|
+
readonly hostname: string;
|
|
32
|
+
readonly port: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface UnixAddress {
|
|
36
|
+
readonly _tag: "UnixAddress";
|
|
37
|
+
readonly path: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @tsplus static fncts.http.ServerOps __call
|
|
42
|
+
*/
|
|
43
|
+
export function make(options: {
|
|
44
|
+
readonly serve: (httpApp: HttpApp.Default<never, unknown>, middleware?: Middleware) => IO<Scope, never, void>;
|
|
45
|
+
readonly address: Address;
|
|
46
|
+
}): Server {
|
|
47
|
+
return new (class extends Server {
|
|
48
|
+
serve = options.serve;
|
|
49
|
+
address = options.address;
|
|
50
|
+
})();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @tsplus pipeable fncts.http.HttpApp serve
|
|
55
|
+
*/
|
|
56
|
+
export function serve(): <R, E>(
|
|
57
|
+
httpApp: HttpApp.Default<R, E>,
|
|
58
|
+
) => Layer<Server | Exclude<R, ServerRequest | Scope>, never, never>;
|
|
59
|
+
export function serve<R, E, App extends HttpApp.Default<any, any>>(
|
|
60
|
+
middleware: Middleware.Applied<R, E, App>,
|
|
61
|
+
): (
|
|
62
|
+
httpApp: HttpApp.Default<R, E>,
|
|
63
|
+
middleware: Middleware.Applied<R, E, App>,
|
|
64
|
+
) => Layer<Server | Exclude<R, ServerRequest | Scope>, never, never>;
|
|
65
|
+
export function serve<R, E, App extends HttpApp.Default<any, any>>(
|
|
66
|
+
middleware?: Middleware.Applied<R, E, App>,
|
|
67
|
+
): (httpApp: HttpApp.Default<R, E>) => Layer<Server | Exclude<R, ServerRequest | Scope>, never, never> {
|
|
68
|
+
return (httpApp) =>
|
|
69
|
+
Layer.scopedDiscard(IO.service(Server.Tag).flatMap((server) => server.serve(httpApp, middleware!))) as any;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @tsplus pipeable fncts.http.HttpApp serveIO
|
|
74
|
+
*/
|
|
75
|
+
export function serveIO(): <R, E>(
|
|
76
|
+
httpApp: HttpApp.Default<R, E>,
|
|
77
|
+
) => IO<Server | Exclude<R, ServerRequest | Scope>, never, never>;
|
|
78
|
+
export function serveIO<R, E, App extends HttpApp.Default<any, any>>(
|
|
79
|
+
middleware: Middleware.Applied<R, E, App>,
|
|
80
|
+
): (
|
|
81
|
+
httpApp: HttpApp.Default<R, E>,
|
|
82
|
+
middleware: Middleware.Applied<R, E, App>,
|
|
83
|
+
) => IO<Server | Exclude<R, ServerRequest | Scope>, never, never>;
|
|
84
|
+
export function serveIO<R, E, App extends HttpApp.Default<any, any>>(
|
|
85
|
+
middleware?: Middleware.Applied<R, E, App>,
|
|
86
|
+
): (httpApp: HttpApp.Default<R, E>) => IO<Server | Exclude<R, ServerRequest | Scope>, never, never> {
|
|
87
|
+
return (httpApp) => IO.service(Server.Tag).flatMap((server) => server.serve(httpApp, middleware!)) as any;
|
|
88
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Runtime } from "@fncts/base/data/FiberId";
|
|
2
|
+
import { globalValue } from "@fncts/base/data/Global";
|
|
3
|
+
|
|
4
|
+
export const clientAbortFiberId = globalValue("fncts.http.ServerError.clientAbortFiberId", () => new Runtime(-499, 0));
|
|
5
|
+
|
|
6
|
+
export const ServeErrorTypeId = Symbol.for("fncts.http.ServeError");
|
|
7
|
+
export type ServeErrorTypeId = typeof ServeErrorTypeId;
|
|
8
|
+
|
|
9
|
+
export class ServeError extends Error {
|
|
10
|
+
readonly [ServeErrorTypeId]: ServeErrorTypeId = ServeErrorTypeId;
|
|
11
|
+
constructor(readonly error?: unknown) {
|
|
12
|
+
super();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ServerRequest } from "./definition.js";
|
|
2
|
+
|
|
3
|
+
import { ServerRequestImpl } from "./internal.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @tsplus static fncts.http.ServerRequestOps fromWeb
|
|
7
|
+
*/
|
|
8
|
+
export function fromWeb(request: globalThis.Request): ServerRequest {
|
|
9
|
+
return new ServerRequestImpl(request, request.url);
|
|
10
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Headers } from "../Headers.js";
|
|
2
|
+
import type { Method } from "../Method.js";
|
|
3
|
+
import type { RequestError } from "../RequestError.js";
|
|
4
|
+
import type { Socket } from "@fncts/http/Socket";
|
|
5
|
+
|
|
6
|
+
import { IncomingMessage } from "../IncomingMessage/definition.js";
|
|
7
|
+
|
|
8
|
+
export const ServerRequestTypeId = Symbol.for("fncts.http.ServerRequest");
|
|
9
|
+
export type ServerRequestTypeId = typeof ServerRequestTypeId;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @tsplus type fncts.http.ServerRequest
|
|
13
|
+
* @tsplus companion fncts.http.ServerRequestOps
|
|
14
|
+
*/
|
|
15
|
+
export abstract class ServerRequest extends IncomingMessage<RequestError> {
|
|
16
|
+
readonly [ServerRequestTypeId]: ServerRequestTypeId = ServerRequestTypeId;
|
|
17
|
+
|
|
18
|
+
abstract readonly source: unknown;
|
|
19
|
+
abstract readonly url: string;
|
|
20
|
+
abstract readonly originalUrl: string;
|
|
21
|
+
abstract readonly method: Method;
|
|
22
|
+
abstract upgrade: IO<never, RequestError, Socket>;
|
|
23
|
+
abstract modify(options: {
|
|
24
|
+
readonly url?: string;
|
|
25
|
+
readonly headers?: Headers;
|
|
26
|
+
readonly remoteAddress?: string;
|
|
27
|
+
}): ServerRequest;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @tsplus static fncts.http.ServerRequestOps Tag
|
|
32
|
+
*/
|
|
33
|
+
export const ServerRequestTag = Tag<ServerRequest>();
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { Method } from "../Method.js";
|
|
2
|
+
import type { Socket } from "@fncts/http/Socket";
|
|
3
|
+
|
|
4
|
+
import { Headers } from "../Headers.js";
|
|
5
|
+
import { RequestError } from "../RequestError.js";
|
|
6
|
+
import { UrlParams } from "../UrlParams.js";
|
|
7
|
+
import { ServerRequest } from "./definition.js";
|
|
8
|
+
|
|
9
|
+
export class ServerRequestImpl extends ServerRequest {
|
|
10
|
+
constructor(
|
|
11
|
+
readonly source: Request,
|
|
12
|
+
readonly url: string,
|
|
13
|
+
public headersOverride?: Headers,
|
|
14
|
+
private remoteAddressOverride?: string,
|
|
15
|
+
) {
|
|
16
|
+
super();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get method(): Method {
|
|
20
|
+
return this.source.method.toUpperCase() as Method;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get originalUrl() {
|
|
24
|
+
return this.source.url;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get remoteAddress() {
|
|
28
|
+
return Maybe.fromNullable(this.remoteAddressOverride);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get headers() {
|
|
32
|
+
this.headersOverride ??= Headers.fromHeaders(this.source.headers);
|
|
33
|
+
return this.headersOverride;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private textIO: IO<never, RequestError, string> | undefined;
|
|
37
|
+
get text(): IO<never, RequestError, string> {
|
|
38
|
+
if (this.textIO) {
|
|
39
|
+
return this.textIO;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.textIO = IO.fromPromiseCatch(
|
|
43
|
+
this.source.text(),
|
|
44
|
+
(error) => new RequestError(this, "Decode", error),
|
|
45
|
+
).memoize.unsafeRun.getOrThrow;
|
|
46
|
+
|
|
47
|
+
return this.textIO;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get json(): IO<never, RequestError, unknown> {
|
|
51
|
+
return this.text.flatMap((text) =>
|
|
52
|
+
IO.tryCatch(
|
|
53
|
+
() => JSON.parse(text),
|
|
54
|
+
(error) => new RequestError(this, "Decode", error),
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get urlParamsBody(): IO<never, RequestError, UrlParams> {
|
|
60
|
+
return this.text.flatMap((text) =>
|
|
61
|
+
IO.tryCatch(
|
|
62
|
+
() => UrlParams(new URLSearchParams(text)),
|
|
63
|
+
(error) => new RequestError(this, "Decode", error),
|
|
64
|
+
),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private arrayBufferIO: IO<never, RequestError, ArrayBuffer> | undefined;
|
|
69
|
+
get arrayBuffer(): IO<never, RequestError, ArrayBuffer> {
|
|
70
|
+
if (this.arrayBufferIO) {
|
|
71
|
+
return this.arrayBufferIO;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.arrayBufferIO = IO.fromPromiseCatch(
|
|
75
|
+
this.source.arrayBuffer(),
|
|
76
|
+
(error) => new RequestError(this, "Decode", error),
|
|
77
|
+
).memoize.unsafeRun.getOrThrow;
|
|
78
|
+
|
|
79
|
+
return this.arrayBufferIO;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
get stream(): Stream<never, RequestError, Uint8Array> {
|
|
83
|
+
if (this.source.body) {
|
|
84
|
+
return Stream.fromReadableStream(this.source.body, (error) => new RequestError(this, "Decode", error));
|
|
85
|
+
} else {
|
|
86
|
+
return Stream.failNow(new RequestError(this, "Decode", "cannot create stream from empty body"));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
modify(options: {
|
|
91
|
+
readonly url?: string | undefined;
|
|
92
|
+
readonly headers?: Headers | undefined;
|
|
93
|
+
readonly remoteAddress?: string | undefined;
|
|
94
|
+
}): ServerRequest {
|
|
95
|
+
return new ServerRequestImpl(
|
|
96
|
+
this.source,
|
|
97
|
+
options.url ?? this.url,
|
|
98
|
+
options.headers ?? this.headers,
|
|
99
|
+
options.remoteAddress ?? this.remoteAddressOverride,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
get upgrade(): IO<never, RequestError, Socket> {
|
|
104
|
+
return IO.failNow(new RequestError(this, "Decode", "Not an upgradeable ServerRequest"));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { BodyError } from "../BodyError.js";
|
|
2
|
+
import type { Options } from "./definition";
|
|
3
|
+
|
|
4
|
+
import { Body, BodyTag } from "../Body.js";
|
|
5
|
+
import { Headers } from "../Headers.js";
|
|
6
|
+
import { UrlParams } from "../UrlParams.js";
|
|
7
|
+
import { ServerResponse } from "./definition.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @tsplus static fncts.http.ServerResponseOps empty
|
|
11
|
+
*/
|
|
12
|
+
export function empty(options?: Options.WithContent): ServerResponse {
|
|
13
|
+
return new ServerResponse(options?.status ?? 204, options?.statusText, options?.headers ?? Headers.empty, Body.empty);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @tsplus static fncts.http.ServerResponseOps uint8Array
|
|
18
|
+
*/
|
|
19
|
+
export function uint8Array(body: Uint8Array, options?: Options.WithContentType): ServerResponse {
|
|
20
|
+
return new ServerResponse(
|
|
21
|
+
options?.status ?? 200,
|
|
22
|
+
options?.statusText,
|
|
23
|
+
options?.headers ?? Headers.empty,
|
|
24
|
+
Body.uint8Array(body, getContentType(options)),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @tsplus static fncts.http.ServerResponseOps text
|
|
30
|
+
*/
|
|
31
|
+
export function text(body: string, options?: Options.WithContentType): ServerResponse {
|
|
32
|
+
return new ServerResponse(
|
|
33
|
+
options?.status ?? 200,
|
|
34
|
+
options?.statusText,
|
|
35
|
+
options?.headers ?? Headers.empty,
|
|
36
|
+
Body.text(body, getContentType(options)),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @tsplus static fncts.http.ServerResponseOps json
|
|
42
|
+
*/
|
|
43
|
+
export function json(body: unknown, options?: Options.WithContent): IO<never, BodyError, ServerResponse> {
|
|
44
|
+
return Body.json(body).map(
|
|
45
|
+
(body) => new ServerResponse(options?.status ?? 200, options?.statusText, options?.headers ?? Headers.empty, body),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @tsplus static fncts.http.ServerResponseOps schemaJson
|
|
51
|
+
*/
|
|
52
|
+
export function schemaJson<A>(schema: Schema<A>) {
|
|
53
|
+
const encode = Body.jsonSchema(schema);
|
|
54
|
+
return (body: A, options?: Options.WithContent): IO<never, BodyError, ServerResponse> =>
|
|
55
|
+
encode(body).map(
|
|
56
|
+
(body) =>
|
|
57
|
+
new ServerResponse(options?.status ?? 200, options?.statusText, options?.headers ?? Headers.empty, body),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @tsplus static fncts.http.ServerResponseOps urlParams
|
|
63
|
+
*/
|
|
64
|
+
export function urlParams(body: UrlParams.Input, options?: Options.WithContent): ServerResponse {
|
|
65
|
+
return new ServerResponse(
|
|
66
|
+
options?.status ?? 200,
|
|
67
|
+
options?.statusText,
|
|
68
|
+
options?.headers ?? Headers.empty,
|
|
69
|
+
Body.text(UrlParams(body).toString(), "application/x-www-form-urlencoded"),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @tsplus static fncts.http.ServerResponseOps stream
|
|
75
|
+
*/
|
|
76
|
+
export function stream(body: Stream<never, unknown, Uint8Array>, options?: Options): ServerResponse {
|
|
77
|
+
return new ServerResponse(
|
|
78
|
+
options?.status ?? 200,
|
|
79
|
+
options?.statusText,
|
|
80
|
+
options?.headers ?? Headers.empty,
|
|
81
|
+
Body.stream(body, {
|
|
82
|
+
contentType: getContentType(options),
|
|
83
|
+
contentLength: options?.contentLength,
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @tsplus pipeable fncts.http.ServerResponse setHeader
|
|
90
|
+
*/
|
|
91
|
+
export function setHeader(key: string, value: string) {
|
|
92
|
+
return (self: ServerResponse): ServerResponse =>
|
|
93
|
+
new ServerResponse(self.status, self.statusText, self.headers.set(key, value), self.body);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @tsplus pipeable fncts.http.ServerResponse setHeaders
|
|
98
|
+
*/
|
|
99
|
+
export function setHeaders(headers: Headers.Input) {
|
|
100
|
+
return (self: ServerResponse): ServerResponse =>
|
|
101
|
+
new ServerResponse(self.status, self.statusText, self.headers.setAll(headers), self.body);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @tsplus pipeable fncts.http.ServerResponse setStatus
|
|
106
|
+
*/
|
|
107
|
+
export function setStatus(status: number, statusText?: string) {
|
|
108
|
+
return (self: ServerResponse): ServerResponse => new ServerResponse(status, statusText, self.headers, self.body);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @tsplus pipeable fncts.http.ServerResponse setBody
|
|
113
|
+
*/
|
|
114
|
+
export function setBody(body: Body) {
|
|
115
|
+
return (self: ServerResponse): ServerResponse => {
|
|
116
|
+
let headers = self.headers;
|
|
117
|
+
body.concrete();
|
|
118
|
+
if (body._tag === BodyTag.Empty) {
|
|
119
|
+
headers = self.headers.remove("content-type").remove("content-length");
|
|
120
|
+
}
|
|
121
|
+
return new ServerResponse(self.status, self.statusText, headers, body);
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @tsplus pipeable fncts.http.ServerResponse toWeb
|
|
127
|
+
*/
|
|
128
|
+
export function toWeb(withoutBody = false) {
|
|
129
|
+
return (self: ServerResponse): Response => {
|
|
130
|
+
if (withoutBody) {
|
|
131
|
+
return new Response(undefined, {
|
|
132
|
+
status: self.status,
|
|
133
|
+
statusText: self.statusText,
|
|
134
|
+
headers: self.headers.toHeadersInit(),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
self.body.concrete();
|
|
139
|
+
switch (self.body._tag) {
|
|
140
|
+
case BodyTag.Empty:
|
|
141
|
+
return new Response(undefined, {
|
|
142
|
+
status: self.status,
|
|
143
|
+
statusText: self.statusText,
|
|
144
|
+
headers: self.headers.toHeadersInit(),
|
|
145
|
+
});
|
|
146
|
+
case BodyTag.Uint8Array:
|
|
147
|
+
case BodyTag.Raw:
|
|
148
|
+
return new Response(self.body.body as any, {
|
|
149
|
+
status: self.status,
|
|
150
|
+
statusText: self.statusText,
|
|
151
|
+
headers: self.headers.toHeadersInit(),
|
|
152
|
+
});
|
|
153
|
+
case BodyTag.FormData:
|
|
154
|
+
return new Response(self.body.formData, {
|
|
155
|
+
status: self.status,
|
|
156
|
+
statusText: self.statusText,
|
|
157
|
+
headers: self.headers.toHeadersInit(),
|
|
158
|
+
});
|
|
159
|
+
case BodyTag.Stream:
|
|
160
|
+
return new Response(self.body.stream.toReadableStream, {
|
|
161
|
+
status: self.status,
|
|
162
|
+
statusText: self.statusText,
|
|
163
|
+
headers: self.headers.toHeadersInit(),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function getContentType(options: Options | undefined): string | undefined {
|
|
170
|
+
if (options?.contentType) {
|
|
171
|
+
return options.contentType;
|
|
172
|
+
} else if (options?.headers) {
|
|
173
|
+
return options.headers.unsafeGet("content-type");
|
|
174
|
+
} else {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Body } from "../Body.js";
|
|
2
|
+
|
|
3
|
+
import { Headers } from "../Headers.js";
|
|
4
|
+
|
|
5
|
+
export const ServerResponseTypeId = Symbol.for("fncts.http.ServerResponse");
|
|
6
|
+
export type ServerResponseTypeId = typeof ServerResponseTypeId;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @tsplus type fncts.http.ServerResponse
|
|
10
|
+
* @tsplus companion fncts.http.ServerResponseOps
|
|
11
|
+
*/
|
|
12
|
+
export class ServerResponse {
|
|
13
|
+
readonly [ServerResponseTypeId]: ServerResponseTypeId = ServerResponseTypeId;
|
|
14
|
+
readonly headers: Headers;
|
|
15
|
+
constructor(
|
|
16
|
+
readonly status: number,
|
|
17
|
+
readonly statusText: string | undefined,
|
|
18
|
+
headers: Headers,
|
|
19
|
+
readonly body: Body,
|
|
20
|
+
) {
|
|
21
|
+
if (body.contentType || body.contentLength) {
|
|
22
|
+
const newHeaders = headers.backing.beginMutation;
|
|
23
|
+
if (body.contentType) {
|
|
24
|
+
newHeaders.set("content-type", body.contentType);
|
|
25
|
+
}
|
|
26
|
+
if (body.contentLength) {
|
|
27
|
+
newHeaders.set("content-length", body.contentLength.toString());
|
|
28
|
+
}
|
|
29
|
+
this.headers = new Headers(newHeaders.endMutation);
|
|
30
|
+
} else {
|
|
31
|
+
this.headers = headers;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface Options {
|
|
37
|
+
status?: number;
|
|
38
|
+
statusText?: string;
|
|
39
|
+
headers?: Headers;
|
|
40
|
+
contentType?: string;
|
|
41
|
+
contentLength?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export declare namespace Options {
|
|
45
|
+
export interface WithContent extends Omit<Options, "contentType" | "contentLength"> {}
|
|
46
|
+
export interface WithContentType extends Omit<Options, "contentLength"> {}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isServerResponse(u: unknown): u is ServerResponse {
|
|
50
|
+
return isObject(u) && ServerResponseTypeId in u;
|
|
51
|
+
}
|