@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 +21 -0
- package/README.md +110 -0
- package/dist/index.d.ts +408 -0
- package/dist/index.js +838 -0
- package/dist/index.js.map +1 -0
- package/dist/testing.d.ts +57 -0
- package/dist/testing.js +29 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-B_SUwInq.d.ts +217 -0
- package/package.json +68 -0
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
|
package/dist/index.d.ts
ADDED
|
@@ -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 };
|