Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ jr_url_path(raw, pkg, rest) :- jr_url_rest(raw, pkg, rest), substr(rest, 0, 1) =
jr_url_path(raw, pkg, r2) :- jr_url_rest(raw, pkg, rest), substr(rest, 0, 1) = "@",
j = min y : { y = range(0, strlen(rest)), substr(rest, y, 1) = "/" }, r2 = substr(rest, j, strlen(rest) - j).
jr_spec(raw, pkg) :- jr_url_path(raw, pkg, "/mod.ts").
// a published alias and its subpaths (`@hono/hono`, `@hono/hono/factory`): the package's own name. The alias must end
// at a `/` or at the end, so another package that only starts with it (`@hono/hono-x`) is itself
jr_spec(raw, pkg) :- jr_spec(raw, x), router_spec_alias(x, pkg).
jr_spec(raw, cat(pkg, substr(x, strlen(a), strlen(x) - strlen(a)))) :- jr_spec(raw, x), router_spec_alias(a, pkg),
strlen(x) > strlen(a) + 1, substr(x, 0, strlen(a) + 1) = cat(a, "/").

.decl jr_module(c0:symbol)
jr_module(spec) :- router_app_class(spec, _).
Expand Down
4 changes: 4 additions & 0 deletions graph/javascript/engine/config-resolution/knobs.dl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ router_spec_url("https://deno.land/x/hono", "hono").
router_spec_url("https://esm.sh/hono", "hono").
// the JSR name of the package
router_app_class("@hono/hono", "Hono").
// router_spec_alias(Published, Package): a package published under another name, whose subpaths are the package's
// (`@hono/hono/factory`, as an import map's key or after `jsr:`, is `hono/factory`)
.decl router_spec_alias(c0:symbol, c1:symbol)
router_spec_alias("@hono/hono", "hono").
// router_verb(Method, Verb): a route registration and the verb it serves
.decl router_verb(c0:symbol, c1:symbol)
router_verb("get", "GET"). router_verb("post", "POST").
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"imports": {
"@hono/hono": "jsr:@hono/hono@^4",
"@hono/hono-kit/": "./kit/"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// not Hono's factory: the function it returns runs, never the one it is given
type Step = (c: unknown, next: () => Promise<void>) => Promise<void>
export const createMiddleware = (_inner: Step): Step => async (_c, next) => {
await next()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// census: an import map keyed by the JSR name (@hono/hono) and its subpaths imported under that name
// (@hono/hono/factory): createMiddleware's argument is the middleware. Look-alike: a local module whose
// specifier only starts with the JSR name (@hono/hono-kit/factory): its createMiddleware is a project function.
import { Hono } from '@hono/hono'
import { createMiddleware } from '@hono/hono/factory'
import { createMiddleware as kitMiddleware } from '@hono/hono-kit/factory'

const auth = createMiddleware(async (c, next) => {
await next()
})
const counted = kitMiddleware(async (c, next) => {
await next()
})
const app = new Hono()
app.use('/admin/*', auth)
app.get('/admin/users', (c) => c.json([]))
app.get('/open', counted, (c) => c.text('open'))

Deno.serve(app.fetch)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createRoute, type RouteConfig, type RouteHandler } from '@hono/zod-openapi'
import type { MiddlewareHandler } from 'hono'

export function requireKey(): MiddlewareHandler {
return async (c, next) => {
if (!c.req.header('x-key')) return c.text('no key', 401)
await next()
}
}

// A route builder: the definition it is called with is the route, and `.handler(fn)` hands back both, for the app
// to register in one call: `app.openapi(ep.route, ep.handler)`.
export const defineEndpoint = <R extends RouteConfig>(config: R) => {
const route = createRoute(config)
const handler = (fn: RouteHandler<typeof route>) => ({ handler: fn, route: route })
return { handler }
}

// A builder made per API version: the definition is spread into the one it builds, which adds the key check
export const versioned = (_version: string) => {
const define = <R extends RouteConfig>(config: R) => {
const route = createRoute({ ...config, middleware: [requireKey()] as const })
const handler = (fn: RouteHandler<typeof route>) => ({ handler: fn, route })
return { handler }
}
return { define }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// census: create_route, openapi_register, new_openapihono (route builder: a project helper returns { route, handler }
// for each definition and the app registers both: app.openapi(ep.route, ep.handler); a plain builder and a version-scoped
// builder whose definition is spread into createRoute with the builder's own middleware).
// Look-alikes: an endpoint the builder made that nothing registers, and `.openapi(..)` on an object that is no app.
import { OpenAPIHono } from '@hono/zod-openapi'
import { draft, getItem, listItems } from './routes'

const docs = { openapi: (_route: unknown, _handler: unknown) => undefined }
const app = new OpenAPIHono()
app.openapi(getItem.route, getItem.handler)
app.openapi(listItems.route, listItems.handler)
docs.openapi(draft.route, draft.handler)

export default app
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { z } from '@hono/zod-openapi'
import { defineEndpoint, versioned } from './builder'

const v1 = versioned('v1')

export const getItem = defineEndpoint({
method: 'get',
path: '/items/{id}',
request: { params: z.object({ id: z.string() }) },
responses: { 200: { description: 'one item' } },
}).handler(async (c) => c.json({ id: c.req.valid('param').id }, 200))

export const listItems = v1.define({
method: 'get',
path: '/v1/items',
responses: { 200: { description: 'every item' } },
}).handler(async (c) => c.json([], 200))

// built but never registered: no route
export const draft = defineEndpoint({
method: 'post',
path: '/drafts',
responses: { 201: { description: 'made' } },
}).handler(async (c) => c.json({}, 201))
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# hand-reviewed 2026-11-04 against the fixture and the oracle: @hono/hono and @hono/hono/factory named by the import map key; the factory's argument is the middleware of /admin/users; the @hono/hono-kit look-alike is a project function, so what it returns is the middleware of /open
# census: an import map keyed by the JSR name (@hono/hono) and its subpaths imported under that name
entry kit/factory.ts:3:57 http
entry main.ts:16:25 http
entry main.ts:17:27 http
entry main.ts:8:31 http
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# hand-reviewed 2026-11-04 against the fixture and the oracle: @hono/hono and @hono/hono/factory named by the import map key; the factory's argument is the middleware of /admin/users; the @hono/hono-kit look-alike is a project function, so what it returns is the middleware of /open
# census: an import map keyed by the JSR name (@hono/hono) and its subpaths imported under that name
route GET /admin/users main.ts:16:25 main.ts:14:13 1
route GET /open main.ts:17:27 main.ts:14:13 3
mw main.ts:8:31 main.ts:16:25 GET /admin/users 0 main.ts:14:13
mw kit/factory.ts:3:57 main.ts:17:27 GET /open 0 main.ts:14:13
regby main.ts:1:1 main.ts:16:25 GET /admin/users main.ts:14:13
regby main.ts:1:1 main.ts:17:27 GET /open main.ts:14:13
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# hand-reviewed 2026-11-04 against the fixture and the oracle's runtime half (the static half cannot follow a closure's local through the builder and reports both registrations unknown: an oracle gap): GET /items/:id with its param schema, GET /v1/items with the version builder's key check at position 0; the never-registered /drafts and the docs.openapi look-alike are no route
# census: create_route, openapi_register, new_openapihono (route builder: a project helper returns { route, handler }
entry builder.ts:5:10 http
entry routes.ts:11:12 http
entry routes.ts:17:12 http
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# hand-reviewed 2026-11-04 against the fixture and the oracle's runtime half (the static half cannot follow a closure's local through the builder and reports both registrations unknown: an oracle gap): GET /items/:id with its param schema, GET /v1/items with the version builder's key check at position 0; the never-registered /drafts and the docs.openapi look-alike are no route
# census: create_route, openapi_register, new_openapihono (route builder: a project helper returns { route, handler }
route GET /items/:id routes.ts:11:12 index.ts:9:13 0
route GET /v1/items routes.ts:17:12 index.ts:9:13 1
mw builder.ts:5:10 routes.ts:17:12 GET /v1/items 0 index.ts:9:13
schema routes.ts:11:12 param [email protected]:9:22
regby index.ts:1:1 routes.ts:11:12 GET /items/:id index.ts:9:13
regby index.ts:1:1 routes.ts:17:12 GET /v1/items index.ts:9:13
47 changes: 47 additions & 0 deletions graph/typescript/engine/config-resolution/http-router-apps.dl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ hr_url_path(raw, pkg, rest) :- hr_url_rest(raw, pkg, rest), substr(rest, 0, 1) =
hr_url_path(raw, pkg, r2) :- hr_url_rest(raw, pkg, rest), substr(rest, 0, 1) = "@",
j = min y : { y = range(0, strlen(rest)), substr(rest, y, 1) = "/" }, r2 = substr(rest, j, strlen(rest) - j).
hr_spec(raw, pkg) :- hr_url_path(raw, pkg, "/mod.ts").
// a published alias and its subpaths (`@hono/hono`, `@hono/hono/factory`): the package's own name. The alias must end
// at a `/` or at the end, so another package that only starts with it (`@hono/hono-x`) is itself
hr_spec(raw, pkg) :- hr_spec(raw, x), router_spec_alias(x, pkg).
hr_spec(raw, cat(pkg, substr(x, strlen(a), strlen(x) - strlen(a)))) :- hr_spec(raw, x), router_spec_alias(a, pkg),
strlen(x) > strlen(a) + 1, substr(x, 0, strlen(a) + 1) = cat(a, "/").
.decl hr_import_spec(c0:symbol, c1:symbol)
hr_import_spec(ih, s) :- rd_import_path(ih, raw), hr_spec(raw, s).

Expand Down Expand Up @@ -501,6 +506,48 @@ hr_def_param(y, p) :- hr_def_param_need(y), expr_kind("client", k, _, y), expr_k
hr_def_val(e, o) :- hr_def_val(i, o), var_initializer("client", _, i, v), module_export_any("client", m, n, "VARIABLE", v),
import_binds_namespace(ih, "client", m), expr_referenced("client", "IMPORT_BINDING", ih, r),
expr_child("client", e, "RECEIVER", _, r), property_access_name(e, n).
// …a route BUILDER: a project function W returns an object whose method H (a closure of W) hands back `{ route,
// handler }` (`const ep = defineEndpoint({ method, path, .. }).handler(async (c) => ..)`, then
// `app.openapi(ep.route, ep.handler)`). Each such method call is its own route: the definition is what W's call was given
// (createRoute(config) of W's parameter, or of an object W spreads it into, which adds W's own keys), the handler the
// method call's argument (http-router-routes.dl hr_bld_arg). Read only from a registration's `.route` / `.handler`
// argument, never for every object a call returns.
.decl hr_bld_need(c0:symbol)
hr_bld_need(q) :- hr_reg(ce, _, cn), router_openapi(cn), expr_child("client", ce, "ARGUMENT", pos, a), (pos = "0" ; pos = "1"),
property_access_name(a, _), expr_child("client", a, "RECEIVER", _, q).
hr_bld_need(i) :- hr_bld_need(x), hr_denotes_var(x, v), var_initializer("client", _, i, v), i != "".
hr_bld_need(c) :- hr_bld_need(x), expr_kind("client", k, _, x), (expr_kind_is_transparent(k) ; k = "AWAIT_EXPRESSION"),
expr_child("client", x, _, _, c).
// (method call, an expression its callee returns, or one inside it through parentheses and casts)
.decl hr_bld_ret(c0:symbol, c1:symbol)
hr_bld_ret(x, e) :- hr_bld_need(x), call_site("client", "METHOD_CALL", _, _, _, x, _), hr_call_of(x, h), hr_ret_expr(h, e).
hr_bld_ret(x, c) :- hr_bld_ret(x, e), expr_kind("client", k, _, e), expr_kind_is_transparent(k), expr_child("client", e, _, _, c).
// (expression, the method call it is, the method, the object literal it returns)
.decl hr_bld_call(c0:symbol, c1:symbol, c2:symbol, c3:symbol)
hr_bld_call(x, x, h, o) :- hr_bld_ret(x, o), expr_kind("client", "OBJECT_LITERAL", _, o), hr_call_of(x, h), hr_ret_expr(h, _).
hr_bld_call(x, c, h, o) :- hr_bld_need(x), hr_denotes_var(x, v), var_initializer("client", _, i, v), i != "", hr_bld_call(i, c, h, o).
hr_bld_call(x, c, h, o) :- hr_bld_need(x), expr_kind("client", k, _, x), (expr_kind_is_transparent(k) ; k = "AWAIT_EXPRESSION"),
expr_child("client", x, _, _, y), hr_bld_call(y, c, h, o).
// (registration argument `ep.route`, the call of W the method was called on, the createRoute call W made, its argument)
.decl hr_bld_route(c0:symbol, c1:symbol, c2:symbol, c3:symbol)
hr_bld_route(e, c1, d, a) :- hr_reg(ce, _, cn), router_openapi(cn), expr_child("client", ce, "ARGUMENT", "0", e),
property_access_name(e, n), expr_child("client", e, "RECEIVER", _, q), hr_bld_call(q, c, _, o), rd_obj_prop(o, n, v),
hr_denotes_var(v, rv), var_initializer("client", _, d, rv), d != "",
call_site("client", "FUNCTION_CALL", _, _, _, d, _), expr_child("client", d, "METHOD_NAME", _, f),
expr_referenced("client", "IMPORT_BINDING", ih, f), import_binding("client", _, _, orig, _, ih), hr_import_spec(ih, spec),
router_route_def(spec, orig), expr_child("client", d, "ARGUMENT", "0", a),
call_site("client", "METHOD_CALL", _, _, c1, c, _), c1 != "".
// the object createRoute is given in W (in place or a const), with W's own keys
hr_def_val(e, o) :- hr_bld_route(e, _, d, _), hr_def_val(d, o).
hr_def_param_need(a) :- hr_bld_route(_, _, _, a).
// …and what W's call was given: W's parameter passed on, or spread into that object
.decl hr_bld_cfg(c0:symbol, c1:symbol)
hr_bld_cfg(e, y) :- hr_bld_route(e, c1, _, a), hr_def_param(a, p), hr_call_of(c1, w), param_decl("client", _, pos, _, w, p),
expr_child("client", c1, "ARGUMENT", pos, y).
hr_bld_cfg(e, y) :- hr_bld_route(e, c1, _, a), hr_def_obj(a, o), expr_child("client", o, "SPREAD_OPERAND", _, sx), hv_spread_src(sx, z),
hr_def_param(z, p), hr_call_of(c1, w), param_decl("client", _, pos, _, w, p), expr_child("client", c1, "ARGUMENT", pos, y).
hr_def_obj_need(y) :- hr_bld_cfg(_, y).
hr_def_val(e, o2) :- hr_bld_cfg(e, y), hr_def_obj(y, o2).
.decl hr_openapi_def(c0:symbol, c1:symbol)
hr_openapi_def(ce, o) :- hr_reg(ce, _, cn), router_openapi(cn), expr_child("client", ce, "ARGUMENT", "0", x), hr_def_val(x, o).
rd_demand(v) :- hr_openapi_def(_, o), rd_obj_prop(o, k, v), (router_def_key(k, "method") ; router_def_key(k, "path")).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ hr_fprop(a, v) :- hr_farg(a), property_access_name(a, n), expr_child("client", a
hr_fprop(a, v) :- hr_farg(a), expr_referenced("client", "VARIABLE", d, a), var_binding("client", _, "PROPERTY", src, i, d), i != "",
hr_fobj(i, o), rd_obj_prop(o, src, v).
hr_farg(v) :- hr_fprop(_, v).
// a route builder's `.handler` (http-router-apps.dl hr_bld_call): the property is the method's own parameter, so its value
// is that method call's argument (`({ handler: fn, route })` returned by `.handler(fn)`)
.decl hr_bld_arg(c0:symbol, c1:symbol)
hr_bld_arg(a, y) :- hr_farg(a), property_access_name(a, n), expr_child("client", a, "RECEIVER", _, q), hr_bld_call(q, c, h, o),
rd_obj_prop(o, n, v), expr_referenced("client", "PARAMETER", p, v), param_decl("client", _, pos, _, h, p),
expr_child("client", c, "ARGUMENT", pos, y).
hr_farg(y) :- hr_bld_arg(_, y).
.decl hr_wrapper_call(c0:symbol)
hr_wrapper_call(a) :- call_site("client", "METHOD_CALL", cn, _, r, a, _), router_factory_mw(cn), hr_factory_val(r, _).
hr_wrapper_call(a) :- hr_detached(a, n, _), router_factory_mw(n).
Expand All @@ -212,6 +219,7 @@ hr_fn(a, m) :- hr_farg(a), hr_call_of(a, f), hr_ret_expr(f, e), hr_fn(e, m).
hr_fn(a, m) :- hr_farg(a), hr_wrapper_call(a), expr_child("client", a, "ARGUMENT", "0", x), hr_fn(x, m).
hr_fn(a, m) :- hr_farg(a), hr_assign_call(a), expr_child("client", a, "ARGUMENT", "0", x), hr_fn(x, m).
hr_fn(a, m) :- hr_fprop(a, v), hr_fn(v, m).
hr_fn(a, m) :- hr_bld_arg(a, y), hr_fn(y, m).
hr_fn(a, m) :- hr_farg(a), expr_kind("client", k, _, a), expr_kind_is_transparent(k),
expr_child("client", a, _, _, c), hr_fn(c, m).

Expand Down
4 changes: 4 additions & 0 deletions graph/typescript/engine/config-resolution/knobs.dl
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,10 @@ router_spec_url("https://deno.land/x/hono", "hono").
router_spec_url("https://esm.sh/hono", "hono").
// the JSR name of the package
router_app_class("@hono/hono", "Hono").
// router_spec_alias(Published, Package): a package published under another name, whose subpaths are the package's
// (`@hono/hono/factory`, as an import map's key or after `jsr:`, is `hono/factory`)
.decl router_spec_alias(c0:symbol, c1:symbol)
router_spec_alias("@hono/hono", "hono").
// router_verb(Method, Verb): a route registration and the verb it serves
.decl router_verb(c0:symbol, c1:symbol)
router_verb("get", "GET"). router_verb("post", "POST").
Expand Down
Loading