@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.
- package/dist/runtime/core/modules/pages.js +45 -2
- package/dist/runtime/core/modules/runtime.gen.js +1 -1
- package/dist/runtime/core/router.d.ts +14 -2
- package/dist/runtime/core/router.js +8 -2
- package/package.json +1 -1
- package/src/runtime/browser/runtime.browser.js +62 -11
- package/src/runtime/browser/runtime.browser.test.ts +105 -1
- package/src/runtime/core/modules/agent.test.ts +6 -3
- package/src/runtime/core/modules/pages.test.ts +127 -0
- package/src/runtime/core/modules/pages.ts +44 -2
- package/src/runtime/core/modules/runtime.gen.ts +1 -1
- package/src/runtime/core/modules/surfaces.test.ts +8 -4
- package/src/runtime/core/router.ts +21 -2
|
@@ -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,
|
|
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:
|
|
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:
|
|
123
|
+
route: Route,
|
|
122
124
|
opts: { method?: string; query?: string; body?: string } = {},
|
|
123
125
|
): Promise<HttpResponse> {
|
|
124
|
-
|
|
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:
|
|
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))
|
|
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
|
};
|