@nanobpm/urban 0.76.0 → 0.77.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,9 +4,10 @@
4
4
  import { test } from "node:test";
5
5
  import assert from "node:assert/strict";
6
6
  import { mountSurfaces } from "./surfaces.ts";
7
- import type { EngineClient, HostContext, HttpRequest, HttpResponse } from "../host.ts";
7
+ import type { EngineClient, HostContext, HttpResponse } from "../host.ts";
8
8
  import { DataLayer } from "./datasource.ts";
9
9
  import { createLogger } from "../logger.ts";
10
+ import type { Route } from "../router.ts";
10
11
 
11
12
  function ctxWith(surfaces: Record<string, unknown>): Parameters<typeof mountSurfaces>[0] {
12
13
  const host: HostContext = {
@@ -63,7 +64,7 @@ const fakeApp: Parameters<typeof mountSurfaces>[1] = {
63
64
  log: createLogger(() => {}),
64
65
  };
65
66
 
66
- async function render(route: { handler: (req: HttpRequest) => HttpResponse | Promise<HttpResponse> }): Promise<string> {
67
+ async function render(route: Route): Promise<string> {
67
68
  const res = await route.handler({
68
69
  method: "GET",
69
70
  path: "/",
@@ -71,6 +72,7 @@ async function render(route: { handler: (req: HttpRequest) => HttpResponse | Pro
71
72
  headers: new Headers(),
72
73
  text: async () => "",
73
74
  });
75
+ assert.ok(res, "handler should not decline");
74
76
  return String(res.body);
75
77
  }
76
78
 
@@ -118,16 +120,18 @@ function appWith(engine: EngineClient): Parameters<typeof mountSurfaces>[1] {
118
120
 
119
121
  /** Invoke a route handler with an optional query string and POST body. */
120
122
  async function call(
121
- route: { handler: (req: HttpRequest) => HttpResponse | Promise<HttpResponse> },
123
+ route: Route,
122
124
  opts: { method?: string; query?: string; body?: string } = {},
123
125
  ): Promise<HttpResponse> {
124
- return route.handler({
126
+ const res = await route.handler({
125
127
  method: opts.method ?? "GET",
126
128
  path: "/",
127
129
  query: new URLSearchParams(opts.query ?? ""),
128
130
  headers: new Headers(),
129
131
  text: async () => opts.body ?? "",
130
132
  });
133
+ assert.ok(res, "handler should not decline");
134
+ return res;
131
135
  }
132
136
 
133
137
  function inboxRoutes(engine: EngineClient) {
@@ -4,12 +4,25 @@
4
4
 
5
5
  import type { HttpHandler, HttpRequest, HttpResponse } from "./host.ts";
6
6
 
7
+ /**
8
+ * A route handler that may DECLINE a matched request by returning `undefined`
9
+ * (or a promise of it). The router then treats the route as if it had not matched
10
+ * and continues to the next route (fall-through). This lets a broad prefix route —
11
+ * e.g. the pages surface's nested static serve at `/` — claim only the requests it
12
+ * can actually satisfy (a real, safe asset) and let everything else (`/healthz`, an
13
+ * app's own trigger routes registered *after* it) resolve to their own handlers,
14
+ * instead of a root catch-all silently swallowing them. A handler that returns a
15
+ * concrete `HttpResponse` (including a 404) still short-circuits as before, so this
16
+ * is backwards-compatible with every existing handler (none return `undefined`).
17
+ */
18
+ export type RouteHandler = (req: HttpRequest) => Promise<HttpResponse | undefined> | HttpResponse | undefined;
19
+
7
20
  export interface Route {
8
21
  method: string;
9
22
  path: string;
10
23
  /** When true, match any path that starts with `path`. */
11
24
  prefix?: boolean;
12
- handler: HttpHandler;
25
+ handler: RouteHandler;
13
26
  /** For diagnostics / inspect(). */
14
27
  source?: string;
15
28
  }
@@ -61,7 +74,13 @@ function matches(route: Route, req: HttpRequest): boolean {
61
74
  export function makeRouter(routes: Route[]): HttpHandler {
62
75
  return async (req: HttpRequest): Promise<HttpResponse> => {
63
76
  for (const route of routes) {
64
- if (matches(route, req)) return route.handler(req);
77
+ if (matches(route, req)) {
78
+ // A handler may DECLINE by returning `undefined` (see RouteHandler): treat the
79
+ // route as unmatched and keep looking, so a broad prefix serve can't shadow a
80
+ // later exact route (`/healthz`, a trigger) it didn't actually satisfy.
81
+ const res = await route.handler(req);
82
+ if (res !== undefined) return res;
83
+ }
65
84
  }
66
85
  return text("not found", 404);
67
86
  };