diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..1f5c47b
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,25 @@
+{
+ "parser": "@babel/eslint-parser",
+ "parserOptions": {
+ "ecmaVersion": 2021,
+ "sourceType": "module",
+ "babelOptions": {
+ "presets": [
+ "@babel/preset-env",
+ "@babel/preset-react"
+ ]
+ }
+ },
+ "env": {
+ "browser": true,
+ "node": true
+ },
+ "plugins": [
+ "react"
+ ],
+ "extends": [
+ "eslint:recommended",
+ "plugin:react/recommended"
+ ],
+ "rules": {}
+}
\ No newline at end of file
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..115338d
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,12 @@
+on: push
+
+name: test
+jobs:
+ install:
+ name: test
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: oven-sh/setup-bun@v1
+
+ - run: bun install && bun test
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5c005f9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,46 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# vercel
+.vercel
+
+**/*.trace
+**/*.zip
+**/*.tar.gz
+**/*.tgz
+**/*.log
+package-lock.json
+**/*.bun
+
+server
+
+db.sqlite
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1f39de5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,99 @@
+# hyperwave 🌊
+
+hyperwave is a server-side framework for building web applications.
+
+- fast: tiny payloads with no bloated client side rendering
+- productive: build _really_ fast with htmx, tailwind, tsx, hyperscript
+- ergonomic: best tooling of modern tooling combined with rock solid app architecture
+- portable: compile a binary to deploy anywhere
+
+Choosing hyperwave means embracing a smarter way to develop web applications,
+where ease of use, performance, and developer experience go hand in hand.
+
+### Setup
+
+`bun install && bun run src/db.ts && bun dev`
+
+Visit port 1234 and edit `server.tsx`
+
+---
+
+### Example
+
+This is the endpoint serving our initial landing page:
+
+```typescript
+app.get("/", ({ html }) =>
+ html(
+
+
+
+
+
+
+ ,
+ ),
+);
+```
+
+- The API serves a full HTML document to the client, which includes Tailwind classes and HTMX attributes
+- The response is wrapped in a `` tag, a server-rendered functional component, which takes a `title` prop
+- The button, when clicked, will issue a `GET` request to `/instructions` and replace the content of its parent div with the response.
+- Includes a tiny hyperscript to toggle a class when the button is clicked
+
+---
+
+### Deployment
+
+Build an executable for your current architecture with `bun run build`
+
+`PORT` environment variable is available if needed (default is 1234)
+
+Note: deploy `public/` with the executable, it contains the generated UnoCSS build.
+
+---
+
+### Components
+
+- [bun](https://bun.sh/) provides the bundler, runtime, test runner, and package manager.
+- [SQLite](https://bun.sh/docs/api/sqlite) is production-ready and built into Bun.
+- [hono](https://hono.dev) is a robust web framework with great DX and performance
+- [unoCSS](https://unocss.dev/integrations/cli) is Tailwind-compatible and generates only the styles used in application code.
+- [htmx](https://htmx.org/reference/) gives 99% of the client-side interactivity most apps need.
+- [hyperscript](http://hyperscript.org) is a scripting library for rapid application development.
+- [zod](https://zod.dev/) is a powerful runtime validation library.
+
+---
+
+### Benefits and takeways
+
+**Why bother switching to hyperwave?**
+
+- Drastically reduces time from idea to rendered UI
+- Very little cognitive friction to creating something new, after initial learning curve
+
+**Speed / performance benefit**
+
+- hyperwave is designed to generate the smallest possible payloads
+- Deployment is as simple as compiling and running a binary 😎
+
+**Simplicity**
+
+- Bun saves us a ton of time and effort fighting tooling issues
+- SPAs are over-prescribed and inherently introduce serious costs
+
+**Dev UX benefit**
+
+- Better primitives for quickly building UX
+- Uniform interface simplifies writing and reading code
+
+**Architectural benefit**
+
+- Can scale backend and product independently, loosely coupled
diff --git a/assets/icons/gear.tsx b/assets/icons/gear.tsx
new file mode 100644
index 0000000..c577e21
--- /dev/null
+++ b/assets/icons/gear.tsx
@@ -0,0 +1,12 @@
+export default function Gear() {
+ return (
+
+ );
+}
diff --git a/assets/icons/house.tsx b/assets/icons/house.tsx
new file mode 100644
index 0000000..8b15499
--- /dev/null
+++ b/assets/icons/house.tsx
@@ -0,0 +1,12 @@
+export default function House() {
+ return (
+
+ );
+}
diff --git a/assets/icons/magnify.tsx b/assets/icons/magnify.tsx
new file mode 100644
index 0000000..90a56d9
--- /dev/null
+++ b/assets/icons/magnify.tsx
@@ -0,0 +1,15 @@
+export default function Magnify() {
+ return (
+
+ );
+}
diff --git a/bun.lockb b/bun.lockb
new file mode 100755
index 0000000..9fbe423
Binary files /dev/null and b/bun.lockb differ
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..43ca157
--- /dev/null
+++ b/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "hyperwave",
+ "version": "0.1.0",
+ "scripts": {
+ "build": "bun css && bun build --compile ./src/server.tsx",
+ "css": "unocss \"src/**/*.tsx\" -o public/styles/uno.css",
+ "css:watch": "unocss \"src/**/*.tsx\" -o public/styles/uno.css --watch",
+ "db": "bun run src/db.ts",
+ "dev": "concurrently \"bun css:watch\" \"bun server:watch\"",
+ "prettier": "bunx prettier --write src/ test/ --plugin prettier-plugin-tailwindcss",
+ "server:watch": "bun --watch run src/server.tsx",
+ "test": "bun run test"
+ },
+ "dependencies": {
+ "@unocss/preset-web-fonts": "^0.58.0",
+ "hono": "^3.6.3",
+ "unocss": "^0.58.0",
+ "zod": "^3.23.5"
+ },
+ "devDependencies": {
+ "@unocss/cli": "^0.56.5",
+ "bun-types": "latest",
+ "concurrently": "^8.2.1",
+ "prettier": "^3.1.0",
+ "prettier-plugin-tailwindcss": "^0.5.9"
+ },
+ "module": "src/server.tsx"
+}
diff --git a/public/styles/uno.css b/public/styles/uno.css
new file mode 100644
index 0000000..65b2357
--- /dev/null
+++ b/public/styles/uno.css
@@ -0,0 +1,122 @@
+/* layer: preflights */
+*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;}::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;}
+/* latin-ext */
+@font-face {
+ font-family: 'Lato';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/lato/v24/S6uyw4BMUTPHjxAwXjeu.woff2) format('woff2');
+ unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Lato';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/lato/v24/S6uyw4BMUTPHjx4wXg.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+/* layer: default */
+.fixed{position:fixed;}
+.relative{position:relative;}
+.sticky{position:sticky;}
+.left-10{left:2.5rem;}
+.right-8{right:2rem;}
+.top-0\.5{top:0.125rem;}
+.top-11{top:2.75rem;}
+.m-0{margin:0;}
+.m-auto{margin:auto;}
+.me{margin-inline-end:1rem;}
+.block{display:block;}
+.hidden{display:none;}
+.h-8{height:2rem;}
+.h-full{height:100%;}
+.max-h-14{max-height:3.5rem;}
+.min-h-14{min-height:3.5rem;}
+.w-16{width:4rem;}
+.w-40{width:10rem;}
+.w-80{width:20rem;}
+.w-full{width:100%;}
+.hover\:w-56:hover{width:14rem;}
+.flex{display:flex;}
+.flex-col{flex-direction:column;}
+.cursor-pointer{cursor:pointer;}
+.list-none{list-style-type:none;}
+.items-center{align-items:center;}
+.self-start{align-self:flex-start;}
+.justify-center{justify-content:center;}
+.justify-between{justify-content:space-between;}
+.gap-3{gap:0.75rem;}
+.gap-4{gap:1rem;}
+.gap-8{gap:2rem;}
+.border{border-width:1px;}
+.border-b-1{border-bottom-width:1px;}
+.border-blue-300{--un-border-opacity:1;border-color:rgb(147 197 253 / var(--un-border-opacity));}
+.border-gray-2{--un-border-opacity:1;border-color:rgb(229 231 235 / var(--un-border-opacity));}
+.focus\:border-blue-200:focus{--un-border-opacity:1;border-color:rgb(191 219 254 / var(--un-border-opacity));}
+.rounded-md{border-radius:0.375rem;}
+.border-none{border-style:none;}
+.border-solid{border-style:solid;}
+.border-b-solid{border-bottom-style:solid;}
+.bg-blue-100{--un-bg-opacity:1;background-color:rgb(219 234 254 / var(--un-bg-opacity));}
+.bg-blue-200{--un-bg-opacity:1;background-color:rgb(191 219 254 / var(--un-bg-opacity));}
+.bg-blue-300{--un-bg-opacity:1;background-color:rgb(147 197 253 / var(--un-bg-opacity));}
+.bg-blue-50{--un-bg-opacity:1;background-color:rgb(239 246 255 / var(--un-bg-opacity));}
+.bg-blue-700{--un-bg-opacity:1;background-color:rgb(29 78 216 / var(--un-bg-opacity));}
+.bg-blue-900{--un-bg-opacity:1;background-color:rgb(30 58 138 / var(--un-bg-opacity));}
+.bg-transparent{background-color:transparent;}
+.bg-white{--un-bg-opacity:1;background-color:rgb(255 255 255 / var(--un-bg-opacity));}
+.hover\:bg-blue-400:hover{--un-bg-opacity:1;background-color:rgb(96 165 250 / var(--un-bg-opacity));}
+.hover\:bg-blue-700:hover{--un-bg-opacity:1;background-color:rgb(29 78 216 / var(--un-bg-opacity));}
+.fill-neutral-500{--un-fill-opacity:1;fill:rgb(115 115 115 / var(--un-fill-opacity));}
+.fill-white{--un-fill-opacity:1;fill:rgb(255 255 255 / var(--un-fill-opacity));}
+.p-0{padding:0;}
+.p-4{padding:1rem;}
+.px-10{padding-left:2.5rem;padding-right:2.5rem;}
+.px-4{padding-left:1rem;padding-right:1rem;}
+.py-1{padding-top:0.25rem;padding-bottom:0.25rem;}
+.py-2{padding-top:0.5rem;padding-bottom:0.5rem;}
+.py-3{padding-top:0.75rem;padding-bottom:0.75rem;}
+.py-4{padding-top:1rem;padding-bottom:1rem;}
+.pl-20{padding-left:5rem;}
+.pl-3{padding-left:0.75rem;}
+.pl-6{padding-left:1.5rem;}
+.pr-10{padding-right:2.5rem;}
+.text-left{text-align:left;}
+.text-base{font-size:1rem;line-height:1.5rem;}
+.text-sm{font-size:0.875rem;line-height:1.25rem;}
+.text-neutral-500{--un-text-opacity:1;color:rgb(115 115 115 / var(--un-text-opacity));}
+.text-slate-400{--un-text-opacity:1;color:rgb(148 163 184 / var(--un-text-opacity));}
+.text-white{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity));}
+.hover\:text-white:hover{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity));}
+.font-bold{font-weight:700;}
+.leading-5{line-height:1.25rem;}
+.font-lato{font-family:"Lato";}
+.uppercase{text-transform:uppercase;}
+.no-underline{text-decoration:none;}
+.opacity-0{opacity:0;}
+.group:hover .group-hover\:opacity-100{opacity:1;}
+.shadow-md{--un-shadow:var(--un-shadow-inset) 0 4px 6px -1px var(--un-shadow-color, rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 2px 4px -2px var(--un-shadow-color, rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}
+.shadow-sm{--un-shadow:var(--un-shadow-inset) 0 1px 2px 0 var(--un-shadow-color, rgb(0 0 0 / 0.05));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}
+.outline{outline-style:solid;}
+.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}
+.transition-width{transition-property:width;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}
+.duration-200{transition-duration:200ms;}
+.duration-300{transition-duration:300ms;}
+.duration-50{transition-duration:50ms;}
+@media (min-width: 640px){
+.sm\:ml-4{margin-left:1rem;}
+}
+@media (min-width: 768px){
+.md\:top-14{top:3.5rem;}
+.md\:block{display:block;}
+.md\:w-56{width:14rem;}
+.md\:w-96{width:24rem;}
+.md\:flex-row{flex-direction:row;}
+.md\:pl-60{padding-left:15rem;}
+.md\:pr-10{padding-right:2.5rem;}
+.md\:opacity-100{opacity:1;}
+}
\ No newline at end of file
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
new file mode 100644
index 0000000..b278911
--- /dev/null
+++ b/src/components/Button.tsx
@@ -0,0 +1,20 @@
+type Props = {
+ class?: string;
+ children: any;
+ [string: string]: any;
+};
+
+export default function PinkButton({
+ class: className,
+ children,
+ ...rest
+}: Props) {
+ return (
+
+ );
+}
diff --git a/src/components/Input.tsx b/src/components/Input.tsx
new file mode 100644
index 0000000..8a161cb
--- /dev/null
+++ b/src/components/Input.tsx
@@ -0,0 +1,15 @@
+export default function Input({ class: className, placeholder, ...rest }) {
+ return (
+
+ );
+}
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
new file mode 100644
index 0000000..c35d98a
--- /dev/null
+++ b/src/components/Layout.tsx
@@ -0,0 +1,52 @@
+import Magnify from "../../assets/icons/magnify";
+import Input from "./Input";
+import Nav from "./Nav";
+
+type Props = {
+ title: string;
+ currentPath?: string;
+ children: any;
+};
+
+export default function Layout({ title, children, currentPath }: Props) {
+ return (
+
+
+
+
+
+ {title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/src/components/Nav.tsx b/src/components/Nav.tsx
new file mode 100644
index 0000000..e67f708
--- /dev/null
+++ b/src/components/Nav.tsx
@@ -0,0 +1,17 @@
+import NavItem from "./NavItem";
+import Button from "./Button";
+import { routes } from "../routes";
+
+export default function Nav({ currentPath }: { currentPath: string }) {
+ return (
+
+ );
+}
diff --git a/src/components/NavItem.tsx b/src/components/NavItem.tsx
new file mode 100644
index 0000000..bedd862
--- /dev/null
+++ b/src/components/NavItem.tsx
@@ -0,0 +1,25 @@
+import { Route } from "../routes";
+
+type NavItemProps = {
+ currentPath: string;
+ route: Route;
+};
+
+export default function NavItem({ currentPath, route }: NavItemProps) {
+ return (
+
+
+ {route.icon}
+
+ {route.title}
+
+
+
+ );
+}
diff --git a/src/db.ts b/src/db.ts
new file mode 100644
index 0000000..0ebbda4
--- /dev/null
+++ b/src/db.ts
@@ -0,0 +1,15 @@
+import { Database } from "bun:sqlite";
+
+export const db = new Database("db.sqlite", { create: true });
+
+export const createUsers = () =>
+ db.query(`
+ CREATE TABLE IF NOT EXISTS User (
+ first_name TEXT,
+ last_name TEXT,
+ email TEXT
+ );
+`);
+
+export const addUser = () =>
+ db.query("INSERT INTO User (first_name, last_name, email) VALUES (?, ?, ?);");
diff --git a/src/routes.ts b/src/routes.ts
new file mode 100644
index 0000000..b849945
--- /dev/null
+++ b/src/routes.ts
@@ -0,0 +1,22 @@
+import Gear from "../assets/icons/gear";
+import House from "../assets/icons/house";
+
+export type Route = {
+ title: string;
+ link?: string;
+ childRoutes?: Route[];
+ icon?: any;
+};
+
+export const routes: Route[] = [
+ {
+ title: "Dashboard",
+ link: "/dashboard",
+ icon: House(),
+ },
+ {
+ title: "Settings",
+ link: "/settings",
+ icon: Gear(),
+ },
+];
diff --git a/src/routes/editUserInfo.tsx b/src/routes/editUserInfo.tsx
new file mode 100644
index 0000000..4cf5123
--- /dev/null
+++ b/src/routes/editUserInfo.tsx
@@ -0,0 +1,116 @@
+import { Hono } from "hono";
+import Input from "../components/Input.tsx";
+import Button from "../components/Button.tsx";
+import { addUser, createUsers, db } from "../db.ts";
+import { z } from "zod";
+import { HTTPException } from "hono/http-exception";
+
+const app = new Hono();
+
+createUsers().run();
+
+app.get("/", async ({ html }) => {
+ const me = db.query("select * from User limit 1").get() as {
+ first_name: string;
+ last_name: string;
+ email: string;
+ };
+
+ if (!me || !me.email || !me.first_name || !me.last_name) {
+ addUser().run("hyper@wave.com", "Hyper", "Wave");
+ return html(Refresh pls, you hit a race condition :)
);
+ }
+
+ return html(
+
+
+
Hi, {me.first_name}!
+
Email: {me.email}
+
+
+ Change Info
+
+ ,
+ );
+});
+
+app.post("/:email", async (c) => {
+ const email = c.req.param("email");
+ const body = await c.req.parseBody();
+
+ const userSchema = z.object({
+ email: z.string().email(),
+ firstName: z.string(),
+ lastName: z.string(),
+ });
+
+ try {
+ const validatedBody = userSchema.parse(body);
+ const { email: newEmail, firstName, lastName } = validatedBody;
+
+ db.prepare(
+ "UPDATE USER SET email = $1, first_name = $2, last_name = $3 WHERE email = $4",
+ ).run({
+ $1: newEmail,
+ $2: firstName,
+ $3: lastName,
+ $4: email,
+ });
+
+ return c.html(
+
+
Updated user info.
+
+
,
+ );
+ } catch (error) {
+ console.error(error);
+ return c.html(
+
+
Error: {error.errors[0]?.message ?? "Unknown error"}.
+
+
,
+ );
+ }
+});
+
+export default app;
diff --git a/src/server.tsx b/src/server.tsx
new file mode 100644
index 0000000..188e17b
--- /dev/null
+++ b/src/server.tsx
@@ -0,0 +1,38 @@
+import { Hono } from "hono";
+import { serveStatic } from "hono/bun";
+import { logger } from "hono/logger";
+import Layout from "./components/Layout.tsx";
+import Button from "./components/Button.tsx";
+
+import editUserRoutes from "./routes/editUserInfo.tsx";
+
+const app = new Hono();
+
+app.use("/styles/*", serveStatic({ root: "./public/" }));
+app.use("*", logger());
+
+app.get("/", (c) => c.redirect("/dashboard"));
+app.get("/settings", async ({ html }) => {
+ return html(
+
+ Settings go here
+ ,
+ );
+});
+
+app.get("/dashboard", async ({ html }) =>
+ html(
+
+
+ ,
+ ),
+);
+
+app.route("/editUser", editUserRoutes);
+
+export default {
+ port: process.env.PORT || 1234,
+ fetch: app.fetch,
+};
diff --git a/test/fake.test.ts b/test/fake.test.ts
new file mode 100644
index 0000000..b0c260d
--- /dev/null
+++ b/test/fake.test.ts
@@ -0,0 +1,7 @@
+import { describe, expect, it } from "bun:test";
+
+describe("Smoke test", () => {
+ it("Should be able to run a test", () => {
+ expect(true).toBeTruthy();
+ });
+});
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..4fb3088
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "hono/jsx",
+ "allowImportingTsExtensions": true,
+ "noEmit": true,
+ "types": [
+ "bun-types"
+ ]
+ }
+}
diff --git a/uno.config.ts b/uno.config.ts
new file mode 100644
index 0000000..d0cba67
--- /dev/null
+++ b/uno.config.ts
@@ -0,0 +1,15 @@
+import { defineConfig, presetAttributify, presetWind } from "unocss";
+import presetWebFonts from "@unocss/preset-web-fonts";
+
+export default defineConfig({
+ presets: [
+ presetAttributify(),
+ presetWind(),
+ presetWebFonts({
+ provider: "google",
+ fonts: {
+ lato: "Lato",
+ },
+ }),
+ ],
+});