Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minimal HTTP router in JSX #3

Merged
merged 5 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions components/components.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/**
* @module
*
* @example
*
* ```tsx
* if (import.meta.main) {
* const router = (
* <Router>
* <Get
* pattern="/favicon.ico"
* handle={() =>
* new Response(null, {
* status: 302,
* headers: { Location: "https://deno.com/favicon.ico" },
* })}
* />
* <Get pattern="/" handle={() => new Response("Hello, World!")} />
* <Get pattern="/foo" handle={() => new Response("foo")} />
* <Get pattern="/bar" handle={() => new Response("bar")} />
* <Get pattern="/baz" handle={() => new Response("baz")} />
* </Router>
* );
*
* Deno.serve((request) => router.fetch(request));
* }
* ```
*/

import type {
Handle as IHandle,
Method as IMethod,
Route as IRoute,
} from "rtx/mod.ts";
import { createRouter, Router as CRouter } from "rtx/mod.ts";

// deno run -A components/components.tsx
//
if (import.meta.main) {
const router = (
<Router>
<Get
pattern="/favicon.ico"
handle={() =>
new Response(null, {
status: 302,
headers: { Location: "https://deno.com/favicon.ico" },
})}
/>
<Get pattern="/" handle={() => new Response("Hello, World!")} />
<Get pattern="/foo" handle={() => new Response("foo")} />
<Get pattern="/bar" handle={() => new Response("bar")} />
<Get pattern="/baz" handle={() => new Response("baz")} />
</Router>
);

Deno.serve((request) => router.fetch(request));
}

/**
* ComponentsInterface is the interface for the components.
*/
export type ComponentsInterface = Record<
Capitalize<Lowercase<IMethod>>,
(props: RouteProps) => CRouter
>;

/**
* RouteProps are the props for a route component.
*/
export interface RouteProps {
pattern: string;
handle: IHandle;
}

/**
* Router is the router component.
*/
export function Router(props: { children?: unknown[] }): CRouter {
const router = createRouter();
((props.children) as CRouter[])
?.forEach((child) => {
if (child instanceof CRouter) {
router.use(child);
return;
}

throw new Error("Invalid child of Router");
});

return router;
}

/**
* Route is the route component.
*/
export function Route(props: IRoute): CRouter {
return createRouter().with(props);
}

/**
* Connect is the route component for a CONNECT route.
*/
export function Connect(props: RouteProps): CRouter {
return createRouter().connect(props.pattern, props.handle);
}

/**
* Delete is the route component for a DELETE route.
*/
export function Delete(props: RouteProps): CRouter {
return createRouter().delete(props.pattern, props.handle);
}

/**
* Get is the route component for a GET route.
*/
export function Get(props: RouteProps): CRouter {
return createRouter().get(props.pattern, props.handle);
}

/**
* Head is the route component for a HEAD route.
*/
export function Head(props: RouteProps): CRouter {
return createRouter().head(props.pattern, props.handle);
}

/**
* Options is the route component for a OPTIONS route.
*/
export function Options(props: RouteProps): CRouter {
return createRouter().options(props.pattern, props.handle);
}

/**
* Patch is the route component for a PATCH route.
*/
export function Patch(props: RouteProps): CRouter {
return createRouter().patch(props.pattern, props.handle);
}

/**
* Post is the route component for a POST route.
*/
export function Post(props: RouteProps): CRouter {
return createRouter().post(props.pattern, props.handle);
}

/**
* Put is the route component for a PUT route.
*/
export function Put(props: RouteProps): CRouter {
return createRouter().put(props.pattern, props.handle);
}

/**
* Trace is the route component for a TRACE route.
*/
export function Trace(props: RouteProps): CRouter {
return createRouter().trace(props.pattern, props.handle);
}
4 changes: 4 additions & 0 deletions components/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./components.tsx";

import * as components from "./components.tsx";
components satisfies components.ComponentsInterface;
10 changes: 9 additions & 1 deletion deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@
"lock": false,
"name": "@fartlabs/rtx",
"version": "0.0.1",
"exports": "./mod.ts",
"exports": {
".": "./mod.ts",
"./components": "./components/mod.ts"
},
"imports": {
"@fartlabs/jsonx": "jsr:@fartlabs/jsonx@^0.0.10",
"rtx/": "./"
},
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@fartlabs/jsonx"
},
"tasks": {
"example": "deno run --allow-net examples/farm/farm.ts"
}
Expand Down
Empty file added main.tsx
Empty file.
18 changes: 9 additions & 9 deletions rtx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,21 +184,21 @@ export class Router implements RouterInterface {
/**
* with appends a route to the router.
*/
public with<T extends string>(
handle: Handle<T>,
): this;
public with<T extends string>(route: Route<T>): this;
public with<T extends string>(
match: Match,
handle: Handle<T>,
): this;
public with<T extends string>(
matchOrHandle: Match | Handle<T>,
routeOrMatch: Match | Route<T>,
handle?: Handle<T>,
): this {
if (typeof matchOrHandle === "function" && handle === undefined) {
this.routes.push({ handle: matchOrHandle as Handle<T> });
} else if (handle !== undefined) {
this.routes.push({ handle, match: matchOrHandle as Match });
if (handle === undefined && "handle" in routeOrMatch) {
this.routes.push(routeOrMatch);
} else if (handle !== undefined && !("handle" in routeOrMatch)) {
this.routes.push({ match: routeOrMatch, handle });
} else {
throw new Error("Invalid arguments");
}

return this;
Expand Down Expand Up @@ -234,7 +234,7 @@ export class Router implements RouterInterface {
): this {
return this.with({
method: "CONNECT",
pattern: new URLPattern({ pathname: pattern }),
pattern: pattern ? new URLPattern({ pathname: pattern }) : undefined,
}, handle);
}

Expand Down