diff --git a/.github/workflow/main.yml b/.github/workflow/main.yml index c59e42c..6a9e507 100644 --- a/.github/workflow/main.yml +++ b/.github/workflow/main.yml @@ -15,5 +15,7 @@ jobs: bun-version: 1.x - uses: actions/setup-node@v4 with: - node-version: 22.x - - run: deno task ci \ No newline at end of file + node-version-file: .nvmrc + - run: | + deno task ci + deno task test \ No newline at end of file diff --git a/.gitpod.yml b/.gitpod.yml index 4dd5f45..27add7e 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -12,14 +12,12 @@ ports: tasks: - init: > nvm install && - npm install -g pnpm && - pnpm install + npm install -g pnpm @biomejs/biome command: > nvm install && - npm install -g pnpm && - pnpm install + npm install -g pnpm @biomejs/biome vscode: extensions: - - esbenp.prettier-vscode - - denoland.vscode-deno \ No newline at end of file + - denoland.vscode-deno + - biomejs.biome diff --git a/biome.jsonc b/biome.jsonc new file mode 100644 index 0000000..e82a582 --- /dev/null +++ b/biome.jsonc @@ -0,0 +1,30 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.1/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": [] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + } +} diff --git a/components/Movies.tsx b/components/Movies.tsx new file mode 100644 index 0000000..263e7b9 --- /dev/null +++ b/components/Movies.tsx @@ -0,0 +1,47 @@ +/** @jsx precompile */ +/** @jsxImportSource @hono/hono/jsx */ +import type { FC } from "@hono/hono/jsx"; + +export interface WeatherProps { + name: string; + longitude: number; + latitude: number; +} + +/** + * Function to fetch the weather data from the API. + * @returns The weather data as a promise. + */ +async function fetchWeatherData({ + latitude = 43.7001, + longitude = -79.4163, +}: Partial = {}): Promise { + const response = await fetch( + `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,wind_speed_10m&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m` + ); + if (!response.ok) { + throw new Error("Failed to fetch weather data"); + } + return response.json(); +} + +/** + * A JSX component that renders the weather data. + * @returns The JSX element to be rendered. + */ +export const WeatherComponent: FC = async (props) => { + const name = props.name ?? "Toronto"; + const result = await fetchWeatherData(props); + const { current: weather } = result; + + return ( +
+

Current Weather in {name}

+

Time: {new Date(weather.time).toLocaleString()}

+

Temperature: {weather.temperature_2m}°C

+

Wind Speed: {weather.wind_speed_10m} km/h

+
+ ); +}; + +export default WeatherComponent; diff --git a/components/Weather.tsx b/components/Weather.tsx new file mode 100644 index 0000000..b053d8a --- /dev/null +++ b/components/Weather.tsx @@ -0,0 +1,65 @@ +/** @jsx precompile */ +/** @jsxImportSource @hono/hono/jsx */ +import type { FC } from "@hono/hono/jsx"; + +/** + * Interface to define the shape of the current weather data fetched from the API. + */ +interface CurrentWeatherData { + time: string; + temperature_2m: number; + wind_speed_10m: number; +} + +/** + * Interface to define the shape of the API response. + */ +interface WeatherApiResponse { + latitude: number; + longitude: number; + current: CurrentWeatherData; +} + +export interface WeatherProps { + name: string; + longitude: number; + latitude: number; +} + +/** + * Function to fetch the weather data from the API. + * @returns The weather data as a promise. + */ +async function fetchWeatherData({ + latitude = 43.7001, + longitude = -79.4163, +}: Partial = {}): Promise { + const response = await fetch( + `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,wind_speed_10m&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m` + ); + if (!response.ok) { + throw new Error("Failed to fetch weather data"); + } + return response.json(); +} + +/** + * A JSX component that renders the weather data. + * @returns The JSX element to be rendered. + */ +export const WeatherComponent: FC = async (props) => { + const name = props.name ?? "Toronto"; + const result = await fetchWeatherData(props); + const { current: weather } = result; + + return ( +
+

Current Weather in {name}

+

Time: {new Date(weather.time).toLocaleString()}

+

Temperature: {weather.temperature_2m}°C

+

Wind Speed: {weather.wind_speed_10m} km/h

+
+ ); +}; + +export default WeatherComponent; diff --git a/deno.jsonc b/deno.jsonc new file mode 100644 index 0000000..0c77954 --- /dev/null +++ b/deno.jsonc @@ -0,0 +1,26 @@ +{ + "lock": false, + "tasks": { + "biome": "deno run -A npm:@biomejs/biome", + "lint": "deno task biome lint", + "ci": "deno task biome ci", + "test": "deno test -RWN --allow-run=deno,bun,node,npx --clean --trace-leaks", + "start": "deno serve -N --watch mod.tsx", + "dev": "deno serve -N --watch mod.tsx", + "edgedb": "deno run -RWNSE --allow-run=/bin/sh npm:edgedb", + "edgedb:install": "curl https://sh.edgedb.com --proto '=https' -sSf1 | sh", + "edgedb:generate": "deno run -A npm:@edgedb/generate" + }, + "imports": { + "@hono/hono": "jsr:@hono/hono@^4", + "@libs/testing": "jsr:@libs/testing@^2.2.3", + "@std/assert": "jsr:@std/assert@^1", + "@std/expect": "jsr:@std/expect@^1.0.0", + "edgedb": "npm:edgedb@^1.5.12", + "~/": "./" + }, + "compilerOptions": { + "jsx": "precompile", + "jsxImportSource": "@hono/hono/jsx" + } +} \ No newline at end of file diff --git a/edgedb.toml b/edgedb.toml new file mode 100644 index 0000000..efe72b8 --- /dev/null +++ b/edgedb.toml @@ -0,0 +1,5 @@ +[edgedb] +server-version = "5.6" + +[project] +schema-dir = "schema" diff --git a/layouts/Layout.tsx b/layouts/Layout.tsx new file mode 100644 index 0000000..54b6e88 --- /dev/null +++ b/layouts/Layout.tsx @@ -0,0 +1,13 @@ +/** @jsx precompile */ +/** @jsxImportSource @hono/hono/jsx */ +import type { FC } from "@hono/hono/jsx"; + +export const Layout: FC = (props) => { + return ( + + {props.children} + + ); +}; + +export default Layout; \ No newline at end of file diff --git a/mod.tsx b/mod.tsx new file mode 100644 index 0000000..d735af0 --- /dev/null +++ b/mod.tsx @@ -0,0 +1,30 @@ +import { renderToReadableStream } from "@hono/hono/jsx/streaming"; +import { Hono } from '@hono/hono'; + +import { Weather } from "~/pages/weather.tsx"; +import { Home } from "~/pages/home.tsx"; + +const app = new Hono(); +app.get('/', (c) => c.text('Hono!')) +app.get("/jsx", (c) => { + const messages = ["Good Morning", "Good Evening", "Good Night"]; + return c.html(); +}); + +app.get("/stream", (c) => { + const stream = renderToReadableStream( + + ); + return c.body(stream, { + headers: { + "Content-Type": "text/html; charset=UTF-8", + "Transfer-Encoding": "chunked", + }, + }); +}); + +export default app; diff --git a/mod_test.ts b/mod_test.ts new file mode 100644 index 0000000..8e5fa8c --- /dev/null +++ b/mod_test.ts @@ -0,0 +1,9 @@ +import { test } from "@libs/testing"; +import { expect } from "@std/expect"; +import app from "~/mod.tsx"; + +test("bun", "deno", "node")("Basic hono server test", async () => { + const res = await app.request('/'); + expect(res.status).toBe( 200); + expect(await res.text()).toBe("Hono!"); +}); \ No newline at end of file diff --git a/pages/home.tsx b/pages/home.tsx new file mode 100644 index 0000000..9eabedc --- /dev/null +++ b/pages/home.tsx @@ -0,0 +1,19 @@ +/** @jsx precompile */ +/** @jsxImportSource @hono/hono/jsx */ +import type { FC } from "@hono/hono/jsx"; +import { Layout } from "~/layouts/Layout.tsx"; + +export const Home: FC<{ messages: string[] }> = (props) => { + return ( + +

Hello Hono!

+
    + {props.messages.map((message) => { + return
  • {message}!!
  • ; + })} +
+
+ ); +}; + +export default Home; \ No newline at end of file diff --git a/pages/movies.tsx b/pages/movies.tsx new file mode 100644 index 0000000..82a23a9 --- /dev/null +++ b/pages/movies.tsx @@ -0,0 +1,21 @@ +/** @jsx precompile */ +/** @jsxImportSource @hono/hono/jsx */ +import { ErrorBoundary, type FC } from "@hono/hono/jsx"; +import { Suspense } from "@hono/hono/jsx/streaming"; + +import { WeatherComponent, type WeatherProps } from "~/components/Weather.tsx"; +import { Layout } from "~/layouts/Layout.tsx"; + +export const Movies: FC = (props) => { + return ( + + Out of Service}> + Loading...}> + + + + + ); +}; + +export default Movies; diff --git a/pages/weather.tsx b/pages/weather.tsx new file mode 100644 index 0000000..6d6288b --- /dev/null +++ b/pages/weather.tsx @@ -0,0 +1,21 @@ +/** @jsx precompile */ +/** @jsxImportSource @hono/hono/jsx */ +import { ErrorBoundary, type FC } from "@hono/hono/jsx"; +import { Suspense } from "@hono/hono/jsx/streaming"; + +import { WeatherComponent, type WeatherProps } from "~/components/Weather.tsx"; +import { Layout } from "~/layouts/Layout.tsx"; + +export const Weather: FC = (props) => { + return ( + + Out of Service}> + Loading...}> + + + + + ); +}; + +export default Weather; \ No newline at end of file diff --git a/schema/default.esdl b/schema/default.esdl new file mode 100644 index 0000000..7203561 --- /dev/null +++ b/schema/default.esdl @@ -0,0 +1,10 @@ +module default { + type Person { + required name: str; + } + + type Movie { + required title: str; + multi actors: Person; + } +} diff --git a/schema/migrations/00001-m1vegpx.edgeql b/schema/migrations/00001-m1vegpx.edgeql new file mode 100644 index 0000000..96a2669 --- /dev/null +++ b/schema/migrations/00001-m1vegpx.edgeql @@ -0,0 +1,11 @@ +CREATE MIGRATION m1vegpxb3odf7j6rsioor2j5zcassvioypuixdcfujquycuufa3k2a + ONTO initial +{ + CREATE TYPE default::Person { + CREATE REQUIRED PROPERTY name: std::str; + }; + CREATE TYPE default::Movie { + CREATE MULTI LINK actors: default::Person; + CREATE REQUIRED PROPERTY title: std::str; + }; +};