All files / context / http.ts

100.00% Branches 0/0
12.12% Lines 4/33
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
 
x2
x2
x2
x2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


























































































import type { Fn, Step } from '../index.ts';
import { HttpServer } from '../deps.ts';
import { Pipe, Name } from '../format.ts';
import { tcpAddr } from './tcp.ts';
import { Ports } from './port.ts';

/** HTTP context. */
export type Http = Ports & {
  serve (at: number, handler: Fn<[Request]>): Server,
  fetch (url: string|URL): Promise<ReturnType<typeof fetch>>,
};

export type Server = { url?: URL } & Router;

/** URL router. */
export type Router = { url?: string, method?: string, body?: string };

/** URL route. */
export type Route = <T extends Request>(_: unknown, ...handlers: Fn<[T]>[]) => Fn<[T]>;

/** URL route handler. */
export type Handler = Step<Router>;

/** Define HTTP server. */
export const serveHttp = (at: number|string|URL, ...routes: Handler[]) => {
  at = tcpAddr(at);
  return Name(`HTTP ${at.toString()}`,
    function runHttpServer (ctx: Ports = Ports()): HttpServer {
      const { port, hostname = '127.0.0.1' } = at;
      const server = new HttpServer();
      server.listen(`${hostname}:${port}`);
      ctx.ports[port] = { url: at, server };
      server.on('close', () => delete ctx.ports[port]);
      return server;
    }, { at, routes });
}

/** Define URL route. */
export const route = (path, ...routes) => Name(path,
  async function routeRequest (context: Request) {
    if (matchRoute(path)(context.url)) return Pipe(...routes)(context)
  }, { routes });

/** Match URL from request against route patterns. */
export const matchRoute = (expected) => (actual) => false; // TODO

/** Only handle if HTTP method matches. */
export const method = (method, ...routes: Route[]) => Name(method,
  async function onMethod (context: Router) {
    if (context.method === method) return Pipe(...routes)(context);
  }, { method, routes });

/** Only handle if HTTP method is GET. */
export const get = (path, ...routes) => route(path, method('get', ...routes));

/** Only handle if HTTP method is POST. */
export const post = (path, ...routes) => route(path, method('post', ...routes));

/** Set request parameter. */
export const param = (name: string, fn) =>
  async (req: Request & { params: Record<string, unknown> }) =>
    req.params[name] = await fn(req);

/** If condition doesn't match, return with specified code. */
export const guard = (code: number, ...handlers: Handler[]) =>
  async (req: Request & { params: Record<string, unknown> }) => {
    if (!await (Pipe(...handlers)(req))) return code };

// TODO: construct API client from method set
// like `Endpoint` in old `@hackbg/port`
//[>* API endpoint client. <]
//export class Endpoint {
  //url: URL
  //constructor (url: string) {
    //this.url = new URL(url)
  //}
  //get (pathname: string = '', params: Record<string, string|undefined> = {}): Promise<any> {
    //const url = Object.assign(new URL(this.url.toString()), { pathname })
    //for (const [key, value] of Object.entries(params)) {
      //if (value) url.searchParams.set(key, value)
    //}
    //return new Promise((resolve, reject)=>{
      //this._get(url.toString(), res => {
        //let data = ''
        //res.on('data', chunk => data += chunk)
        //res.on('end', () => resolve(JSON.parse(data)))
      //}).on('error', reject)
    //})
  //}
  //_get = http.get
//}