@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.
Files changed (211) hide show
  1. package/Body/api.d.ts +63 -0
  2. package/Body/definition.d.ts +53 -0
  3. package/Body.d.ts +2 -0
  4. package/BodyError.d.ts +30 -0
  5. package/Headers.d.ts +42 -0
  6. package/HttpApp.d.ts +46 -0
  7. package/IncomingMessage/api.d.ts +19 -0
  8. package/IncomingMessage/definition.d.ts +21 -0
  9. package/IncomingMessage.d.ts +2 -0
  10. package/Method.d.ts +2 -0
  11. package/Middleware.d.ts +24 -0
  12. package/QueryParams.d.ts +7 -0
  13. package/RequestError.d.ts +10 -0
  14. package/ResponseError.d.ts +14 -0
  15. package/Route/api.d.ts +37 -0
  16. package/Route/definition.d.ts +35 -0
  17. package/Route/internal.d.ts +17 -0
  18. package/Route.d.ts +2 -0
  19. package/RouteNotFound.d.ts +8 -0
  20. package/Router/api.d.ts +96 -0
  21. package/Router/definition.d.ts +28 -0
  22. package/Router/internal.d.ts +26 -0
  23. package/Router.d.ts +2 -0
  24. package/Server.d.ts +53 -0
  25. package/ServerError.d.ts +9 -0
  26. package/ServerRequest/api.d.ts +6 -0
  27. package/ServerRequest/definition.d.ts +30 -0
  28. package/ServerRequest/internal.d.ts +33 -0
  29. package/ServerRequest.d.ts +2 -0
  30. package/ServerResponse/api.d.ts +69 -0
  31. package/ServerResponse/definition.d.ts +30 -0
  32. package/ServerResponse.d.ts +2 -0
  33. package/Socket.d.ts +107 -0
  34. package/UrlParams.d.ts +21 -0
  35. package/_cjs/Body/api.cjs +93 -0
  36. package/_cjs/Body/api.cjs.map +1 -0
  37. package/_cjs/Body/definition.cjs +71 -0
  38. package/_cjs/Body/definition.cjs.map +1 -0
  39. package/_cjs/Body.cjs +28 -0
  40. package/_cjs/Body.cjs.map +1 -0
  41. package/_cjs/BodyError.cjs +43 -0
  42. package/_cjs/BodyError.cjs.map +1 -0
  43. package/_cjs/Headers.cjs +87 -0
  44. package/_cjs/Headers.cjs.map +1 -0
  45. package/_cjs/HttpApp.cjs +109 -0
  46. package/_cjs/HttpApp.cjs.map +1 -0
  47. package/_cjs/IncomingMessage/api.cjs +35 -0
  48. package/_cjs/IncomingMessage/api.cjs.map +1 -0
  49. package/_cjs/IncomingMessage/definition.cjs +20 -0
  50. package/_cjs/IncomingMessage/definition.cjs.map +1 -0
  51. package/_cjs/IncomingMessage.cjs +28 -0
  52. package/_cjs/IncomingMessage.cjs.map +1 -0
  53. package/_cjs/Method.cjs +10 -0
  54. package/_cjs/Method.cjs.map +1 -0
  55. package/_cjs/Middleware.cjs +16 -0
  56. package/_cjs/Middleware.cjs.map +1 -0
  57. package/_cjs/QueryParams.cjs +6 -0
  58. package/_cjs/QueryParams.cjs.map +1 -0
  59. package/_cjs/RequestError.cjs +19 -0
  60. package/_cjs/RequestError.cjs.map +1 -0
  61. package/_cjs/ResponseError.cjs +27 -0
  62. package/_cjs/ResponseError.cjs.map +1 -0
  63. package/_cjs/Route/api.cjs +61 -0
  64. package/_cjs/Route/api.cjs.map +1 -0
  65. package/_cjs/Route/definition.cjs +35 -0
  66. package/_cjs/Route/definition.cjs.map +1 -0
  67. package/_cjs/Route/internal.cjs +31 -0
  68. package/_cjs/Route/internal.cjs.map +1 -0
  69. package/_cjs/Route.cjs +28 -0
  70. package/_cjs/Route.cjs.map +1 -0
  71. package/_cjs/RouteNotFound.cjs +18 -0
  72. package/_cjs/RouteNotFound.cjs.map +1 -0
  73. package/_cjs/Router/api.cjs +141 -0
  74. package/_cjs/Router/api.cjs.map +1 -0
  75. package/_cjs/Router/definition.cjs +22 -0
  76. package/_cjs/Router/definition.cjs.map +1 -0
  77. package/_cjs/Router/internal.cjs +85 -0
  78. package/_cjs/Router/internal.cjs.map +1 -0
  79. package/_cjs/Router.cjs +28 -0
  80. package/_cjs/Router.cjs.map +1 -0
  81. package/_cjs/Server.cjs +53 -0
  82. package/_cjs/Server.cjs.map +1 -0
  83. package/_cjs/ServerError.cjs +21 -0
  84. package/_cjs/ServerError.cjs.map +1 -0
  85. package/_cjs/ServerRequest/api.cjs +14 -0
  86. package/_cjs/ServerRequest/api.cjs.map +1 -0
  87. package/_cjs/ServerRequest/definition.cjs +29 -0
  88. package/_cjs/ServerRequest/definition.cjs.map +1 -0
  89. package/_cjs/ServerRequest/internal.cjs +77 -0
  90. package/_cjs/ServerRequest/internal.cjs.map +1 -0
  91. package/_cjs/ServerRequest.cjs +28 -0
  92. package/_cjs/ServerRequest.cjs.map +1 -0
  93. package/_cjs/ServerResponse/api.cjs +157 -0
  94. package/_cjs/ServerResponse/api.cjs.map +1 -0
  95. package/_cjs/ServerResponse/definition.cjs +44 -0
  96. package/_cjs/ServerResponse/definition.cjs.map +1 -0
  97. package/_cjs/ServerResponse.cjs +28 -0
  98. package/_cjs/ServerResponse.cjs.map +1 -0
  99. package/_cjs/Socket.cjs +221 -0
  100. package/_cjs/Socket.cjs.map +1 -0
  101. package/_cjs/UrlParams.cjs +34 -0
  102. package/_cjs/UrlParams.cjs.map +1 -0
  103. package/_cjs/global.cjs +6 -0
  104. package/_cjs/global.cjs.map +1 -0
  105. package/_mjs/Body/api.mjs +78 -0
  106. package/_mjs/Body/api.mjs.map +1 -0
  107. package/_mjs/Body/definition.mjs +58 -0
  108. package/_mjs/Body/definition.mjs.map +1 -0
  109. package/_mjs/Body.mjs +5 -0
  110. package/_mjs/Body.mjs.map +1 -0
  111. package/_mjs/BodyError.mjs +33 -0
  112. package/_mjs/BodyError.mjs.map +1 -0
  113. package/_mjs/Headers.mjs +75 -0
  114. package/_mjs/Headers.mjs.map +1 -0
  115. package/_mjs/HttpApp.mjs +96 -0
  116. package/_mjs/HttpApp.mjs.map +1 -0
  117. package/_mjs/IncomingMessage/api.mjs +25 -0
  118. package/_mjs/IncomingMessage/api.mjs.map +1 -0
  119. package/_mjs/IncomingMessage/definition.mjs +13 -0
  120. package/_mjs/IncomingMessage/definition.mjs.map +1 -0
  121. package/_mjs/IncomingMessage.mjs +5 -0
  122. package/_mjs/IncomingMessage.mjs.map +1 -0
  123. package/_mjs/Method.mjs +4 -0
  124. package/_mjs/Method.mjs.map +1 -0
  125. package/_mjs/Middleware.mjs +9 -0
  126. package/_mjs/Middleware.mjs.map +1 -0
  127. package/_mjs/QueryParams.mjs +2 -0
  128. package/_mjs/QueryParams.mjs.map +1 -0
  129. package/_mjs/RequestError.mjs +12 -0
  130. package/_mjs/RequestError.mjs.map +1 -0
  131. package/_mjs/ResponseError.mjs +20 -0
  132. package/_mjs/ResponseError.mjs.map +1 -0
  133. package/_mjs/Route/api.mjs +48 -0
  134. package/_mjs/Route/api.mjs.map +1 -0
  135. package/_mjs/Route/definition.mjs +25 -0
  136. package/_mjs/Route/definition.mjs.map +1 -0
  137. package/_mjs/Route/internal.mjs +21 -0
  138. package/_mjs/Route/internal.mjs.map +1 -0
  139. package/_mjs/Route.mjs +5 -0
  140. package/_mjs/Route.mjs.map +1 -0
  141. package/_mjs/RouteNotFound.mjs +11 -0
  142. package/_mjs/RouteNotFound.mjs.map +1 -0
  143. package/_mjs/Router/api.mjs +123 -0
  144. package/_mjs/Router/api.mjs.map +1 -0
  145. package/_mjs/Router/definition.mjs +15 -0
  146. package/_mjs/Router/definition.mjs.map +1 -0
  147. package/_mjs/Router/internal.mjs +76 -0
  148. package/_mjs/Router/internal.mjs.map +1 -0
  149. package/_mjs/Router.mjs +5 -0
  150. package/_mjs/Router.mjs.map +1 -0
  151. package/_mjs/Server.mjs +41 -0
  152. package/_mjs/Server.mjs.map +1 -0
  153. package/_mjs/ServerError.mjs +14 -0
  154. package/_mjs/ServerError.mjs.map +1 -0
  155. package/_mjs/ServerRequest/api.mjs +8 -0
  156. package/_mjs/ServerRequest/api.mjs.map +1 -0
  157. package/_mjs/ServerRequest/definition.mjs +20 -0
  158. package/_mjs/ServerRequest/definition.mjs.map +1 -0
  159. package/_mjs/ServerRequest/internal.mjs +68 -0
  160. package/_mjs/ServerRequest/internal.mjs.map +1 -0
  161. package/_mjs/ServerRequest.mjs +5 -0
  162. package/_mjs/ServerRequest.mjs.map +1 -0
  163. package/_mjs/ServerResponse/api.mjs +138 -0
  164. package/_mjs/ServerResponse/api.mjs.map +1 -0
  165. package/_mjs/ServerResponse/definition.mjs +34 -0
  166. package/_mjs/ServerResponse/definition.mjs.map +1 -0
  167. package/_mjs/ServerResponse.mjs +5 -0
  168. package/_mjs/ServerResponse.mjs.map +1 -0
  169. package/_mjs/Socket.mjs +202 -0
  170. package/_mjs/Socket.mjs.map +1 -0
  171. package/_mjs/UrlParams.mjs +25 -0
  172. package/_mjs/UrlParams.mjs.map +1 -0
  173. package/_mjs/global.mjs +2 -0
  174. package/_mjs/global.mjs.map +1 -0
  175. package/_src/Body/api.ts +106 -0
  176. package/_src/Body/definition.ts +74 -0
  177. package/_src/Body.ts +5 -0
  178. package/_src/BodyError.ts +38 -0
  179. package/_src/Headers.ts +84 -0
  180. package/_src/HttpApp.ts +129 -0
  181. package/_src/IncomingMessage/api.ts +25 -0
  182. package/_src/IncomingMessage/definition.ts +20 -0
  183. package/_src/IncomingMessage.ts +5 -0
  184. package/_src/Method.ts +5 -0
  185. package/_src/Middleware.ts +29 -0
  186. package/_src/QueryParams.ts +7 -0
  187. package/_src/RequestError.ts +13 -0
  188. package/_src/ResponseError.ts +25 -0
  189. package/_src/Route/api.ts +53 -0
  190. package/_src/Route/definition.ts +40 -0
  191. package/_src/Route/internal.ts +25 -0
  192. package/_src/Route.ts +5 -0
  193. package/_src/RouteNotFound.ts +14 -0
  194. package/_src/Router/api.ts +161 -0
  195. package/_src/Router/definition.ts +29 -0
  196. package/_src/Router/internal.ts +95 -0
  197. package/_src/Router.ts +5 -0
  198. package/_src/Server.ts +88 -0
  199. package/_src/ServerError.ts +14 -0
  200. package/_src/ServerRequest/api.ts +10 -0
  201. package/_src/ServerRequest/definition.ts +33 -0
  202. package/_src/ServerRequest/internal.ts +106 -0
  203. package/_src/ServerRequest.ts +5 -0
  204. package/_src/ServerResponse/api.ts +177 -0
  205. package/_src/ServerResponse/definition.ts +51 -0
  206. package/_src/ServerResponse.ts +5 -0
  207. package/_src/Socket.ts +294 -0
  208. package/_src/UrlParams.ts +28 -0
  209. package/_src/global.ts +2 -0
  210. package/global.d.ts +1 -0
  211. package/package.json +27 -0
@@ -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
+ }
@@ -0,0 +1,5 @@
1
+ /* eslint-disable simple-import-sort/exports */
2
+ // codegen:start { preset: barrel, include: ./IncomingMessage/*.ts }
3
+ export * from "./IncomingMessage/definition.js";
4
+ export * from "./IncomingMessage/api.js";
5
+ // codegen:end
package/_src/Method.ts ADDED
@@ -0,0 +1,5 @@
1
+ export type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
2
+
3
+ export function hasBody(method: Method): boolean {
4
+ return method !== "GET" && method !== "HEAD";
5
+ }
@@ -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,7 @@
1
+ /**
2
+ * @tsplus type fncts.http.QueryParams
3
+ * @tsplus companion fncts.http.QueryParams
4
+ */
5
+ export interface QueryParams {
6
+ [param: string]: string;
7
+ }
@@ -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,5 @@
1
+ /* eslint-disable simple-import-sort/exports */
2
+ // codegen:start { preset: barrel, include: ./Route/*.ts, exclude: Route/*(*internal).* }
3
+ export * from "./Route/definition.js";
4
+ export * from "./Route/api.js";
5
+ // codegen:end
@@ -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
+ }