@katajs/core 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yaseer A. Okino
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # @katajs/core
2
+
3
+ The runtime for [katajs](https://github.com/ookino/katajs) — an opinionated framework for [Hono](https://hono.dev) on [Cloudflare Workers](https://developers.cloudflare.com/workers/).
4
+
5
+ ```bash
6
+ pnpm add @katajs/core hono zod @hono/zod-validator
7
+ ```
8
+
9
+ ## What you get
10
+
11
+ - `defineModule` — declare a unit of code (provides + requires + routes)
12
+ - `createApp` — boot a Hono app from a list of modules with validated dependencies
13
+ - Per-request DI container with lazy resolution and cycle detection
14
+ - `AppError` + `errorMapper` — typed domain errors → HTTP responses
15
+ - `validate({ body, query, param })` — Zod validation that preserves Hono RPC types
16
+ - `inspectModules` — static graph inspection (Mermaid + HTML)
17
+ - `@katajs/core/testing` — `makeTestContainer` for unit tests
18
+
19
+ ## A minimal example
20
+
21
+ ```ts
22
+ import { Hono } from 'hono';
23
+ import { createApp, defineModule, validate } from '@katajs/core';
24
+ import { z } from 'zod';
25
+
26
+ // 1. Declare a module
27
+ const greetingModule = defineModule({
28
+ name: 'greeting',
29
+ provides: {
30
+ greeter: () => ({ greet: (name: string) => `hello, ${name}` }),
31
+ },
32
+ requires: [] as const,
33
+ routes: new Hono().get(
34
+ '/:name',
35
+ ...validate({ param: z.object({ name: z.string() }) }),
36
+ (c) => {
37
+ const { name } = c.req.valid('param');
38
+ return c.json({ msg: c.var.resolve('greeter').greet(name) });
39
+ },
40
+ ),
41
+ prefix: '/hello',
42
+ });
43
+
44
+ // 2. Tell the Registry about it (in src/types.d.ts)
45
+ declare module '@katajs/core' {
46
+ interface Registry {
47
+ greeter: { greet: (name: string) => string };
48
+ }
49
+ }
50
+
51
+ // 3. Boot the app
52
+ const { app } = createApp({
53
+ bindings: {} as { /* your Cloudflare bindings */ },
54
+ modules: [greetingModule],
55
+ routes: (base) => base.route(greetingModule.prefix, greetingModule.routes),
56
+ });
57
+
58
+ export default app;
59
+ export type AppType = typeof app;
60
+ ```
61
+
62
+ ## Public API
63
+
64
+ ```ts
65
+ // Modules
66
+ export { defineModule } from '@katajs/core';
67
+ export type { Module, RoutedModule, ServiceOnlyModule } from '@katajs/core';
68
+
69
+ // App
70
+ export { createApp } from '@katajs/core';
71
+ export type { AppConfig, BaseApp } from '@katajs/core';
72
+
73
+ // Middleware
74
+ export { defineMiddleware } from '@katajs/core';
75
+ export type { DbAdapter, RequestVariables } from '@katajs/core';
76
+
77
+ // Errors
78
+ export { AppError, ValidationError, errorMapper } from '@katajs/core';
79
+
80
+ // Validation
81
+ export { validate } from '@katajs/core';
82
+
83
+ // Devtools
84
+ export { inspectModules } from '@katajs/core';
85
+
86
+ // Augmentable interfaces
87
+ export type { Registry, AppEnv, AppDb, RegistryKey } from '@katajs/core';
88
+
89
+ // Testing helpers
90
+ export { makeTestContainer } from '@katajs/core/testing';
91
+ ```
92
+
93
+ ## Documentation
94
+
95
+ - [Concepts: intro](https://github.com/ookino/katajs/blob/main/docs/concepts/intro.md)
96
+ - [Concepts: modules](https://github.com/ookino/katajs/blob/main/docs/concepts/modules.md)
97
+ - [Concepts: container](https://github.com/ookino/katajs/blob/main/docs/concepts/container.md)
98
+ - [Concepts: registry](https://github.com/ookino/katajs/blob/main/docs/concepts/registry.md)
99
+ - [Concepts: routes](https://github.com/ookino/katajs/blob/main/docs/concepts/routes.md)
100
+ - [Concepts: validation](https://github.com/ookino/katajs/blob/main/docs/concepts/validation.md)
101
+ - [Concepts: errors](https://github.com/ookino/katajs/blob/main/docs/concepts/errors.md)
102
+ - [Concepts: transactions](https://github.com/ookino/katajs/blob/main/docs/concepts/transactions.md)
103
+ - [Concepts: testing](https://github.com/ookino/katajs/blob/main/docs/concepts/testing.md)
104
+ - [Concepts: devtools](https://github.com/ookino/katajs/blob/main/docs/concepts/devtools.md)
105
+ - [Concepts: architecture](https://github.com/ookino/katajs/blob/main/docs/concepts/architecture.md)
106
+ - [Showcase example](https://github.com/ookino/katajs/tree/main/examples/showcase)
107
+
108
+ ## License
109
+
110
+ [MIT](./LICENSE) © Yaseer A. Okino
@@ -0,0 +1,408 @@
1
+ import { Hono, MiddlewareHandler, Context } from 'hono';
2
+ import { P as ProvidesMap, R as RequiresList, C as ConsumerSpec, M as ModuleContainer, S as ServiceFactory, a as RequestContainer, Q as QueuesRegistry, b as QueueDeclaration } from './types-B_SUwInq.js';
3
+ export { A as AppDb, c as AppEnv, B as BaseContainer, d as ConsumerBatchHandler, e as ConsumerHandler, f as MergeProvides, g as MessageSchema, h as Registry, i as RegistryKey, j as ResolveOf, k as ResolveProvides, l as ResolvedProvides, m as SendBatchOptions, n as SendOptions, T as TransactionalContainer, o as TypedQueue, V as ValidatedBatch, p as ValidatedMessage } from './types-B_SUwInq.js';
4
+ import { zValidator } from '@hono/zod-validator';
5
+ import { ZodSchema } from 'zod';
6
+
7
+ type AnyHono = Hono<any, any, any>;
8
+ /**
9
+ * A services-only module: defines the registry contributions and dependency
10
+ * graph for a feature, but mounts no HTTP routes. Use for cross-cutting
11
+ * concerns like an `events` recorder or an `audit` logger that other modules
12
+ * fan out into. Optionally carries a queue `consumer` so a services-only
13
+ * module can also process queue messages (notifications, indexing, etc.).
14
+ */
15
+ type ServiceOnlyModule<Provides extends ProvidesMap = ProvidesMap, Requires extends RequiresList = RequiresList> = {
16
+ readonly name: string;
17
+ readonly provides: Provides;
18
+ readonly requires: Requires;
19
+ readonly consumer?: ConsumerSpec<any>;
20
+ };
21
+ /**
22
+ * A routed module: services + HTTP routes mounted at a fixed prefix. The
23
+ * routes and prefix are co-required — they always come as a pair. May also
24
+ * carry a queue `consumer` for modules that own both an HTTP surface and a
25
+ * queue handler (e.g., an `orders` module with CRUD routes plus a consumer
26
+ * that processes order events).
27
+ */
28
+ type RoutedModule<Provides extends ProvidesMap = ProvidesMap, Requires extends RequiresList = RequiresList, Routes extends AnyHono = AnyHono, Prefix extends string = string> = ServiceOnlyModule<Provides, Requires> & {
29
+ readonly routes: Routes;
30
+ readonly prefix: Prefix;
31
+ };
32
+ /** Either kind. Used internally by `createApp` and the boot validation. */
33
+ type Module<Provides extends ProvidesMap = ProvidesMap, Requires extends RequiresList = RequiresList> = ServiceOnlyModule<Provides, Requires> | RoutedModule<Provides, Requires>;
34
+ /** Map of service keys to their resolved (return) types. */
35
+ type ProvidesReturns<P extends Record<string, unknown>> = {
36
+ readonly [K in keyof P]: ServiceFactory<P[K]>;
37
+ };
38
+ type DefineSpecBase<PReturns extends Record<string, unknown>, Requires extends RequiresList> = {
39
+ readonly name: string;
40
+ readonly provides: {
41
+ readonly [K in keyof PReturns]: (c: ModuleContainer<PReturns, Requires[number]>) => PReturns[K];
42
+ };
43
+ readonly requires: Requires;
44
+ readonly consumer?: ConsumerSpec<any>;
45
+ };
46
+ /**
47
+ * Declare a feature module. Returns either a `RoutedModule` (when `routes`
48
+ * and `prefix` are provided) or a `ServiceOnlyModule` (when they aren't).
49
+ * The two-overload signature means TypeScript can prove at the type level
50
+ * that `routedModule.routes` is non-optional — no `!` at the call site.
51
+ *
52
+ * DX caveat: in a module with exactly one factory in `provides`,
53
+ * TypeScript's self-referential inference of `PReturns` weakens — `c.resolve`
54
+ * may not reject undeclared keys at compile time inside that lone factory.
55
+ * The runtime boot-time checks (§10) still catch every error.
56
+ */
57
+ declare function defineModule<PReturns extends Record<string, unknown>, Requires extends RequiresList, Routes extends AnyHono, Prefix extends string>(spec: DefineSpecBase<PReturns, Requires> & {
58
+ readonly routes: Routes;
59
+ readonly prefix: Prefix;
60
+ }): RoutedModule<ProvidesReturns<PReturns>, Requires, Routes, Prefix>;
61
+ declare function defineModule<PReturns extends Record<string, unknown>, Requires extends RequiresList>(spec: DefineSpecBase<PReturns, Requires>): ServiceOnlyModule<ProvidesReturns<PReturns>, Requires>;
62
+
63
+ /**
64
+ * Hono Variables shape contributed by katajs's container middleware.
65
+ *
66
+ * `resolve`, `withTransaction`, and `queues` are convenience handles mounted
67
+ * directly on `c.var` so route handlers can write
68
+ * `c.var.resolve('postService')` and `c.var.queues.orders.send(...)` without
69
+ * digging into `c.var.container`.
70
+ */
71
+ type RequestVariables = {
72
+ container: RequestContainer;
73
+ requestId: string;
74
+ resolve: RequestContainer['resolve'];
75
+ withTransaction: RequestContainer['withTransaction'];
76
+ queues: QueuesRegistry;
77
+ };
78
+ /**
79
+ * Adapter contract for the database layer. `create` is called once per request
80
+ * to produce the per-request client; `runTransaction` (optional) runs a callback
81
+ * inside a transaction with a transaction-bound client (`txDb`).
82
+ *
83
+ * The `db` and `txDb` types are intentionally loose (`any`) at the adapter
84
+ * boundary because, e.g., Drizzle's `DrizzleClient` and `DrizzleTx` are
85
+ * different types with the same query API. User code should rely on the
86
+ * augmented `AppDb` interface to type `c.db`, treating the client and tx as
87
+ * interchangeable for repository code (per spec §6.4).
88
+ */
89
+ type DbAdapter = {
90
+ create(env: unknown): unknown;
91
+ runTransaction?<T>(db: any, fn: (txDb: any) => Promise<T>): Promise<T>;
92
+ };
93
+ /**
94
+ * Typed-identity helper for user-defined middleware. Lets the user write
95
+ * `c.var.container` (and the shortcuts) without authoring the env generics inline.
96
+ */
97
+ declare function defineMiddleware<Env extends {
98
+ Variables: RequestVariables;
99
+ } = {
100
+ Variables: RequestVariables;
101
+ }>(handler: MiddlewareHandler<Env>): MiddlewareHandler<Env>;
102
+
103
+ type ErrorContext = {
104
+ c: Context;
105
+ requestId: string;
106
+ };
107
+ /**
108
+ * Base class for every domain error in user code.
109
+ *
110
+ * - `super(message)` is the *internal* message — for logs and stack traces.
111
+ * - `publicMessage` is what the client sees.
112
+ * - `publicPayload` is optional structured data merged into the response body.
113
+ */
114
+ declare abstract class AppError extends Error {
115
+ abstract readonly status: number;
116
+ abstract readonly code: string;
117
+ abstract readonly publicMessage: string;
118
+ /**
119
+ * Optional structured payload merged into the response body. Subclasses may
120
+ * override as either a property or a getter.
121
+ */
122
+ get publicPayload(): Record<string, unknown> | undefined;
123
+ constructor(message: string);
124
+ }
125
+ type ValidationIssue = {
126
+ path: (string | number)[];
127
+ message: string;
128
+ };
129
+ declare class ValidationError extends AppError {
130
+ readonly issues: ValidationIssue[];
131
+ readonly status = 400;
132
+ readonly code = "validation_failed";
133
+ readonly publicMessage = "Request validation failed";
134
+ constructor(issues: ValidationIssue[]);
135
+ get publicPayload(): Record<string, unknown>;
136
+ }
137
+ type ErrorMapperOptions = {
138
+ /** Hook invoked for unhandled (non-AppError) errors. Use to log to Sentry, etc. */
139
+ onUnhandled?: (err: unknown, ctx: ErrorContext) => void;
140
+ };
141
+ type ErrorMapperHandler = (err: Error, c: Context) => Response | Promise<Response>;
142
+ /**
143
+ * Build a Hono `onError` handler that renders thrown errors as JSON.
144
+ *
145
+ * - `AppError` subclasses → `{error, message, ...publicPayload, requestId}` at the error's status.
146
+ * - Other errors → safe 500 with `requestId`; `onUnhandled` invoked with the original error.
147
+ *
148
+ * Wired automatically by `createApp` via `app.onError(errorMapper(opts))`.
149
+ */
150
+ declare function errorMapper(opts?: ErrorMapperOptions): ErrorMapperHandler;
151
+
152
+ /**
153
+ * Helper for defining a queue consumer with full contextual typing on the
154
+ * `handle` / `handleBatch` parameters. Drives contextual typing for the
155
+ * inner functions so `message.body` is inferred from the schema instead of
156
+ * widening to `unknown`.
157
+ *
158
+ * export const ordersConsumer = defineConsumer({
159
+ * queue: 'ORDER_QUEUE',
160
+ * schema: OrderEventSchema,
161
+ * async handle(message, c) {
162
+ * message.body; // typed as z.infer<typeof OrderEventSchema>
163
+ * },
164
+ * });
165
+ */
166
+ declare function defineConsumer<TBody>(spec: ConsumerSpec<TBody>): ConsumerSpec<TBody>;
167
+ /**
168
+ * Cloudflare Queues `Message<Body>` shape, duck-typed to avoid a hard dep on
169
+ * `@cloudflare/workers-types`. Only the fields the framework consumes are
170
+ * declared.
171
+ */
172
+ type RawMessage<Body = unknown> = {
173
+ readonly id: string;
174
+ readonly timestamp: Date;
175
+ readonly body: Body;
176
+ readonly attempts: number;
177
+ ack(): void;
178
+ retry(options?: {
179
+ delaySeconds?: number;
180
+ }): void;
181
+ };
182
+ type RawBatch<Body = unknown> = {
183
+ readonly queue: string;
184
+ readonly messages: readonly RawMessage<Body>[];
185
+ ackAll(): void;
186
+ retryAll(options?: {
187
+ delaySeconds?: number;
188
+ }): void;
189
+ };
190
+ /** Public Worker `queue` handler signature. */
191
+ type QueueHandler = (batch: RawBatch, env: unknown, ctx: unknown) => Promise<void>;
192
+ type QueueErrorContext = {
193
+ readonly queue: string;
194
+ readonly messageId?: string;
195
+ readonly attempts?: number;
196
+ };
197
+ type QueueErrorMapperOptions = {
198
+ /** Hook invoked when a handler throws. Use for Sentry / Logflare / etc. */
199
+ onUnhandled?: (err: unknown, ctx: QueueErrorContext) => void;
200
+ };
201
+ type BuildQueueHandlerConfig = {
202
+ readonly modules: readonly Module[];
203
+ readonly registry: ReadonlyMap<string, ServiceFactory<unknown>>;
204
+ readonly db: DbAdapter;
205
+ readonly errorMapper?: QueueErrorMapperOptions;
206
+ /** Override `crypto.randomUUID` for deterministic tests. Used as fallback when message has no `id`. */
207
+ readonly generateRequestId?: () => string;
208
+ };
209
+
210
+ /** Base Hono app produced by `createApp` (middleware + onError, no routes mounted). */
211
+ type BaseApp = Hono<{
212
+ Variables: RequestVariables;
213
+ }>;
214
+ type AppConfig<Modules extends readonly Module[] = readonly Module[], ChainResult extends Hono<any, any, any> = BaseApp> = {
215
+ /**
216
+ * Type-only marker for the bindings shape (Cloudflare env). Pass `{} as YourBindings`.
217
+ * v0.1 doesn't propagate bindings to the Hono generic — augment `AppEnv` if you need it.
218
+ */
219
+ bindings?: unknown;
220
+ /** The DB adapter (e.g., `drizzleAdapter()`). */
221
+ db: DbAdapter;
222
+ /** Modules to compose. Order doesn't affect behaviour but is the boot validation order. */
223
+ modules: Modules;
224
+ /**
225
+ * User middleware that runs after the container middleware and before any
226
+ * route handler. Use this for cross-cutting concerns like logging or auth
227
+ * gates. The error handler is wired automatically via `app.onError`.
228
+ */
229
+ middleware?: MiddlewareHandler[];
230
+ /** Options for the auto-wired errorMapper. Pass `onUnhandled` to log to Sentry, etc. */
231
+ errorMapper?: ErrorMapperOptions;
232
+ /**
233
+ * Options for the queue-side error mapper. Distinct from the HTTP `errorMapper`
234
+ * because queue failures don't render an HTTP response — they retry or DLQ.
235
+ * Pass `onUnhandled` to log queue handler failures to Sentry / Logflare / etc.
236
+ */
237
+ queueErrorMapper?: QueueErrorMapperOptions;
238
+ /** Override `crypto.randomUUID` for deterministic tests. */
239
+ generateRequestId?: () => string;
240
+ /**
241
+ * Producer manifest. Each entry registers a queue this app sends to,
242
+ * surfaced as a typed wrapper at `c.var.queues.<name>.send(body)`.
243
+ * Validates body against the schema before delegating to the underlying
244
+ * Cloudflare binding.
245
+ *
246
+ * queues: {
247
+ * orders: { binding: 'ORDER_QUEUE', schema: OrderEventSchema },
248
+ * }
249
+ *
250
+ * Independent of consumer modules — the consumer can live in this app
251
+ * (single-Worker), in `apps/worker` (monorepo), or be external entirely.
252
+ */
253
+ queues?: Record<string, QueueDeclaration>;
254
+ /**
255
+ * Define the app's HTTP surface. Receives the framework-prepared base app
256
+ * (with container middleware + error mapper already wired) and returns the
257
+ * fully chained app. The chain happens via Hono's native `.route()` /
258
+ * `.get()` / `.post()` etc., so Hono RPC end-to-end types are preserved.
259
+ *
260
+ * routes: (base) => base
261
+ * .get('/health', (c) => c.json({ ok: true }))
262
+ * .route(postsModule.prefix, postsModule.routes)
263
+ *
264
+ * Omit this option to get the bare base app back; you can then chain
265
+ * routes externally (the original two-step pattern).
266
+ */
267
+ routes?: (base: BaseApp) => ChainResult;
268
+ };
269
+ /**
270
+ * Compose modules into a Hono app. Performs three boot-time checks (per spec §10.2):
271
+ * 1. No duplicate `provides` keys across modules.
272
+ * 2. Every key in any module's `requires` is provided by some module.
273
+ * 3. No module dependency cycles.
274
+ *
275
+ * Throws with a self-explanatory message on any failure (per spec §10.4).
276
+ *
277
+ * const { app } = createApp({
278
+ * db: drizzleAdapter({ schema }),
279
+ * modules: [eventsModule, auditModule, postsModule],
280
+ * routes: (base) => base
281
+ * .get('/health', (c) => c.json({ ok: true }))
282
+ * .route(postsModule.prefix, postsModule.routes),
283
+ * });
284
+ * export type AppType = typeof app; // RPC types preserved
285
+ * export default app;
286
+ */
287
+ declare function createApp<const Modules extends readonly Module[], ChainResult extends Hono<any, any, any> = BaseApp>(config: AppConfig<Modules, ChainResult>): {
288
+ app: ChainResult;
289
+ modules: Modules;
290
+ /**
291
+ * Cloudflare Queues handler. Defined when at least one module declares a
292
+ * `consumer:` field; `undefined` otherwise. Wire it into the Worker default
293
+ * export alongside `fetch` to consume queue messages:
294
+ *
295
+ * export default { fetch: app.fetch, queue };
296
+ */
297
+ queue: QueueHandler | undefined;
298
+ };
299
+
300
+ type V<T extends ZodSchema | undefined, Target extends 'json' | 'query' | 'param'> = T extends ZodSchema ? [ReturnType<typeof zValidator<T, Target, any, string>>] : [];
301
+ /**
302
+ * Validate `body`, `query`, and/or `param` against Zod schemas.
303
+ *
304
+ * Returns a tuple of Hono middlewares that the user spreads into a route.
305
+ * Each middleware preserves Hono RPC's typed surface so `c.req.valid('json' |
306
+ * 'query' | 'param')` is correctly typed in handlers downstream.
307
+ *
308
+ * app.post('/posts',
309
+ * ...validate({ body: CreatePostSchema, param: PostIdParam }),
310
+ * async (c) => {
311
+ * const body = c.req.valid('json'); // typed as z.infer<typeof CreatePostSchema>
312
+ * const params = c.req.valid('param'); // typed as z.infer<typeof PostIdParam>
313
+ * }
314
+ * );
315
+ *
316
+ * On validation failure, throws a `ValidationError` (rendered as 400 by the
317
+ * `errorMapper` middleware).
318
+ */
319
+ declare function validate<TBody extends ZodSchema | undefined = undefined, TQuery extends ZodSchema | undefined = undefined, TParam extends ZodSchema | undefined = undefined>(opts: {
320
+ body?: TBody;
321
+ query?: TQuery;
322
+ param?: TParam;
323
+ }): [...V<TBody, 'json'>, ...V<TQuery, 'query'>, ...V<TParam, 'param'>];
324
+
325
+ /**
326
+ * Static snapshot of a module's contribution to the app graph. Doesn't
327
+ * carry runtime state — purely shape data extracted from the user's
328
+ * `defineModule(...)` calls.
329
+ */
330
+ type GraphModule = {
331
+ name: string;
332
+ provides: string[];
333
+ requires: string[];
334
+ prefix?: string;
335
+ hasRoutes: boolean;
336
+ /** Present when the module declares `consumer:` — points at a queue binding. */
337
+ consumer?: GraphConsumer;
338
+ };
339
+ /** Queue consumer attached to a module. */
340
+ type GraphConsumer = {
341
+ /** wrangler binding name for the queue this module consumes. */
342
+ queue: string;
343
+ /** Optional dead-letter binding name. */
344
+ dlq?: string;
345
+ };
346
+ /**
347
+ * Producer manifest entry from `createApp({ queues })`. Producers are app-level
348
+ * (not module-level) so they're inspected separately from modules.
349
+ */
350
+ type GraphProducer = {
351
+ /** The key under `queues:` (also the wrapper name on `c.var.queues.<name>`). */
352
+ name: string;
353
+ /** wrangler binding name (e.g. 'ORDER_QUEUE'). */
354
+ binding: string;
355
+ };
356
+ /** Directed dependency edge: `from` requires service `via`, which `to` provides. */
357
+ type GraphEdge = {
358
+ from: string;
359
+ to: string;
360
+ via: string;
361
+ };
362
+ /** Flattened HTTP route, prefixed with its owning module's mount path. */
363
+ type GraphRoute = {
364
+ method: string;
365
+ path: string;
366
+ module: string;
367
+ };
368
+ type Inspection = {
369
+ modules: GraphModule[];
370
+ edges: GraphEdge[];
371
+ routes: GraphRoute[];
372
+ /** App-level producer manifests, optional. Empty array when no producers passed. */
373
+ producers: GraphProducer[];
374
+ /** A `graph TD` Mermaid source string suitable for embedding in markdown or HTML. */
375
+ mermaid(): string;
376
+ /** Pretty-printed JSON for piping to other tooling. */
377
+ json(): string;
378
+ /** Self-contained HTML page that renders the graph + tables in a browser. */
379
+ html(opts?: HtmlOptions): string;
380
+ };
381
+ type InspectOptions = {
382
+ /**
383
+ * App-level producer manifests, normally the contents of `createApp({ queues })`.
384
+ * Each entry maps a producer name to its wrangler binding. Pass-through to the
385
+ * Inspection's `producers` array so devtools can render producer/consumer pairs.
386
+ */
387
+ producers?: Record<string, {
388
+ binding: string;
389
+ }>;
390
+ };
391
+ type HtmlOptions = {
392
+ /** Title shown at the top of the page. Default: "katajs — module graph". */
393
+ title?: string;
394
+ };
395
+ /**
396
+ * Inspect a list of modules and return everything needed to render the app's
397
+ * structure (graph, dependency edges, route table) without booting the app.
398
+ *
399
+ * Pure / synchronous — safe to call at build time. Use the returned `.html()`
400
+ * to write a self-contained snapshot file, or `.mermaid()` to embed in docs.
401
+ *
402
+ * TODO(Shape B): when we build the live devtools server, it will read the
403
+ * same `Inspection` shape from a `__katajs/graph` debug endpoint and render
404
+ * with an interactive Cytoscape canvas instead of a Mermaid snapshot.
405
+ */
406
+ declare function inspectModules(modules: readonly Module[], options?: InspectOptions): Inspection;
407
+
408
+ export { type AnyHono, type AppConfig, AppError, type BaseApp, type BuildQueueHandlerConfig, ConsumerSpec, type DbAdapter, type ErrorContext, type ErrorMapperHandler, type ErrorMapperOptions, type GraphConsumer, type GraphEdge, type GraphModule, type GraphProducer, type GraphRoute, type HtmlOptions, type InspectOptions, type Inspection, type Module, ModuleContainer, ProvidesMap, QueueDeclaration, type QueueErrorContext, type QueueErrorMapperOptions, type QueueHandler, QueuesRegistry, RequestContainer, type RequestVariables, RequiresList, type RoutedModule, ServiceFactory, type ServiceOnlyModule, ValidationError, type ValidationIssue, createApp, defineConsumer, defineMiddleware, defineModule, errorMapper, inspectModules, validate };