@smuzi/http-server 0.0.5 → 0.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smuzi/http-server",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "HTTP server and routing for JavaScript and TypeScript",
5
5
  "type": "module",
6
6
  "types": "./build/index.d.ts",
@@ -18,8 +18,8 @@
18
18
  "access": "public"
19
19
  },
20
20
  "files": [
21
- "./src",
22
- "./build"
21
+ "build/**/*.js",
22
+ "build/**/*.d.ts"
23
23
  ],
24
24
  "exports": {
25
25
  "./package.json": "./package.json",
@@ -36,12 +36,12 @@
36
36
  "#lib/*": "./src/*"
37
37
  },
38
38
  "dependencies": {
39
- "@smuzi/std": "0.2.6",
40
- "@smuzi/schema": "0.0.5"
39
+ "@smuzi/std": "0.2.8",
40
+ "@smuzi/schema": "0.0.7"
41
41
  },
42
42
  "devDependencies": {
43
- "@smuzi/faker": "0.0.5",
44
- "@smuzi/tests": "0.0.3",
43
+ "@smuzi/faker": "0.0.6",
44
+ "@smuzi/tests": "0.0.5",
45
45
  "@types/node": "^22.15.21",
46
46
  "tsx": "^4.20.6",
47
47
  "typescript": "^7.0.2"
@@ -49,5 +49,6 @@
49
49
  "scripts": {
50
50
  "test": "tsx --conditions=development tests/index.ts",
51
51
  "build": "tsc --project tsconfig.build.json"
52
- }
52
+ },
53
+ "main": "./build/index.js"
53
54
  }
package/src/config.ts DELETED
@@ -1,60 +0,0 @@
1
- import {Option, HttpProtocol, None, HttpResponse, transformError, dump} from "@smuzi/std";
2
- import {ActionErrorHandler, Context, Http1Router} from "./router.js";
3
- import {ServerResponse} from "node:http";
4
-
5
- type Cert = Option<{
6
- key: string,
7
- cert: string,
8
- }>
9
-
10
- export type Http1ServerConfig = {
11
- host: string;
12
- port: number,
13
- router: Http1Router,
14
- cert: Cert,
15
- protocol: HttpProtocol,
16
- errorHandler: ActionErrorHandler<ServerResponse>
17
- }
18
-
19
- type InputHttp1ServerConfig = Partial<Http1ServerConfig> & {
20
- router: Http1Router,
21
- }
22
-
23
- function http1ErrorHandler(context: Context<ServerResponse>, error) {
24
- //TODO: write error to log and remove dump()
25
- dump({msg: "http1ErrorHandler", error})
26
- return HttpResponse.asJson({error:"Internal Server Error"}, 500);
27
- }
28
-
29
- export function buildHttp1ServerConfig({host = 'localhost', port = 8080, router, cert = None(), errorHandler = http1ErrorHandler}: InputHttp1ServerConfig): Http1ServerConfig {
30
- return {
31
- host,
32
- port,
33
- router,
34
- cert,
35
- protocol: cert.someOrNone(HttpProtocol.HTTPS, HttpProtocol.HTTP),
36
- errorHandler,
37
- };
38
- };
39
-
40
- type Http2BaseServerConfig = {
41
- host: string;
42
- port: number,
43
- router: Http1Router,
44
- cert?: Cert
45
- }
46
-
47
- export type Http2ServerConfig = Http2BaseServerConfig & {
48
- cert: Cert,
49
- protocol: HttpProtocol,
50
- }
51
-
52
- export function buildHttp2ServerConfig({host, port, router, cert = None() }: Http2BaseServerConfig): Http2ServerConfig {
53
- return {
54
- host,
55
- port,
56
- router,
57
- cert,
58
- protocol: cert.someOrNone(HttpProtocol.HTTPS, HttpProtocol.HTTP)
59
- };
60
- };
@@ -1,263 +0,0 @@
1
- import http, { IncomingMessage, ServerResponse } from 'node:http';
2
- import https from 'node:https';
3
- import { TLSSocket } from 'node:tls';
4
- import fs from 'node:fs';
5
-
6
- import { methodFromString } from "../router.js";
7
- import {
8
- isArray,
9
- isObject,
10
- isString,
11
- match,
12
- StdRecord,
13
- matchUnknown,
14
- OptionFromNullable,
15
- Some,
16
- Result,
17
- Option,
18
- Err,
19
- Ok,
20
- isNull,
21
- transformError,
22
- StdError,
23
- dump,
24
- HttpResponse,
25
- HttpRequest,
26
- StdMap,
27
- isSome,
28
- isOption,
29
- isResult,
30
- isIterable,
31
- ResponseHttpHeaders, RequestHttpHeaders, asList, asRecord, asMap, querystring, QueryParams,
32
- StdJson, uuid
33
- } from '@smuzi/std';
34
- import { HttpServer, HttpServerRunError, Http1ServerConfig } from "../index.js";
35
- import { log } from 'node:console';
36
-
37
- type NativeServer = any
38
-
39
- export class StdHttp1Server implements HttpServer {
40
- readonly #server: NativeServer
41
-
42
- constructor(server: NativeServer) {
43
- this.#server = server;
44
- }
45
- async close(): Promise<Result<boolean, StdError>> {
46
- return new Promise(resolve => {
47
- this.#server.close((err) => {
48
- return resolve(isNull(err) ? Ok(true) : Err(transformError(err)));
49
- })
50
- })
51
- }
52
- }
53
-
54
- function readRequestBodyAsBuffer(req: IncomingMessage): () => Promise<Result<Buffer, Error>> {
55
- return async () => {
56
- return new Promise((resolve, reject) => {
57
- const chunks: Buffer[] = [];
58
-
59
- req.on("data", (chunk) => {
60
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
61
- });
62
-
63
- req.on("end", () => {
64
- resolve(Ok(Buffer.concat(chunks)));
65
- });
66
-
67
- req.on("error", (err) => reject(Err(err)));
68
- });
69
- }
70
- }
71
-
72
- function readRequestJson(req: IncomingMessage): <T>() => Promise<Result<Option<T>, StdError>> {
73
- return async(encoding: BufferEncoding = "utf-8") => {
74
- return new Promise((resolve) => {
75
- let body = "";
76
-
77
- req.setEncoding(encoding);
78
-
79
- req.on("data", (chunk: string) => {
80
- body += chunk;
81
- });
82
-
83
- req.on("end", () => {
84
- resolve(StdJson.fromString(body));
85
- });
86
-
87
- req.on("error", (err) => resolve(Err(transformError(err))));
88
- });
89
- }
90
- }
91
-
92
- function readRequestInput(req: IncomingMessage): <T extends StdRecord<QueryParams>>() => Promise<Result<T, StdError>> {
93
- return async(encoding: BufferEncoding = "utf-8") => {
94
- return new Promise((resolve) => {
95
- let body = "";
96
-
97
- req.setEncoding(encoding);
98
-
99
- req.on("data", (chunk: string) => {
100
- body += chunk;
101
- });
102
-
103
- req.on("end", () => {
104
- resolve(querystring.fromString(body));
105
- });
106
-
107
- req.on("error", (err) => resolve(Err(transformError(err))));
108
- });
109
- }
110
- }
111
-
112
- function readRawBody(req: IncomingMessage): <T>() => Promise<Result<string, StdError>> {
113
- return async(encoding: BufferEncoding = "utf-8") => {
114
- return new Promise((resolve) => {
115
- let body = "";
116
-
117
- req.setEncoding(encoding);
118
-
119
- req.on("data", (chunk: string) => {
120
- body += chunk;
121
- });
122
-
123
- req.on("end", () => {
124
- resolve(Ok(body));
125
- });
126
-
127
- req.on("error", (err) => resolve(Err(transformError(err))));
128
- });
129
- }
130
- }
131
-
132
- let COUNTER = 0;
133
-
134
- export async function http1ServerRun(config: Http1ServerConfig): Promise<Result<StdHttp1Server, HttpServerRunError>> {
135
- return new Promise((resolve) => {
136
- async function handler(nativeRequest: IncomingMessage, nativeResponse: ServerResponse) {
137
- const methodStr = OptionFromNullable(nativeRequest.method).unwrap();
138
- const fullUrl = nativeRequest.url || "/";
139
- const isHttps = nativeRequest.socket instanceof TLSSocket;
140
- const urlObj = new URL(fullUrl, (isHttps ? "http" : "https") + `://${nativeRequest.headers.host}`);
141
- const path = OptionFromNullable(urlObj.pathname).unwrap();
142
- const request = {
143
- path: path.replace(/^\//, '').replace(/\/$/, ''),
144
- method: methodFromString(methodStr).unwrap(`Error: undefined http method '${methodStr}'`),
145
- };
146
-
147
- const routeMatched = config.router.match(request);
148
-
149
- let context = {
150
- request: new HttpRequest({
151
- method: request.method,
152
- path: request.path,
153
- query: () => new StdRecord<QueryParams>(Object.fromEntries(urlObj.searchParams.entries())),
154
- headers: new RequestHttpHeaders(nativeRequest.headers as any),
155
- buffer: readRequestBodyAsBuffer(nativeRequest),
156
- body: readRawBody(nativeRequest),
157
- json: readRequestJson(nativeRequest),
158
- form: readRequestInput(nativeRequest),
159
- }),
160
- response: nativeResponse,
161
- pathParams: routeMatched.pathParams,
162
- };
163
-
164
- let response;
165
- try {
166
- response = await routeMatched.action(context)
167
- } catch (error) {
168
- response = await config.errorHandler(context, error);
169
- }
170
-
171
-
172
- if (isOption(response)) {
173
- response = response.someOr("");
174
- } else if(isResult(response)) {
175
- response = response.unsafeSource();
176
- }
177
-
178
- if (isNull(response)) {
179
- return;
180
- }
181
-
182
- const handlers = new Map();
183
-
184
- handlers.set(resp => isString(resp) || resp instanceof Buffer, (response) => {
185
- nativeResponse.writeHead(200, {
186
- "Content-Type": "text/html; charset=utf-8",
187
- });
188
- nativeResponse.end(response);
189
- });
190
-
191
- handlers.set(resp => resp instanceof HttpResponse, (response: HttpResponse) => {
192
- nativeResponse.statusCode = response.status;
193
- nativeResponse.statusMessage = response.statusText;
194
- nativeResponse.setHeaders(response.headers.unsafeSource());
195
- nativeResponse.end(response.body.someOr(""));
196
- });
197
-
198
- handlers.set(resp => resp instanceof StdError, (error: StdError) => {
199
- nativeResponse.statusCode = 500;
200
- nativeResponse.statusMessage = error.message;
201
- nativeResponse.end(error.message);
202
- });
203
-
204
- handlers.set(
205
- (response) => isObject(response) || isArray(response) || asList(response) || asRecord(response) || asMap(response),
206
- (response) => {
207
- //TODO: return respons on top instead of changed nativeResponse inner
208
- nativeResponse.setHeader("Content-Type", "application/json; charset=utf-8" );
209
-
210
- try {
211
- const resp = StdJson.toString(response).match({
212
- Ok: (jsonStr) => ({status: 200, body: jsonStr }),
213
- Err: (err) => ({status: 500 , body: '{"error":"Internal Server Error"}' }),
214
- });
215
- nativeResponse.statusCode = resp.status;
216
- nativeResponse.end(resp.body);
217
- } catch (err) {
218
- nativeResponse.statusCode = 500;
219
- nativeResponse.end('{"error":"Internal Server Error"}');
220
- }
221
- }
222
- );
223
-
224
-
225
- matchUnknown(response, handlers, () => {
226
- nativeResponse.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
227
- nativeResponse.end("Internal Server Error");
228
- }, false);
229
- }
230
-
231
- const server = config.cert.match({
232
- Some: cert => {
233
- return https.createServer({
234
- key: fs.readFileSync(cert.key),
235
- cert: fs.readFileSync(cert.cert),
236
- }, handler);
237
- },
238
- None: () => {
239
- return http.createServer(handler);
240
- }
241
- })
242
-
243
-
244
- server.once('error', (nativeError: any) => {
245
- log("Server started on", nativeError);
246
- resolve(Err({
247
- errno: OptionFromNullable(nativeError.errno),
248
- code: OptionFromNullable(nativeError.code),
249
- syscall: OptionFromNullable(nativeError.syscall),
250
- path: OptionFromNullable(nativeError.path),
251
- port: OptionFromNullable(nativeError.port),
252
- address: OptionFromNullable(nativeError.address),
253
- }));
254
- });
255
-
256
- server.listen(config.port, () => {
257
- log("Server started on " + config.protocol + "://" + config.host + ":" + config.port)
258
-
259
- resolve(Ok(new StdHttp1Server(server)));
260
- });
261
-
262
- });
263
- }
@@ -1,125 +0,0 @@
1
- import http2, { Http2SecureServer, Http2Server, IncomingHttpHeaders, ServerHttp2Stream } from 'node:http2';
2
- import fs from 'node:fs';
3
-
4
- import { methodFromString } from "../router.js";
5
- import { isArray, isObject, isString, match, matchUnknown, OptionFromNullable, Some, Result, Option, Err, Ok, isNull, transformError, StdError, dump, HttpResponse, StdJson } from '@smuzi/std';
6
- import { HttpServer, HttpServerRunError, Http2ServerConfig } from "../index.js";
7
-
8
- type NativeServer = Http2SecureServer | Http2Server;
9
-
10
- export class StdHttp2Server implements HttpServer {
11
- readonly #server: NativeServer
12
-
13
- constructor(server: NativeServer) {
14
- this.#server = server;
15
- }
16
- async close(): Promise<Result<boolean, StdError>> {
17
- return new Promise(resolve => {
18
- this.#server.close((err) => {
19
- return resolve(isNull(err) ? Ok(true) : Err(transformError(err)));
20
- })
21
- })
22
- }
23
- }
24
-
25
- export function http2ServerRun(config: Http2ServerConfig): Promise<Result<StdHttp2Server, HttpServerRunError>> {
26
- return new Promise((resolve) => {
27
-
28
- const server = config.cert.match({
29
- Some: cert => {
30
- return http2.createSecureServer({
31
- key: fs.readFileSync(cert.key),
32
- cert: fs.readFileSync(cert.cert),
33
- });
34
- },
35
- None: () => {
36
- return http2.createServer();
37
- }
38
- })
39
-
40
-
41
- server.once('error', (nativeError) => {
42
- resolve(Err({
43
- errno: OptionFromNullable(nativeError.errno),
44
- code: OptionFromNullable(nativeError.code),
45
- syscall: OptionFromNullable(nativeError.syscall),
46
- path: OptionFromNullable(nativeError.path),
47
- port: OptionFromNullable(nativeError.port),
48
- address: OptionFromNullable(nativeError.address),
49
- }));
50
- });
51
-
52
- server.on('stream', (stream: ServerHttp2Stream, headers: IncomingHttpHeaders) => {
53
- const methodStr = OptionFromNullable(headers[':method']).unwrap();
54
- const path = OptionFromNullable(headers[':path']).unwrap().replace(/^\//, '').replace(/\/$/, '');
55
- const urlObj = new URL(path, `http://${headers[':authority']}`);
56
-
57
- const request = {
58
- path: path,
59
- method: methodFromString(methodStr).unwrap(`Error: undefined http method '${methodStr}'`),
60
- query: urlObj.searchParams,
61
- };
62
-
63
- const response = config.router.match(request)
64
- const handlers = new Map();
65
-
66
-
67
- handlers.set(isString, (response) => {
68
- stream.respond({
69
- 'content-type': 'text/html; charset=utf-8',
70
- ':status': 200,
71
- });
72
- stream.end(response)
73
- });
74
-
75
- handlers.set(resp => resp instanceof HttpResponse, (response: HttpResponse) => {
76
- stream.respond({
77
- 'content-type': 'application/json; charset=utf-8',
78
- ':status': response.status,
79
-
80
- });
81
- });
82
-
83
-
84
-
85
- handlers.set(response => isObject(response) || isArray(response), (response) => {
86
- stream.respond({
87
- 'content-type': 'application/json; charset=utf-8',
88
- ':status': 200,
89
- });
90
-
91
- stream.end(StdJson.toString(response).match({
92
- Ok: (json) => json,
93
- Err: (err) => {
94
- stream.respond({
95
- 'content-type': 'application/json; charset=utf-8',
96
- ':status': 500,
97
- });
98
-
99
- return `{"error":"Internal Server Error"}`;
100
- }
101
- }));
102
-
103
- });
104
-
105
-
106
- matchUnknown(
107
- response,
108
- handlers,
109
- _ => {
110
- stream.respond({
111
- ':status': 500,
112
- })
113
- stream.end('Internal Server Error');
114
- },
115
- false
116
- )
117
-
118
- });
119
-
120
- server.listen(config.port, () => {
121
- resolve(Ok(new StdHttp2Server(server)));
122
- });
123
-
124
- });
125
- }
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- export * from "./drivers/http2Server.js";
2
- export * from "./drivers/http1Server.js";
3
- export * from "./config.js" ;
4
- export * from "./router.js" ;
5
- export * from "./types.js" ;
package/src/router.ts DELETED
@@ -1,257 +0,0 @@
1
- import {
2
- asRegExp,
3
- asString,
4
- match,
5
- MatchedData,
6
- None,
7
- Option,
8
- Some,
9
- HttpMethod,
10
- HttpRequest,
11
- Result,
12
- HttpResponse,
13
- StdError,
14
- StdMap, StdRecord, StdList,
15
- } from "@smuzi/std";
16
- import { ServerResponse } from "node:http";
17
- import { ServerHttp2Stream } from "node:http2";
18
-
19
-
20
- type Request = { path: string, method: HttpMethod };
21
- type P = any;
22
- type ActionPrimitiveResponse = P | StdMap<string> | StdRecord<any> | StdList | Record<PropertyKey, P> | Record<PropertyKey, P>[];
23
- export type ActionResponse = void | ActionPrimitiveResponse | HttpResponse | Option<ActionPrimitiveResponse> | Result<ActionPrimitiveResponse, ActionPrimitiveResponse>;
24
-
25
- export type Action<Resp extends THttpResponse> = (context: Context<Resp>) => ActionResponse | Promise<ActionResponse>
26
- export type PathParam = string | RegExp;
27
- export type ActionErrorHandler<Resp extends THttpResponse> = (context: Context<Resp>, err: any) => ActionResponse | Promise<ActionResponse>
28
-
29
- type THttpResponse = ServerResponse | ServerHttp2Stream
30
- type Route = { path: PathParam, method: HttpMethod };
31
- type GroupRoute = { path: PathParam };
32
- type RouteMatched = MatchedData<Request, Option<{ path: Record<string, string> }>>
33
- type RouteMatchResult<Resp extends THttpResponse> = {
34
- action: Action<Resp>,
35
- pathParams: Option<Record<string, string | number | boolean>>
36
- }
37
-
38
- export type Router<Resp extends THttpResponse, A = Action<Resp>> = {
39
- group: (groupRouter: Router<Resp>) => void
40
- getMapRoutes: () => Map<Route, (routeData: RouteMatched) => RouteMatchResult<Resp>>
41
- getGroupRoute(): GroupRoute
42
- get: (path: PathParam, action: A) => void
43
- post: (path: PathParam, action: A) => void
44
- put: (path: PathParam, action: A) => void
45
- delete: (path: PathParam, action: A) => void
46
- match: (request: Request) => RouteMatchResult<Resp>
47
- }
48
-
49
- export type Http1Router = Router<ServerResponse>;
50
- export type Http2Router = Router<ServerHttp2Stream>;
51
-
52
- export type Context<Resp extends THttpResponse, Params = unknown,> = {
53
- request: HttpRequest,
54
- response: Resp,
55
- pathParams: Params,
56
- }
57
-
58
- export function processPath(path: PathParam): PathParam {
59
- if (!asString(path)) return path;
60
- if (! /\{[a-zA-Z0-9_]+\}/g.test(path)) return path;
61
-
62
- const pattern = `^${path.replace(/\{([a-zA-Z0-9_]+)\}/g, (_, name) => `(?<${name}>[^/]+)`).replace(/\//g, '\\/')}$`;
63
- return new RegExp(pattern);
64
-
65
- }
66
-
67
- export function contactPaths(path1: PathParam, path2: PathParam): PathParam | never {
68
- const path1AsRegExp = asRegExp(path1);
69
- const path2AsRegExp = asRegExp(path2);
70
-
71
- if (path1AsRegExp || path2AsRegExp) {
72
- const source1 = path1AsRegExp
73
- ? path1.source
74
- : path1;
75
-
76
- const source2 = path2AsRegExp
77
- ? path2.source
78
- : path2;
79
-
80
- return new RegExp(
81
- "^"+(source1 + source2).replaceAll('^', '')
82
- );
83
- }
84
-
85
- return path1 + path2;
86
- }
87
-
88
-
89
- export function toStartWithPattern(input: PathParam): RegExp {
90
- if (asString(input)) {
91
- const escaped = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
92
- return new RegExp(`^${escaped}.*`);
93
- }
94
-
95
- let pattern = input.source;
96
-
97
- if (pattern.endsWith('$')) {
98
- pattern = pattern.slice(0, -1);
99
- }
100
-
101
- return new RegExp(`${pattern}.*`, input.flags);
102
- }
103
-
104
-
105
- export function methodFromString(method: string): Option<HttpMethod> {
106
- const handers = new Map<string, Option<HttpMethod>>([
107
- ['GET', Some(HttpMethod.GET)],
108
- ['POST', Some(HttpMethod.POST)],
109
- ['PUT', Some(HttpMethod.PUT)],
110
- ['DELETE', Some(HttpMethod.DELETE)],
111
- ]);
112
-
113
- return match(
114
- method,
115
- handers,
116
- None(),
117
- false
118
- );
119
- }
120
-
121
- function http1NotFoundHandler(context: Context<ServerResponse>) {
122
- return HttpResponse.asJson({error:"Not Found"}, 404);
123
- }
124
-
125
-
126
- function http2NotFoundHandler(context: Context<ServerHttp2Stream>) {
127
- context.response.respond({
128
- 'content-type': 'application/json; charset=utf-8',
129
- ':status': 404,
130
- });
131
- context.response.end();
132
-
133
- }
134
-
135
-
136
-
137
- // Internal-only surface: lets a parent router recompute a child router's
138
- // (and, recursively, its own nested groups') absolute paths after nesting.
139
- // Not part of the public `Router` type — accessed via a narrow cast.
140
- type Rebasable = { __rebase: (newBase: PathParam) => void };
141
-
142
- export function CreateHttpRouter<Resp extends THttpResponse, GR extends Router<Resp>>(
143
- groupRoute: GroupRoute,
144
- notFound: Action<Resp>,
145
- ): Router<Resp> {
146
- const routes = new Map()
147
-
148
- // Every own route/subgroup is registered ONCE at startup. We additionally
149
- // remember each one's path *relative to this router* so that, if this
150
- // router later gets attached to a parent via `.group()`, we can recompute
151
- // every already-registered absolute path exactly once at that moment —
152
- // never again on every match()/request.
153
- const ownRoutes: { route: Route; localPath: PathParam }[] = [];
154
- const ownGroups: { route: GroupRoute; localGroupPath: PathParam; childRouter: Router<any> }[] = [];
155
-
156
- const computeAbsolute = (localPath: PathParam) =>
157
- processPath(contactPaths(groupRoute.path, localPath));
158
-
159
- const add = (route: Route, action: any) => {
160
- const localPath = route.path;
161
- route.path = computeAbsolute(localPath);
162
- ownRoutes.push({ route, localPath });
163
-
164
- routes.set(route, (routeData: RouteMatched) => {
165
- return {
166
- action,
167
- pathParams: routeData.params.flatByKey("path"),
168
- };
169
- })
170
- };
171
-
172
- const addGroup = (localGroupPath: PathParam, childRouter: Router<any>, action: any) => {
173
- const route: GroupRoute = { path: computeAbsolute(toStartWithPattern(localGroupPath)) };
174
- ownGroups.push({ route, localGroupPath, childRouter });
175
- routes.set(route, action)
176
- };
177
-
178
- // Recomputes this router's own base plus every route/subgroup registered
179
- // on it so far, then recurses into any already-nested group routers so
180
- // the whole subtree stays consistent — called once, when this router is
181
- // attached to a parent via `.group()`.
182
- const rebase = (newBase: PathParam) => {
183
- groupRoute.path = newBase;
184
-
185
- for (const entry of ownRoutes) {
186
- entry.route.path = computeAbsolute(entry.localPath);
187
- }
188
-
189
- for (const entry of ownGroups) {
190
- entry.route.path = computeAbsolute(toStartWithPattern(entry.localGroupPath));
191
- (entry.childRouter as unknown as Rebasable).__rebase(
192
- contactPaths(newBase, entry.localGroupPath)
193
- );
194
- }
195
- };
196
-
197
- const router: Router<Resp> & Rebasable = {
198
- get(path, action) {
199
- add({ path, method: HttpMethod.GET }, action)
200
- },
201
- post(path, action) {
202
- add({ path, method: HttpMethod.POST }, action)
203
- },
204
- put(path, action) {
205
- add({ path, method: HttpMethod.PUT }, action)
206
- },
207
- delete(path, action) {
208
- add({ path, method: HttpMethod.DELETE }, action)
209
- },
210
- group(groupRouter: GR) {
211
- const localGroupPath = groupRouter.getGroupRoute().path;
212
-
213
- addGroup(localGroupPath, groupRouter, (routeData: RouteMatched) => {
214
- return groupRouter.match(routeData.val);
215
- });
216
-
217
- // Re-derive every path already registered on the nested router
218
- // (and anything nested under IT) against our current absolute base.
219
- (groupRouter as unknown as Rebasable).__rebase(
220
- contactPaths(groupRoute.path, localGroupPath)
221
- );
222
- },
223
- getMapRoutes() {
224
- return routes;
225
- },
226
- getGroupRoute(): GroupRoute {
227
- return groupRoute;
228
- },
229
- match(request: Request) {
230
- return match(request, this.getMapRoutes(), (routeData: RouteMatched) => {
231
- return {
232
- action: notFound,
233
- pathParams: routeData.params.flatByKey("path"),
234
- } as RouteMatchResult<Resp>;
235
- })
236
- },
237
- __rebase: rebase,
238
- };
239
-
240
- return router;
241
- }
242
-
243
-
244
-
245
- export function CreateHttp1Router(
246
- groupRoute: GroupRoute,
247
- notFound: Action<ServerResponse> = http1NotFoundHandler,
248
- ): Http1Router {
249
- return CreateHttpRouter<ServerResponse, Http1Router>(groupRoute, notFound);
250
- }
251
-
252
- // export function CreateHttp2Router(
253
- // groupRoute: GroupRoute,
254
- // notFound: Action<ServerHttp2Stream> = http2NotFoundHandler
255
- // ): Http2Router {
256
- // return CreateHttpRouter<ServerHttp2Stream, Http2Router>(groupRoute, notFound);
257
- // }
package/src/types.ts DELETED
@@ -1,14 +0,0 @@
1
- import { Option, Result, StdError } from "@smuzi/std";
2
-
3
- export interface HttpServer {
4
- close(): Promise<Result<boolean, StdError>>
5
- }
6
-
7
- export type HttpServerRunError = {
8
- errno: Option<number>,
9
- code: Option<string>,
10
- syscall: Option<string>,
11
- path: Option<string>,
12
- port: Option<string>,
13
- address: Option<string>,
14
- };