@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
package/_src/HttpApp.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { Middleware } from "./Middleware.js";
|
|
2
|
+
import type { ServerResponse } from "./ServerResponse.js";
|
|
3
|
+
import type { ResponseError } from "@fncts/http/ResponseError";
|
|
4
|
+
|
|
5
|
+
import { globalValue } from "@fncts/base/data/Global";
|
|
6
|
+
import { defaultRuntime, type Runtime } from "@fncts/io/IO";
|
|
7
|
+
|
|
8
|
+
import { clientAbortFiberId } from "./ServerError.js";
|
|
9
|
+
import { ServerRequest } from "./ServerRequest.js";
|
|
10
|
+
import { isServerResponse } from "./ServerResponse.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @tsplus type fncts.http.HttpApp
|
|
14
|
+
*/
|
|
15
|
+
export interface HttpApp<R, E, A> extends IO<R | ServerRequest, E, A> {}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @tsplus type fncts.http.HttpAppOps
|
|
19
|
+
*/
|
|
20
|
+
export interface HttpAppOps {}
|
|
21
|
+
|
|
22
|
+
export const HttpApp: HttpAppOps = {};
|
|
23
|
+
|
|
24
|
+
export declare namespace HttpApp {
|
|
25
|
+
export type Default<R, E> = HttpApp<R, E, ServerResponse>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @tsplus pipeable fncts.http.HttpApp toHandled
|
|
30
|
+
*/
|
|
31
|
+
export function toHandled<E, RH>(
|
|
32
|
+
handleResponse: (request: ServerRequest, exit: Exit<E | ResponseError, ServerResponse>) => IO<RH, never, any>,
|
|
33
|
+
middleware?: Middleware,
|
|
34
|
+
) {
|
|
35
|
+
return <R>(self: HttpApp.Default<R, E>): HttpApp.Default<Exclude<R | RH, Scope>, E | ResponseError> => {
|
|
36
|
+
const responded = IO.withFiberRuntime<R | RH | ServerRequest, E | ResponseError, ServerResponse>((fiber) => {
|
|
37
|
+
const request = fiber.getFiberRef(FiberRef.currentEnvironment).unsafeGet(ServerRequest.Tag);
|
|
38
|
+
const handler = fiber.getFiberRef(HttpApp.currentPreResponseHandlers);
|
|
39
|
+
const preHandled = handler.match(
|
|
40
|
+
() => self,
|
|
41
|
+
(handler) => self.flatMap((response) => handler(request, response)),
|
|
42
|
+
);
|
|
43
|
+
return preHandled.result.flatMap((exit) => {
|
|
44
|
+
if (exit.isFailure()) {
|
|
45
|
+
const haltMaybe = exit.cause.haltMaybe;
|
|
46
|
+
if (haltMaybe.isJust() && isServerResponse(haltMaybe.value)) {
|
|
47
|
+
exit = Exit.succeed(haltMaybe.value);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return handleResponse(request, exit) > exit;
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return (middleware ? middleware(responded) : responded).scoped.uninterruptible;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type PreResponseHandler = (
|
|
59
|
+
request: ServerRequest,
|
|
60
|
+
response: ServerResponse,
|
|
61
|
+
) => FIO<ResponseError, ServerResponse>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @tsplus static fncts.http.HttpAppOps currentPreResponseHandlers
|
|
65
|
+
*/
|
|
66
|
+
export const currentPreResponseHandlers = globalValue(Symbol.for("fncts.http.HttpApp.currentPreResponseHandlers"), () =>
|
|
67
|
+
FiberRef.unsafeMake<Maybe<PreResponseHandler>>(Nothing()),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @tsplus static fncts.http.HttpAppOps appendPreResponseHandler
|
|
72
|
+
*/
|
|
73
|
+
export function appendPreResponseHandler(handler: PreResponseHandler): UIO<void> {
|
|
74
|
+
return HttpApp.currentPreResponseHandlers.update((handlers) =>
|
|
75
|
+
handlers.match(
|
|
76
|
+
() => Just(handler),
|
|
77
|
+
(prev) => Just((request, response) => prev(request, response).flatMap((response) => handler(request, response))),
|
|
78
|
+
),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @tsplus pipeable fncts.http.HttpApp withPreResponseHandler
|
|
84
|
+
*/
|
|
85
|
+
export function withPreResponseHandler(handler: PreResponseHandler) {
|
|
86
|
+
return <R, E, A>(self: HttpApp<R, E, A>): HttpApp<R, E, A> =>
|
|
87
|
+
HttpApp.currentPreResponseHandlers.locallyWith((handlers) =>
|
|
88
|
+
handlers.match(
|
|
89
|
+
() => Just(handler),
|
|
90
|
+
(prev) =>
|
|
91
|
+
Just((request, response) => prev(request, response).flatMap((response) => handler(request, response))),
|
|
92
|
+
),
|
|
93
|
+
)(self);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function toWebHandlerRuntime<R>(runtime: Runtime<R>) {
|
|
97
|
+
const run = runtime.unsafeRunFiber;
|
|
98
|
+
const resolveSymbol = Symbol();
|
|
99
|
+
const rejectSymbol = Symbol();
|
|
100
|
+
return <E>(self: HttpApp.Default<R | Scope, E>) => {
|
|
101
|
+
const handled = self.toHandled((request, exit) => {
|
|
102
|
+
const webRequest = request.source as Request;
|
|
103
|
+
if (exit.isSuccess()) {
|
|
104
|
+
(request as any)[resolveSymbol](exit.value.toWeb(request.method === "HEAD"));
|
|
105
|
+
} else if (exit.cause.isInterruptedOnly) {
|
|
106
|
+
(request as any)[resolveSymbol](new Response(null, { status: webRequest.signal.aborted ? 499 : 503 }));
|
|
107
|
+
} else {
|
|
108
|
+
(request as any)[rejectSymbol](exit.cause.prettyPrint);
|
|
109
|
+
}
|
|
110
|
+
return IO.unit;
|
|
111
|
+
});
|
|
112
|
+
return (request: Request): Promise<Response> =>
|
|
113
|
+
new Promise((resolve, reject) => {
|
|
114
|
+
const req = ServerRequest.fromWeb(request);
|
|
115
|
+
(req as any)[resolveSymbol] = resolve;
|
|
116
|
+
(req as any)[rejectSymbol] = reject;
|
|
117
|
+
const fiber = run(
|
|
118
|
+
self.provideSomeService(req, ServerRequest.Tag).map((res) => res.toWeb(req.method === "HEAD")).scoped,
|
|
119
|
+
);
|
|
120
|
+
request.signal.addEventListener("abort", () => {
|
|
121
|
+
fiber.interruptAsFork(clientAbortFiberId);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function toWebHandler<E>(self: HttpApp.Default<Scope, E>): (request: Request) => Promise<Response> {
|
|
128
|
+
return toWebHandlerRuntime(defaultRuntime)(self);
|
|
129
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { IncomingMessage } from "./definition.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @tsplus pipeable fncts.http.IncomingMessage schemaBodyJson
|
|
5
|
+
*/
|
|
6
|
+
export function schemaBodyJson<A>(schema: Schema<A>) {
|
|
7
|
+
const decode = schema.decode;
|
|
8
|
+
return <E>(self: IncomingMessage<E>): IO<never, E | ParseFailure, A> => self.json.flatMap(decode);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @tsplus pipeable fncts.http.IncomingMessage schemaBodyUrlParams
|
|
13
|
+
*/
|
|
14
|
+
export function schemaBodyUrlParams<A>(schema: Schema<A>) {
|
|
15
|
+
const decode = schema.decode;
|
|
16
|
+
return <E>(self: IncomingMessage<E>): IO<never, E | ParseFailure, A> => self.urlParamsBody.flatMap(decode);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @tsplus pipeable fncts.http.IncomingMessage schemaHeaders
|
|
21
|
+
*/
|
|
22
|
+
export function schemaHeaders<A>(schema: Schema<A>) {
|
|
23
|
+
const decode = schema.decode;
|
|
24
|
+
return <E>(self: IncomingMessage<E>): IO<never, E | ParseFailure, A> => decode(self.headers);
|
|
25
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Headers } from "../Headers.js";
|
|
2
|
+
import type { UrlParams } from "../UrlParams.js";
|
|
3
|
+
|
|
4
|
+
export const IncomingMessageTypeId = Symbol.for("fncts.http.IncomingMessage");
|
|
5
|
+
export type IncomingMessageTypeId = typeof IncomingMessageTypeId;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @tsplus type fncts.http.IncomingMessage
|
|
9
|
+
* @tsplus companion fncts.http.IncomingMessageOps
|
|
10
|
+
*/
|
|
11
|
+
export abstract class IncomingMessage<E> {
|
|
12
|
+
readonly [IncomingMessageTypeId]: IncomingMessageTypeId = IncomingMessageTypeId;
|
|
13
|
+
abstract readonly headers: Headers;
|
|
14
|
+
abstract readonly remoteAddress: Maybe<string>;
|
|
15
|
+
abstract readonly json: IO<never, E, unknown>;
|
|
16
|
+
abstract readonly text: IO<never, E, string>;
|
|
17
|
+
abstract readonly urlParamsBody: IO<never, E, UrlParams>;
|
|
18
|
+
abstract readonly arrayBuffer: IO<never, E, ArrayBuffer>;
|
|
19
|
+
abstract readonly stream: Stream<never, E, Uint8Array>;
|
|
20
|
+
}
|
package/_src/Method.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { HttpApp } from "@fncts/http/HttpApp";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @tsplus type fncts.http.Middleware
|
|
5
|
+
*/
|
|
6
|
+
export interface Middleware {
|
|
7
|
+
<R, E>(self: HttpApp.Default<R, E>): HttpApp.Default<any, any>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @tsplus type fncts.http.MiddlewareOps
|
|
12
|
+
*/
|
|
13
|
+
export interface MiddlewareOps {}
|
|
14
|
+
|
|
15
|
+
export const Middleware: MiddlewareOps = {};
|
|
16
|
+
|
|
17
|
+
export declare namespace Middleware {
|
|
18
|
+
export interface Applied<R, E, A extends HttpApp.Default<any, any>> {
|
|
19
|
+
(self: HttpApp.Default<R, E>): A;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @tsplus static fncts.http.MiddlewareOps __call
|
|
25
|
+
* @tsplus static fncts.http.MiddlewareOps make
|
|
26
|
+
*/
|
|
27
|
+
export function make<M extends Middleware>(middleware: M): M {
|
|
28
|
+
return middleware;
|
|
29
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ServerRequest } from "./ServerRequest";
|
|
2
|
+
|
|
3
|
+
export const RequestErrorTypeId = Symbol.for("fncts.http.RequestError");
|
|
4
|
+
export type RequestErrorTypeId = typeof RequestErrorTypeId;
|
|
5
|
+
|
|
6
|
+
export class RequestError {
|
|
7
|
+
readonly [RequestErrorTypeId]: RequestErrorTypeId = RequestErrorTypeId;
|
|
8
|
+
constructor(
|
|
9
|
+
readonly request: ServerRequest,
|
|
10
|
+
readonly reason: "Transport" | "Decode",
|
|
11
|
+
readonly error: unknown,
|
|
12
|
+
) {}
|
|
13
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ServerRequest } from "@fncts/http/ServerRequest";
|
|
2
|
+
import { ServerResponse } from "@fncts/http/ServerResponse";
|
|
3
|
+
|
|
4
|
+
export const ResponseErrorTypeId = Symbol.for("fncts.http.ResponseError");
|
|
5
|
+
export type ResponseErrorTypeId = typeof ResponseErrorTypeId;
|
|
6
|
+
|
|
7
|
+
export class ResponseError extends Error {
|
|
8
|
+
readonly [ResponseErrorTypeId]: ResponseErrorTypeId = ResponseErrorTypeId;
|
|
9
|
+
constructor(
|
|
10
|
+
readonly request: ServerRequest,
|
|
11
|
+
readonly response: ServerResponse,
|
|
12
|
+
readonly reason: "Decode",
|
|
13
|
+
readonly error: unknown,
|
|
14
|
+
) {
|
|
15
|
+
super();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get methodAndUrl() {
|
|
19
|
+
return `${this.request.method} ${this.request.url}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get message() {
|
|
23
|
+
return `${this.reason} error (${this.response.status} ${this.methodAndUrl}): ${super.message}`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Method } from "../Method.js";
|
|
2
|
+
import type { PathInput, Route } from "./definition.js";
|
|
3
|
+
|
|
4
|
+
import { RouteContext } from "./definition.js";
|
|
5
|
+
import { RouteImpl } from "./internal.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @tsplus static fncts.http.RouteContextOps params
|
|
9
|
+
*/
|
|
10
|
+
export const params = IO.service(RouteContext.Tag).map((routeContext) => routeContext.params);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @tsplus static fncts.http.RouteContextOps searchParams
|
|
14
|
+
*/
|
|
15
|
+
export const searchParams = IO.service(RouteContext.Tag).map((routeContext) => routeContext.searchParams);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @tsplus static fncts.http.RouteContextOps schemaParams
|
|
19
|
+
*/
|
|
20
|
+
export function schemaParams<A>(schema: Schema<A>): IO<RouteContext, ParseFailure, A> {
|
|
21
|
+
const decode = schema.decode;
|
|
22
|
+
return IO.service(RouteContext.Tag).flatMap((routeContext) =>
|
|
23
|
+
decode({ ...routeContext.params, ...routeContext.searchParams }),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @tsplus static fncts.http.RouteContextOps schemaPathParams
|
|
29
|
+
*/
|
|
30
|
+
export function schemaPathParams<A>(schema: Schema<A>): IO<RouteContext, ParseFailure, A> {
|
|
31
|
+
const decode = schema.decode;
|
|
32
|
+
return params.flatMap(decode);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @tsplus static fncts.http.RouteContextOps schemaSearchParams
|
|
37
|
+
*/
|
|
38
|
+
export function schemaSearchParams<A>(schema: Schema<A>): IO<RouteContext, ParseFailure, A> {
|
|
39
|
+
const decode = schema.decode;
|
|
40
|
+
return searchParams.flatMap(decode);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @tsplus static fncts.http.RouteOps __call
|
|
45
|
+
*/
|
|
46
|
+
export function make<R, E>(
|
|
47
|
+
method: Method,
|
|
48
|
+
path: PathInput,
|
|
49
|
+
handler: Route.Handler<R, E>,
|
|
50
|
+
prefix: Maybe<string> = Nothing(),
|
|
51
|
+
): Route<R, E> {
|
|
52
|
+
return new RouteImpl(method, path, handler, prefix);
|
|
53
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Method } from "../Method.js";
|
|
2
|
+
import type { ServerRequest } from "../ServerRequest";
|
|
3
|
+
import type { ServerResponse } from "../ServerResponse.js";
|
|
4
|
+
|
|
5
|
+
export const RouteTypeId = Symbol.for("fncts.http.RouteTypeId");
|
|
6
|
+
export type RouteTypeId = typeof RouteTypeId;
|
|
7
|
+
|
|
8
|
+
export type PathInput = `/${string}` | "*";
|
|
9
|
+
|
|
10
|
+
export abstract class Route<R, E> {
|
|
11
|
+
readonly [RouteTypeId]: RouteTypeId = RouteTypeId;
|
|
12
|
+
|
|
13
|
+
abstract readonly method: Method | "*";
|
|
14
|
+
abstract readonly path: PathInput;
|
|
15
|
+
abstract readonly handler: Route.Handler<R, E>;
|
|
16
|
+
abstract readonly prefix: Maybe<string>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export declare namespace Route {
|
|
20
|
+
export type Handler<R, E> = IO<R | ServerRequest | RouteContext, E, ServerResponse>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const RouteContextTypeId = Symbol.for("fncts.http.RouteContextTypeId");
|
|
24
|
+
export type RouteContextTypeId = typeof RouteContextTypeId;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @tsplus type fncts.http.RouteContext
|
|
28
|
+
* @tsplus companion fncts.http.RouteContextOps
|
|
29
|
+
*/
|
|
30
|
+
export abstract class RouteContext {
|
|
31
|
+
readonly [RouteContextTypeId]: RouteContextTypeId = RouteContextTypeId;
|
|
32
|
+
abstract readonly route: Route<unknown, unknown>;
|
|
33
|
+
abstract readonly params: Readonly<Record<string, string | undefined>>;
|
|
34
|
+
abstract readonly searchParams: Readonly<Record<string, string>>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @tsplus static fncts.http.RouteContextOps Tag
|
|
39
|
+
*/
|
|
40
|
+
export const RouteContextTag = Tag<RouteContext>();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Method } from "../Method.js";
|
|
2
|
+
import type { PathInput } from "./definition";
|
|
3
|
+
|
|
4
|
+
import { Route, RouteContext } from "./definition.js";
|
|
5
|
+
|
|
6
|
+
export class RouteImpl<R, E> extends Route<R, E> {
|
|
7
|
+
constructor(
|
|
8
|
+
readonly method: Method | "*",
|
|
9
|
+
readonly path: PathInput,
|
|
10
|
+
readonly handler: Route.Handler<R, E>,
|
|
11
|
+
readonly prefix: Maybe<string> = Nothing(),
|
|
12
|
+
) {
|
|
13
|
+
super();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class RouteContextImpl extends RouteContext {
|
|
18
|
+
constructor(
|
|
19
|
+
readonly route: Route<unknown, unknown>,
|
|
20
|
+
readonly params: Readonly<Record<string, string | undefined>>,
|
|
21
|
+
readonly searchParams: Readonly<Record<string, string>>,
|
|
22
|
+
) {
|
|
23
|
+
super();
|
|
24
|
+
}
|
|
25
|
+
}
|
package/_src/Route.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ServerRequest } from "./ServerRequest.js";
|
|
2
|
+
|
|
3
|
+
export const RouteNotFoundTypeId = Symbol.for("fncts.http.RouteNotFound");
|
|
4
|
+
export type RouteNotFoundTypeId = typeof RouteNotFoundTypeId;
|
|
5
|
+
|
|
6
|
+
export class RouteNotFound extends Error {
|
|
7
|
+
constructor(readonly request: ServerRequest) {
|
|
8
|
+
super();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
get message() {
|
|
12
|
+
return `${this.request.method} ${this.request.url} not found`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { HttpApp } from "../HttpApp.js";
|
|
2
|
+
import type { Method } from "../Method.js";
|
|
3
|
+
import type { PathInput, Route } from "../Route.js";
|
|
4
|
+
import type { Router } from "./definition.js";
|
|
5
|
+
|
|
6
|
+
import { RouteImpl } from "../Route/internal.js";
|
|
7
|
+
import { RouterInternal } from "./internal.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @tsplus static fncts.http.RouterOps empty
|
|
11
|
+
*/
|
|
12
|
+
export const empty: Router<never, never> = new RouterInternal(Conc.empty(), Conc.empty());
|
|
13
|
+
|
|
14
|
+
export function route(method: Method | "*") {
|
|
15
|
+
return <R1, E1>(path: PathInput, handler: Route.Handler<R1, E1>) =>
|
|
16
|
+
<R, E>(self: Router<R, E>): Router<R | Router.ExcludeProvided<R1>, E | E1> =>
|
|
17
|
+
new RouterInternal<any, any>(self.routes.append(new RouteImpl(method, path, handler)), self.mounts);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @tsplus pipeable fncts.http.Router get
|
|
22
|
+
* @tsplus static fncts.http.RouterOps get
|
|
23
|
+
*/
|
|
24
|
+
export const get: <R1, E1>(
|
|
25
|
+
path: PathInput,
|
|
26
|
+
handler: Route.Handler<R1, E1>,
|
|
27
|
+
) => <R, E>(self: Router<R, E>) => Router<R | Router.ExcludeProvided<R1>, E | E1> = route("GET");
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @tsplus pipeable fncts.http.Router post
|
|
31
|
+
* @tsplus static fncts.http.RouterOps post
|
|
32
|
+
*/
|
|
33
|
+
export const post: <R1, E1>(
|
|
34
|
+
path: PathInput,
|
|
35
|
+
handler: Route.Handler<R1, E1>,
|
|
36
|
+
) => <R, E>(self: Router<R, E>) => Router<R | Router.ExcludeProvided<R1>, E | E1> = route("POST");
|
|
37
|
+
/**
|
|
38
|
+
* @tsplus pipeable fncts.http.Router put
|
|
39
|
+
* @tsplus static fncts.http.RouterOps put
|
|
40
|
+
*/
|
|
41
|
+
export const put: <R1, E1>(
|
|
42
|
+
path: PathInput,
|
|
43
|
+
handler: Route.Handler<R1, E1>,
|
|
44
|
+
) => <R, E>(self: Router<R, E>) => Router<R | Router.ExcludeProvided<R1>, E | E1> = route("PUT");
|
|
45
|
+
/**
|
|
46
|
+
* @tsplus pipeable fncts.http.Router patch
|
|
47
|
+
* @tsplus static fncts.http.RouterOps patch
|
|
48
|
+
*/
|
|
49
|
+
export const patch: <R1, E1>(
|
|
50
|
+
path: PathInput,
|
|
51
|
+
handler: Route.Handler<R1, E1>,
|
|
52
|
+
) => <R, E>(self: Router<R, E>) => Router<R | Router.ExcludeProvided<R1>, E | E1> = route("PATCH");
|
|
53
|
+
/**
|
|
54
|
+
* @tsplus pipeable fncts.http.Router del
|
|
55
|
+
* @tsplus static fncts.http.RouterOps del
|
|
56
|
+
*/
|
|
57
|
+
export const del: <R1, E1>(
|
|
58
|
+
path: PathInput,
|
|
59
|
+
handler: Route.Handler<R1, E1>,
|
|
60
|
+
) => <R, E>(self: Router<R, E>) => Router<R | Router.ExcludeProvided<R1>, E | E1> = route("DELETE");
|
|
61
|
+
/**
|
|
62
|
+
* @tsplus pipeable fncts.http.Router head
|
|
63
|
+
* @tsplus static fncts.http.RouterOps head
|
|
64
|
+
*/
|
|
65
|
+
export const head: <R1, E1>(
|
|
66
|
+
path: PathInput,
|
|
67
|
+
handler: Route.Handler<R1, E1>,
|
|
68
|
+
) => <R, E>(self: Router<R, E>) => Router<R | Router.ExcludeProvided<R1>, E | E1> = route("HEAD");
|
|
69
|
+
/**
|
|
70
|
+
* @tsplus pipeable fncts.http.Router options
|
|
71
|
+
* @tsplus static fncts.http.RouterOps options
|
|
72
|
+
*/
|
|
73
|
+
export const options: <R1, E1>(
|
|
74
|
+
path: PathInput,
|
|
75
|
+
handler: Route.Handler<R1, E1>,
|
|
76
|
+
) => <R, E>(self: Router<R, E>) => Router<R | Router.ExcludeProvided<R1>, E | E1> = route("OPTIONS");
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @tsplus pipeable fncts.http.Router use
|
|
80
|
+
* @tsplus static fncts.http.RouterOps use
|
|
81
|
+
*/
|
|
82
|
+
export function use<R, E, R1, E1>(f: (self: Route.Handler<R, E>) => HttpApp.Default<R1, E1>, __tsplusTrace?: string) {
|
|
83
|
+
return (self: Router<R, E>): Router<Router.ExcludeProvided<R1>, E1> =>
|
|
84
|
+
new RouterInternal<any, any>(
|
|
85
|
+
self.routes.map((route) => new RouteImpl(route.method, route.path, f(route.handler as any), route.prefix)),
|
|
86
|
+
self.mounts.map(({ prefix, httpApp }) => ({ prefix, httpApp: f(httpApp as any) })),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @tsplus static fncts.http.RouterOps from
|
|
92
|
+
*/
|
|
93
|
+
export function from<R extends Route<any, any>>(
|
|
94
|
+
routes: Iterable<R>,
|
|
95
|
+
): Router<R extends Route<infer Env, infer _> ? Env : never, R extends Route<infer _, infer E> ? E : never> {
|
|
96
|
+
return new RouterInternal(Conc.from(routes), Conc.empty());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @tsplus pipeable fncts.http.Router concat
|
|
101
|
+
*/
|
|
102
|
+
export function concat<R1, E1>(that: Router<R1, E1>) {
|
|
103
|
+
return <R, E>(self: Router<R, E>): Router<R | R1, E | E1> =>
|
|
104
|
+
new RouterInternal(self.routes.concat(that.routes) as Conc<Route<R | R1, E | E1>>, self.mounts);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function removeTrailingSlash(path: PathInput): PathInput {
|
|
108
|
+
return (path.endsWith("/") ? path.slice(0, -1) : path) as PathInput;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @tsplus pipeable fncts.http.Router prefixAll
|
|
113
|
+
*/
|
|
114
|
+
export function prefixAll(prefix: PathInput) {
|
|
115
|
+
return <R, E>(self: Router<R, E>): Router<R, E> => {
|
|
116
|
+
prefix = removeTrailingSlash(prefix);
|
|
117
|
+
return new RouterInternal(
|
|
118
|
+
self.routes.map(
|
|
119
|
+
(route) =>
|
|
120
|
+
new RouteImpl(
|
|
121
|
+
route.method,
|
|
122
|
+
route.path === "/" ? prefix : ((prefix + route.path) as PathInput),
|
|
123
|
+
route.handler,
|
|
124
|
+
route.prefix.map((_) => prefix + _).orElse(Just(prefix)),
|
|
125
|
+
),
|
|
126
|
+
),
|
|
127
|
+
self.mounts.map(({ prefix: path, httpApp }) => ({ prefix: path === "/" ? prefix : prefix + path, httpApp })),
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @tsplus pipeable fncts.http.Router mount
|
|
134
|
+
*/
|
|
135
|
+
export function mount<R1, E1>(path: `/${string}`, that: Router<R1, E1>) {
|
|
136
|
+
return <R, E>(self: Router<R, E>): Router<R | R1, E | E1> => self.concat(that.prefixAll(path));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @tsplus pipeable fncts.http.Router catchAll
|
|
141
|
+
*/
|
|
142
|
+
export function catchAll<E, R1, E1>(f: (e: E) => Route.Handler<R1, E1>, __tsplusTrace?: string) {
|
|
143
|
+
return <R>(self: Router<R, E>): Router<R | Router.ExcludeProvided<R1>, E1> =>
|
|
144
|
+
self.use((handler) => handler.catchAll(f));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @tsplus pipeable fncts.http.Router catchAllCause
|
|
149
|
+
*/
|
|
150
|
+
export function catchAllCause<E, R1, E1>(f: (e: Cause<E>) => Route.Handler<R1, E1>, __tsplusTrace?: string) {
|
|
151
|
+
return <R>(self: Router<R, E>): Router<R | Router.ExcludeProvided<R1>, E1> =>
|
|
152
|
+
self.use((handler) => handler.catchAllCause(f));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @tsplus pipeable fncts.http.Router provideService
|
|
157
|
+
*/
|
|
158
|
+
export function provideService<T>(service: T, tag: Tag<T>, __tsplusTrace?: string) {
|
|
159
|
+
return <R, E>(self: Router<R, E>): Router<Exclude<R, T>, E> =>
|
|
160
|
+
self.use((handler) => handler.provideSomeService(service, tag));
|
|
161
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { HttpApp } from "../HttpApp.js";
|
|
2
|
+
import type { Route, RouteContext } from "../Route.js";
|
|
3
|
+
import type { RouteNotFound } from "../RouteNotFound.js";
|
|
4
|
+
import type { ServerRequest } from "../ServerRequest.js";
|
|
5
|
+
import type { ServerResponse } from "@fncts/http/ServerResponse";
|
|
6
|
+
|
|
7
|
+
import { External } from "@fncts/io/IO";
|
|
8
|
+
|
|
9
|
+
export const RouterTypeId = Symbol.for("fncts.http.Router");
|
|
10
|
+
export type RouterTypeId = typeof RouterTypeId;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @tsplus type fncts.http.Router
|
|
14
|
+
* @tsplus companion fncts.http.RouterOps
|
|
15
|
+
*/
|
|
16
|
+
export abstract class Router<R, E>
|
|
17
|
+
extends External<Exclude<R, RouteContext>, E | RouteNotFound, ServerResponse>
|
|
18
|
+
implements HttpApp.Default<Exclude<R, RouteContext>, E | RouteNotFound>
|
|
19
|
+
{
|
|
20
|
+
readonly [RouterTypeId]: RouterTypeId = RouterTypeId;
|
|
21
|
+
abstract readonly routes: Conc<Route<R, E>>;
|
|
22
|
+
abstract readonly mounts: Conc<
|
|
23
|
+
Readonly<{ prefix: string; httpApp: HttpApp.Default<R, E>; options?: { readonly inclduePrefix?: boolean } }>
|
|
24
|
+
>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export declare namespace Router {
|
|
28
|
+
export type ExcludeProvided<A> = Exclude<A, RouteContext | ServerRequest | Scope>;
|
|
29
|
+
}
|