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

feat: expose reload events to each route #17

Merged
merged 1 commit into from Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Expand Up @@ -46,9 +46,8 @@ Alternatively, of course you can create your own script:

```typescript
import Router from "https://lib.deno.dev/x/routets@v2/Router.ts"
import { serve } from "https://deno.land/std@0.192.0/http/server.ts"

await serve(await new Router({ root: import.meta.resolve("./.") }))
Deno.serve(await new Router({ root: import.meta.resolve("./.") }))
```

## Advanced Usage
Expand Down
3 changes: 1 addition & 2 deletions examples/serve.ts
@@ -1,6 +1,5 @@
import Router from "../src/Router.ts"
import { serve } from "https://deno.land/std@0.192.0/http/server.ts"

await serve(
Deno.serve(
await new Router({ root: import.meta.resolve("./."), watch: event => console.log("updated") }),
)
2 changes: 2 additions & 0 deletions src/Route.ts
Expand Up @@ -13,6 +13,8 @@ namespace Route {
path: string
/** A clone of the instance of `URLPattern` that is used to match this route. */
pattern: URLPattern
/** An `AsyncIterable` that yields when the browser is needed to be reloaded as per watch. */
reloads: AsyncIterable<void>
}
export type Handler<
ReturnType = Response | void,
Expand Down
25 changes: 20 additions & 5 deletions src/Router.ts
@@ -1,5 +1,6 @@
import Route from "./Route.ts"
import { relative, isAbsolute, toFileUrl, join } from "https://deno.land/std@0.192.0/path/mod.ts"
import { relative, isAbsolute, toFileUrl, join } from "https://deno.land/std@0.221.0/path/mod.ts"
import { subscribe } from "https://deno.land/x/deno_event_iterator@v2.0.2/mod.ts"
import Distree from "https://deno.land/x/distree@v2.0.0/index.ts"

const isFileUrl = (url: string | URL) => {
Expand Down Expand Up @@ -135,9 +136,8 @@ const emit = async (root: URL, routes: Routes) => {
? `./${relative(root.pathname, self.pathname)}`
: self.href
content += `\nimport Router from "${specifierRouter}"`
content += `\nimport { serve } from "https://deno.land/std@0.192.0/http/server.ts"`
content += `\nconst routetslist = ${Deno.inspect([...map.entries()])} as const`
content += `\nawait serve(await new Router(routetslist))`
content += `\nDeno.serve(await new Router(routetslist))`
await Deno.writeTextFile(join(root.pathname, "serve.gen.ts"), content)
}

Expand All @@ -151,6 +151,16 @@ const thrown = (error: unknown, pathname: string) => {
return new Response(undefined, { status: 500 })
}

const map = async function* <T, U>(
source: AsyncIterable<T>,
transformer: (item: T, index: number) => U | Promise<U>,
) {
let i = 0
for await (const item of source) {
yield await transformer(item, i++)
}
}

namespace Router {
export type Handler = (request: Request) => Promise<Response>
export type Options = {
Expand Down Expand Up @@ -203,6 +213,7 @@ class Router {
}

#routes: Routes = []
#reloads = new EventTarget()
async #populate(options: Router.Options | Routetslist): Promise<void | never> {
if (isRoutetslist(options)) {
const routes = options.map<Routes[number]>(([pathname, route]) => {
Expand All @@ -222,6 +233,7 @@ class Router {
const { createCache } = await import("jsr:@deno/cache-dir@^0.8.0")
const { createGraph } = await import("jsr:@deno/graph@^0.69.10")
const cache = createCache()
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
const graph = await createGraph(
toFileUrl(join(root.pathname, "serve.gen.ts")).href,
Expand All @@ -232,13 +244,14 @@ class Router {
.filter(isFileUrl)
.map(url => new URL(url).pathname)
const watcher = Deno.watchFs([root.pathname, ...modules])
this.#reloads.dispatchEvent(new Event("reload"))
for await (const event of watcher) {
if (event.paths.every(path => path.endsWith("serve.gen.ts"))) continue
watcher.close()
routes = await enumerate(optionsNormalized)
if (write) await emit(root, routes)
this.#routes = routes
if (typeof watch === "function") await watch(event)
watcher.close()
}
}
}
Expand All @@ -262,8 +275,10 @@ class Router {
const captured = match.pathname.groups
const slugs = captured
const pattern = new URLPattern(route.pattern)
const response = await route({ request, captured, slugs, path, pattern })
const reloads = map(subscribe.call(this.#reloads, "reload"), event => {})
const response = await route({ request, captured, slugs, path, pattern, reloads })
if (response instanceof Response) return response
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
else if (response === undefined) continue
else return unexpected(response, url.pathname)
} catch (error) {
Expand Down
3 changes: 1 addition & 2 deletions src/routets.ts
@@ -1,5 +1,4 @@
import Router from "./Router.ts"
import { serve } from "https://deno.land/std@0.192.0/http/server.ts"
import { Command } from "https://deno.land/x/cliffy@v0.25.7/command/command.ts"

if (import.meta.main) {
Expand All @@ -23,7 +22,7 @@ if (import.meta.main) {
const { suffix, write, watch } = options

if (options.serve) {
await serve(await new Router({ root, suffix, write, watch }))
Deno.serve(await new Router({ root, suffix, write, watch }))
} else if (write) {
await Router.write({ root, suffix })
}
Expand Down