@shaferllc/keel 0.66.0 → 0.68.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.
Files changed (132) hide show
  1. package/AGENTS.md +167 -0
  2. package/README.md +30 -1
  3. package/bin/keel-mcp.mjs +9 -0
  4. package/dist/core/application.d.ts +5 -5
  5. package/dist/core/application.js +2 -2
  6. package/dist/core/cache.d.ts +82 -5
  7. package/dist/core/cache.js +181 -23
  8. package/dist/core/cli/stubs.d.ts +12 -0
  9. package/dist/core/cli/stubs.js +120 -0
  10. package/dist/core/events.d.ts +129 -5
  11. package/dist/core/events.js +165 -7
  12. package/dist/core/health.d.ts +141 -0
  13. package/dist/core/health.js +226 -0
  14. package/dist/core/helpers.d.ts +9 -3
  15. package/dist/core/helpers.js +11 -3
  16. package/dist/core/index.d.ts +15 -10
  17. package/dist/core/index.js +7 -4
  18. package/dist/core/lock.d.ts +139 -0
  19. package/dist/core/lock.js +215 -0
  20. package/dist/core/logger.d.ts +82 -4
  21. package/dist/core/logger.js +141 -23
  22. package/dist/core/mail.d.ts +128 -7
  23. package/dist/core/mail.js +264 -16
  24. package/dist/core/queue.d.ts +134 -9
  25. package/dist/core/queue.js +304 -14
  26. package/dist/core/storage.d.ts +159 -6
  27. package/dist/core/storage.js +287 -7
  28. package/dist/mcp/server.d.ts +19 -0
  29. package/dist/mcp/server.js +355 -0
  30. package/docs/ai-manifest.json +2472 -0
  31. package/docs/ai.md +128 -0
  32. package/docs/architecture.md +331 -0
  33. package/docs/authentication.md +453 -0
  34. package/docs/authorization.md +167 -0
  35. package/docs/broadcasting.md +137 -0
  36. package/docs/broker.md +500 -0
  37. package/docs/cache.md +558 -0
  38. package/docs/configuration.md +311 -0
  39. package/docs/console.md +356 -0
  40. package/docs/container.md +467 -0
  41. package/docs/controllers.md +265 -0
  42. package/docs/cors.md +51 -0
  43. package/docs/database.md +530 -0
  44. package/docs/debugging.md +129 -0
  45. package/docs/decorators.md +127 -0
  46. package/docs/errors.md +395 -0
  47. package/docs/events.md +496 -0
  48. package/docs/examples/architecture-app.ts +27 -0
  49. package/docs/examples/authentication.ts +61 -0
  50. package/docs/examples/authorization.ts +79 -0
  51. package/docs/examples/broadcasting.ts +60 -0
  52. package/docs/examples/broker-cache-validate.ts +34 -0
  53. package/docs/examples/broker-fault-tolerance.ts +29 -0
  54. package/docs/examples/broker-middleware.ts +27 -0
  55. package/docs/examples/broker.ts +203 -0
  56. package/docs/examples/cache.ts +222 -0
  57. package/docs/examples/configuration.ts +81 -0
  58. package/docs/examples/container.ts +134 -0
  59. package/docs/examples/controllers.ts +86 -0
  60. package/docs/examples/database.ts +118 -0
  61. package/docs/examples/debugging.ts +41 -0
  62. package/docs/examples/decorators.ts +40 -0
  63. package/docs/examples/errors.ts +121 -0
  64. package/docs/examples/events.ts +204 -0
  65. package/docs/examples/factories.ts +84 -0
  66. package/docs/examples/hashing.ts +71 -0
  67. package/docs/examples/health.ts +94 -0
  68. package/docs/examples/helpers.ts +171 -0
  69. package/docs/examples/hooks.ts +54 -0
  70. package/docs/examples/inertia.ts +81 -0
  71. package/docs/examples/locks.ts +120 -0
  72. package/docs/examples/logger.ts +92 -0
  73. package/docs/examples/mail.ts +160 -0
  74. package/docs/examples/middleware.ts +119 -0
  75. package/docs/examples/migrations.ts +126 -0
  76. package/docs/examples/models.ts +239 -0
  77. package/docs/examples/notification.ts +124 -0
  78. package/docs/examples/providers.ts +123 -0
  79. package/docs/examples/queues.ts +254 -0
  80. package/docs/examples/rate-limiting.ts +42 -0
  81. package/docs/examples/redis.ts +99 -0
  82. package/docs/examples/request-response.ts +197 -0
  83. package/docs/examples/routing.ts +186 -0
  84. package/docs/examples/scheduling.ts +62 -0
  85. package/docs/examples/sessions.ts +102 -0
  86. package/docs/examples/static-files.ts +63 -0
  87. package/docs/examples/storage.ts +132 -0
  88. package/docs/examples/templates.ts +58 -0
  89. package/docs/examples/testing.ts +66 -0
  90. package/docs/examples/transformer.ts +141 -0
  91. package/docs/examples/transformers.ts +49 -0
  92. package/docs/examples/url-builder.ts +86 -0
  93. package/docs/examples/validation.ts +102 -0
  94. package/docs/examples/views.tsx +62 -0
  95. package/docs/examples/vite.ts +106 -0
  96. package/docs/factories.md +166 -0
  97. package/docs/getting-started.md +290 -0
  98. package/docs/hashing.md +259 -0
  99. package/docs/health.md +225 -0
  100. package/docs/helpers.md +347 -0
  101. package/docs/hono.md +186 -0
  102. package/docs/hooks.md +118 -0
  103. package/docs/inertia.md +241 -0
  104. package/docs/locks.md +323 -0
  105. package/docs/logger.md +290 -0
  106. package/docs/mail.md +678 -0
  107. package/docs/middleware.md +425 -0
  108. package/docs/migrations.md +476 -0
  109. package/docs/models.md +810 -0
  110. package/docs/notifications.md +474 -0
  111. package/docs/providers.md +363 -0
  112. package/docs/queues.md +679 -0
  113. package/docs/rate-limiting.md +155 -0
  114. package/docs/redis.md +178 -0
  115. package/docs/request-response.md +953 -0
  116. package/docs/routing.md +804 -0
  117. package/docs/scheduling.md +110 -0
  118. package/docs/security.md +85 -0
  119. package/docs/sessions.md +354 -0
  120. package/docs/social-auth.md +174 -0
  121. package/docs/static-files.md +211 -0
  122. package/docs/storage.md +450 -0
  123. package/docs/templates.md +315 -0
  124. package/docs/testing.md +125 -0
  125. package/docs/transformers.md +381 -0
  126. package/docs/url-builder.md +295 -0
  127. package/docs/validation.md +288 -0
  128. package/docs/views.md +267 -0
  129. package/docs/vite.md +434 -0
  130. package/llms-full.txt +17694 -0
  131. package/llms.txt +116 -0
  132. package/package.json +26 -7
@@ -0,0 +1,311 @@
1
+ # Configuration
2
+
3
+ Keel loads configuration from two sources: environment variables (`.env`) and
4
+ config files (`config/*.ts`). Config files read env vars; your app reads config.
5
+
6
+ ## Environment variables
7
+
8
+ `.env` holds environment-specific values and secrets. It ships with:
9
+
10
+ ```
11
+ APP_NAME=Keel
12
+ APP_ENV=local
13
+ APP_DEBUG=true
14
+ APP_URL=http://localhost:3000
15
+ APP_PORT=3000
16
+ ```
17
+
18
+ `.env` is git-ignored. Commit a `.env.example` with safe defaults so teammates
19
+ know what to set.
20
+
21
+ ### The `env()` helper
22
+
23
+ Read env vars with `env()`, which coerces obvious types:
24
+
25
+ ```ts
26
+ import { env } from "@shaferllc/keel/core";
27
+
28
+ env("APP_NAME"); // "Keel"
29
+ env("APP_DEBUG", false); // true (string "true" -> boolean)
30
+ env("APP_PORT", 3000); // 3000 (coerced to number when the fallback is a number)
31
+ env("MISSING", "default"); // "default"
32
+ ```
33
+
34
+ Coercion follows two rules, and they don't behave the same way:
35
+
36
+ - **Booleans are always coerced.** The literal strings `"true"` and `"false"`
37
+ become `true` / `false` regardless of the fallback — even with no fallback at
38
+ all. So `env("APP_DEBUG")` on `APP_DEBUG=true` returns the boolean `true`, not
39
+ the string `"true"`.
40
+ - **Numbers are coerced only when the fallback is a number.** `env("APP_PORT", 3000)`
41
+ returns the number `3000`, but `env("APP_PORT")` returns the *string* `"3000"` —
42
+ without a numeric fallback there's nothing to signal that a number was wanted.
43
+ An empty string is never treated as a number.
44
+
45
+ ```ts
46
+ env("APP_PORT"); // "3000" (string — no numeric fallback)
47
+ env("APP_PORT", 0); // 3000 (number — fallback is numeric)
48
+ env("APP_DEBUG"); // true (boolean, even with no fallback)
49
+ ```
50
+
51
+ The generic defaults to `string`, but the return is asserted to `T` at the
52
+ boundary — the runtime value can be a boolean or number even where the type says
53
+ string. Pass a fallback of the type you expect and the type follows it.
54
+
55
+ Use `env()` **only inside config files**, not scattered through your app. That
56
+ keeps all environment coupling in one layer.
57
+
58
+ ## Config files
59
+
60
+ Each file in `config/` exports a default object and is loaded under its
61
+ filename. `config/app.ts` becomes the `app` namespace:
62
+
63
+ ```ts
64
+ // config/app.ts
65
+ import { env } from "@shaferllc/keel/core";
66
+
67
+ export default {
68
+ name: env("APP_NAME", "Keel"),
69
+ env: env("APP_ENV", "local"),
70
+ debug: env("APP_DEBUG", true),
71
+ url: env("APP_URL", "http://localhost:3000"),
72
+ port: env("APP_PORT", 3000),
73
+ };
74
+ ```
75
+
76
+ Add more files freely — `config/services.ts`, `config/mail.ts` — and they're
77
+ auto-loaded at boot. No registration needed.
78
+
79
+ ## Reading config
80
+
81
+ The quickest way is the global `config()` helper — no container needed:
82
+
83
+ ```ts
84
+ import { config } from "@shaferllc/keel/core";
85
+
86
+ config("app.name"); // "Keel"
87
+ config("app.port", 3000); // with a fallback
88
+ config("services.stripe.key"); // nested access
89
+ ```
90
+
91
+ It resolves against the active application (registered automatically when the
92
+ `Application` is created). There is a matching `app()` helper that returns the
93
+ container:
94
+
95
+ ```ts
96
+ import { app } from "@shaferllc/keel/core";
97
+
98
+ app().make(SomeService);
99
+ ```
100
+
101
+ Both `config()` and `app()` throw if no application has been bootstrapped yet —
102
+ `config()` reaches the repository *through* `app()`, so the error is the same
103
+ `No Keel application has been bootstrapped…`. In a normal single-app process the
104
+ `Application` constructor registers itself, so this only bites in tests or
105
+ scripts that skip the bootstrap.
106
+
107
+ ### The long form
108
+
109
+ Under the hood, `config()` is sugar for resolving the `Config` repository and
110
+ reading with dot notation. You can still do that explicitly:
111
+
112
+ ```ts
113
+ import { Config, app } from "@shaferllc/keel/core";
114
+
115
+ const config = app().make(Config);
116
+
117
+ config.get("app.name"); // "Keel"
118
+ config.get("app.port", 3000); // with a fallback
119
+ config.get("services.stripe.key"); // nested access
120
+ config.set("app.debug", false); // override at runtime
121
+ config.all(); // the whole tree
122
+ ```
123
+
124
+ Note `app()` is a function — call it, then reach into the container
125
+ (`app().make(...)`), not `app.make(...)`.
126
+
127
+ From within the `Application` there's a shortcut:
128
+
129
+ ```ts
130
+ app().config().get("app.name");
131
+ ```
132
+
133
+ There is **no `has()` method** on `Config`. To check for a key, read it with a
134
+ sentinel fallback and compare, or pass the fallback you'd want anyway:
135
+
136
+ ```ts
137
+ const config = app().make(Config);
138
+ if (config.get("services.stripe.key") !== undefined) {
139
+ // configured
140
+ }
141
+ ```
142
+
143
+ ### Missing keys and fallbacks
144
+
145
+ `get()` walks the key segment by segment. If any segment is missing — or a
146
+ segment isn't an object it can descend into — it returns the fallback (or
147
+ `undefined` when you gave none) rather than throwing:
148
+
149
+ ```ts
150
+ config("services.stripe.key", ""); // "" when unset — never throws
151
+ config("nope.at.all"); // undefined
152
+ ```
153
+
154
+ `set()` creates intermediate objects as it goes, so you can write a deep key
155
+ into an empty tree; and `all()` returns the repository's live object by
156
+ reference — mutating it mutates the config. Treat `all()` as read-only.
157
+
158
+ ## How loading works
159
+
160
+ At boot, `Application`:
161
+
162
+ 1. Loads `.env` via `dotenv`.
163
+ 2. Reads every `*.ts` / `*.js` file in `config/`.
164
+ 3. Registers each under its filename in the `Config` repository.
165
+
166
+ So `config/mail.ts` is reachable at `config('mail.*')` with zero wiring. On
167
+ Workers (no filesystem) skip discovery and pass a config object inline —
168
+ `boot(providers, { discoverConfig: false, config })` — and it's merged under its
169
+ top-level keys the same way. See
170
+ [`src/core/application.ts`](../src/core/application.ts) (`loadConfig`) and
171
+ [`src/core/config.ts`](../src/core/config.ts).
172
+
173
+ ---
174
+
175
+ ## API reference
176
+
177
+ ### `env(key, fallback?)`
178
+
179
+ `env<T = string>(key: string, fallback?: T): T`
180
+
181
+ Reads `process.env[key]`, coercing `"true"`/`"false"` to booleans and numeric
182
+ strings to numbers, with a typed fallback when the variable is unset.
183
+
184
+ ```ts
185
+ const debug = env("APP_DEBUG", false); // boolean
186
+ const port = env("APP_PORT", 3000); // number
187
+ const name = env("APP_NAME", "Keel"); // string
188
+ ```
189
+
190
+ **Notes:** returns the fallback (or `undefined`) when the var is not set.
191
+ Boolean coercion always happens; number coercion happens **only when `fallback`
192
+ is a number** and the raw value is non-empty and numeric. Otherwise the raw
193
+ string is returned. The result is asserted to `T`, so at runtime the value may
194
+ not match the declared type unless your fallback matches the intended type.
195
+
196
+ ### `config(key, fallback?)`
197
+
198
+ `config<T = unknown>(key: string, fallback?: T): T`
199
+
200
+ Global helper: resolves the `Config` repository from the active application and
201
+ reads `key` with dot notation.
202
+
203
+ ```ts
204
+ config("app.name"); // unknown -> narrow or cast
205
+ config<number>("app.port", 3000);
206
+ config("services.stripe.key", "");
207
+ ```
208
+
209
+ **Notes:** thin sugar for `app().make(Config).get(key, fallback)`. Throws
210
+ `No Keel application has been bootstrapped…` if there is no active application.
211
+ Returns the fallback (or `undefined`) for any missing key; never throws on a
212
+ missing key.
213
+
214
+ ### `app()`
215
+
216
+ `app(): Application`
217
+
218
+ Returns the active application container — the one registered by the most recent
219
+ `Application` constructor.
220
+
221
+ ```ts
222
+ app().make(Config);
223
+ app().config().get("app.name");
224
+ ```
225
+
226
+ **Notes:** throws `No Keel application has been bootstrapped…` when no
227
+ `Application` has been created. `app` is a function; call it before reaching into
228
+ the container. In a single-app process the current application is set
229
+ automatically at construction, so you rarely register it by hand.
230
+
231
+ ### `Config`
232
+
233
+ The dot-notation config repository. You normally resolve it from the container
234
+ (`app().make(Config)`) rather than constructing it, but the constructor is public
235
+ for tests and standalone use.
236
+
237
+ #### `new Config(items?)`
238
+
239
+ `new Config(items?: ConfigData)`
240
+
241
+ Creates a repository over the given data (default `{}`).
242
+
243
+ ```ts
244
+ const repo = new Config({ app: { name: "Keel", port: 3000 } });
245
+ repo.get("app.port"); // 3000
246
+ ```
247
+
248
+ **Notes:** the object is held by reference, not cloned — later `set()` calls and
249
+ `all()` operate on the same object you passed in.
250
+
251
+ #### `get(key, fallback?)`
252
+
253
+ `get<T = unknown>(key: string, fallback?: T): T`
254
+
255
+ Reads a value by dot-notation key, descending one segment at a time.
256
+
257
+ ```ts
258
+ repo.get("app.name"); // value
259
+ repo.get("app.port", 3000); // fallback if unset
260
+ repo.get<string>("services.key"); // typed read
261
+ ```
262
+
263
+ **Notes:** returns `fallback` (or `undefined`) if any segment is missing or a
264
+ segment isn't an object it can descend into. Never throws for a missing key. The
265
+ value is asserted to `T` — no runtime validation.
266
+
267
+ #### `set(key, value)`
268
+
269
+ `set(key: string, value: unknown): void`
270
+
271
+ Writes a value at a dot-notation key, creating intermediate objects as needed.
272
+
273
+ ```ts
274
+ repo.set("app.debug", false);
275
+ repo.set("services.stripe.key", "sk_test_…"); // creates `services` on the way
276
+ ```
277
+
278
+ **Notes:** mutates the repository in place. If an intermediate segment exists but
279
+ isn't an object (or is `null`), it's overwritten with a fresh object.
280
+
281
+ #### `all()`
282
+
283
+ `all(): ConfigData`
284
+
285
+ Returns the entire config tree.
286
+
287
+ ```ts
288
+ const tree = repo.all();
289
+ ```
290
+
291
+ **Notes:** returns the live internal object **by reference**, not a copy —
292
+ mutating the result mutates the repository. Treat it as read-only.
293
+
294
+ > There is no `has()` method. Check presence with `get(key) !== undefined`, or
295
+ > pass the fallback you want when the key is absent.
296
+
297
+ ### Interfaces & types
298
+
299
+ #### `ConfigData`
300
+
301
+ `type ConfigData = Record<string, unknown>`
302
+
303
+ The shape of the config tree: a plain string-keyed object, nested arbitrarily.
304
+ Use it to type a config object you build and merge in yourself (for example, the
305
+ inline config passed to `boot({ discoverConfig: false, config })` on Workers).
306
+
307
+ ```ts
308
+ const data: ConfigData = {
309
+ app: { name: "Keel", port: 3000 },
310
+ };
311
+ ```
@@ -0,0 +1,356 @@
1
+ # The Console
2
+
3
+ Keel ships with a console for running the server and generating code. The binary
4
+ is `bin/keel.ts`; npm scripts wrap it with `tsx`.
5
+
6
+ ```bash
7
+ npm run keel <command> [args]
8
+ # e.g.
9
+ npm run keel routes
10
+ ```
11
+
12
+ You can also invoke it directly: `npx tsx bin/keel.ts <command>`.
13
+
14
+ Every command boots the full application first — the same container, config, and
15
+ providers your HTTP requests get. The `serve` and `routes` commands use that
16
+ booted app; the `make:*` generators don't need it, they just stamp files onto
17
+ disk. Commands are wired up with [commander](https://github.com/tj/commander.js)
18
+ in [`src/core/cli/index.ts`](../src/core/cli/index.ts), and the code-generation
19
+ templates live in [`src/core/cli/stubs.ts`](../src/core/cli/stubs.ts).
20
+
21
+ ## Command reference
22
+
23
+ | Command | Argument | Generates / does |
24
+ | --- | --- | --- |
25
+ | `serve` | `--port <n>` | Start the HTTP server |
26
+ | `routes` | — | List every registered route |
27
+ | `make:controller` | `<name>` `[-r]` | `app/Controllers/<Name>Controller.ts` |
28
+ | `make:provider` | `<name>` | `app/Providers/<Name>ServiceProvider.ts` |
29
+ | `make:middleware` | `<name>` | `app/Http/Middleware/<name>Middleware.ts` |
30
+ | `make:factory` | `<model>` | `database/factories/<Model>Factory.ts` |
31
+ | `make:seeder` | `<name>` | `database/seeders/<Name>Seeder.ts` |
32
+ | `make:job` | `<name>` | `app/Jobs/<Name>Job.ts` |
33
+ | `make:notification` | `<name>` | `app/Notifications/<Name>Notification.ts` |
34
+ | `make:transformer` | `<name>` `[-m <model>]` | `app/Transformers/<Name>Transformer.ts` |
35
+ | `mcp` | — | Start the [MCP server](./ai.md) for AI agents (stdio) |
36
+
37
+ Every generator normalizes the name you pass and refuses to overwrite an existing
38
+ file (see [Generator safety](#generator-safety)).
39
+
40
+ ## Runtime commands
41
+
42
+ ### `serve`
43
+
44
+ Start the HTTP server.
45
+
46
+ ```bash
47
+ npm run keel serve
48
+ npm run keel serve --port 8080 # override the port
49
+ ```
50
+
51
+ `serve` builds the [`HttpKernel`](./controllers.md) (reusing a container-bound
52
+ one if you've registered your own, otherwise constructing a fresh one), hands its
53
+ Hono app to `@hono/node-server`, and listens. On boot it prints:
54
+
55
+ ```
56
+ ⚓ Keel listening on http://localhost:3000
57
+ ```
58
+
59
+ The port resolves in this order: the `--port` flag, then `config('app.port')`
60
+ (from the `APP_PORT` env var), then `3000`. The app name in the banner comes from
61
+ `config('app.name')`, defaulting to `Keel`. For a watch-mode dev server that
62
+ restarts on change, use `npm run dev` (which is `serve` under `tsx watch`).
63
+
64
+ ### `routes`
65
+
66
+ List every registered route, its method(s), and its handler.
67
+
68
+ ```bash
69
+ npm run keel routes
70
+ ```
71
+
72
+ ```
73
+ GET / HomeController@index
74
+ GET /users/:id HomeController@show (users.show)
75
+ GET|POST /form Closure
76
+ GET /favicon.ico Static
77
+ ```
78
+
79
+ Each row is `methods`, `path`, then the resolved handler. The handler column
80
+ reflects how the route was registered:
81
+
82
+ - **`Controller@method`** — a `[Controller, "method"]` handler tuple.
83
+ - **`Closure`** — an inline function handler.
84
+ - **`Static`** — a pre-built `Response` served directly.
85
+
86
+ A trailing `(name)` appears for [named routes](./routing.md). Multiple verbs on
87
+ one path are joined with `|`. If nothing is registered, it prints
88
+ `No routes registered.` instead.
89
+
90
+ ### `mcp`
91
+
92
+ Start the Model Context Protocol server over stdio, exposing Keel's docs, public
93
+ API, and generators to AI agents:
94
+
95
+ ```bash
96
+ npm run keel mcp # or the shipped `keel-mcp` bin in a consuming app
97
+ ```
98
+
99
+ See [Building with AI](./ai.md) for how to connect it to Claude Code, Cursor, or
100
+ any MCP client, and the tools it provides.
101
+
102
+ ## Generators
103
+
104
+ Each `make:*` command normalizes the name you give it and writes a single file.
105
+ Name normalization is suffix-aware and case-insensitive: it strips a trailing
106
+ suffix if present, PascalCases the base, then re-appends the canonical suffix.
107
+ So `Post`, `post`, and `PostController` all yield `PostController` — you can pass
108
+ whichever form reads naturally.
109
+
110
+ Generated stubs import their base classes and types from `@shaferllc/keel/core`
111
+ — the published package's core entry point — so they resolve out of the box in a
112
+ project that has `@shaferllc/keel` installed.
113
+
114
+ ### `make:controller`
115
+
116
+ Generate a controller in `app/Controllers/`.
117
+
118
+ ```bash
119
+ npm run keel make:controller Post
120
+ # -> app/Controllers/PostController.ts
121
+ ```
122
+
123
+ The name is normalized: `Post`, `post`, and `PostController` all produce
124
+ `PostController`. The default stub is a single `index` action:
125
+
126
+ ```ts
127
+ import type { Ctx } from "@shaferllc/keel/core";
128
+
129
+ export class PostController {
130
+ index(c: Ctx) {
131
+ return c.json({ controller: "PostController", action: "index" });
132
+ }
133
+ }
134
+ ```
135
+
136
+ Pass `-r` / `--resource` for a full RESTful resource controller with the seven
137
+ standard actions (`index`, `create`, `store`, `show`, `edit`, `update`,
138
+ `destroy`):
139
+
140
+ ```bash
141
+ npm run keel make:controller Post --resource
142
+ # -> app/Controllers/PostController.ts
143
+ ```
144
+
145
+ ```ts
146
+ import type { Ctx } from "@shaferllc/keel/core";
147
+
148
+ export class PostController {
149
+ index(c: Ctx) {
150
+ return c.json({ action: "index" });
151
+ }
152
+
153
+ create(c: Ctx) {
154
+ return c.json({ action: "create" });
155
+ }
156
+
157
+ // ...store, show, edit, update, destroy
158
+ }
159
+ ```
160
+
161
+ Wire it up with `Route.resource(...)` — see [Controllers](./controllers.md).
162
+
163
+ ### `make:provider`
164
+
165
+ Generate a service provider in `app/Providers/`.
166
+
167
+ ```bash
168
+ npm run keel make:provider Billing
169
+ # -> app/Providers/BillingServiceProvider.ts
170
+ ```
171
+
172
+ ```ts
173
+ import { ServiceProvider } from "@shaferllc/keel/core";
174
+
175
+ export class BillingServiceProvider extends ServiceProvider {
176
+ register(): void {
177
+ // Bind services into the container here.
178
+ }
179
+
180
+ boot(): void {
181
+ // Resolve and wire things up here.
182
+ }
183
+ }
184
+ ```
185
+
186
+ Remember to add it to `bootstrap/providers.ts` — generation doesn't register it
187
+ for you. See [Service Providers](./providers.md).
188
+
189
+ ### `make:middleware`
190
+
191
+ Generate an HTTP middleware in `app/Http/Middleware/`.
192
+
193
+ ```bash
194
+ npm run keel make:middleware Auth
195
+ # -> app/Http/Middleware/authMiddleware.ts
196
+ ```
197
+
198
+ The class name is normalized to `AuthMiddleware`, but the **file** and the
199
+ exported constant are camelCased (`authMiddleware`). The stub is a Hono
200
+ `MiddlewareHandler` with before/after seams around `next()`:
201
+
202
+ ```ts
203
+ import type { MiddlewareHandler } from "hono";
204
+
205
+ export const authMiddleware: MiddlewareHandler = async (c, next) => {
206
+ // ...before
207
+ await next();
208
+ // ...after
209
+ };
210
+ ```
211
+
212
+ This is the one stub that imports from `hono` rather than the Keel core. See
213
+ [Middleware](./middleware.md).
214
+
215
+ ### `make:factory`
216
+
217
+ Generate a model factory in `database/factories/`.
218
+
219
+ ```bash
220
+ npm run keel make:factory User
221
+ # -> database/factories/UserFactory.ts
222
+ ```
223
+
224
+ `make:factory` takes a **model** name (no suffix stripped) and generates a
225
+ `<Model>Factory.ts`. The stub imports the model and exports a lowercase-named
226
+ factory built with the `factory()` helper:
227
+
228
+ ```ts
229
+ import { factory } from "@shaferllc/keel/core";
230
+ import { User } from "../../app/Models/User.js";
231
+
232
+ export const userFactory = factory(User, (f) => ({
233
+ // Describe one User's attributes; `f` is a Faker.
234
+ name: f.name(),
235
+ email: f.email(),
236
+ }));
237
+ ```
238
+
239
+ It assumes a matching model at `app/Models/<Model>.ts` — create that first. See
240
+ [Factories & Seeders](./factories.md).
241
+
242
+ ### `make:seeder`
243
+
244
+ Generate a database seeder in `database/seeders/`.
245
+
246
+ ```bash
247
+ npm run keel make:seeder Database
248
+ # -> database/seeders/DatabaseSeeder.ts
249
+ ```
250
+
251
+ ```ts
252
+ import { Seeder } from "@shaferllc/keel/core";
253
+
254
+ export class DatabaseSeeder extends Seeder {
255
+ async run(): Promise<void> {
256
+ // Populate the database, e.g.:
257
+ // await userFactory.count(10).create();
258
+ }
259
+ }
260
+ ```
261
+
262
+ See [Factories & Seeders](./factories.md) for running them.
263
+
264
+ ### `make:job`
265
+
266
+ Generate a queued job in `app/Jobs/`.
267
+
268
+ ```bash
269
+ npm run keel make:job SendWelcome
270
+ # -> app/Jobs/SendWelcomeJob.ts
271
+ ```
272
+
273
+ ```ts
274
+ import { Job } from "@shaferllc/keel/core";
275
+
276
+ export class SendWelcomeJob extends Job {
277
+ constructor(/* pass the data this job needs */) {
278
+ super();
279
+ }
280
+
281
+ async handle(): Promise<void> {
282
+ // Do the background work here.
283
+ }
284
+ }
285
+ ```
286
+
287
+ The `handle()` method holds the work; the constructor takes whatever data the
288
+ job needs to carry onto the queue. See [Queues & Jobs](./queues.md) for
289
+ dispatching them.
290
+
291
+ ### `make:notification`
292
+
293
+ Generate a notification in `app/Notifications/`.
294
+
295
+ ```bash
296
+ npm run keel make:notification InvoicePaid
297
+ # -> app/Notifications/InvoicePaidNotification.ts
298
+ ```
299
+
300
+ ```ts
301
+ import { Notification, type Notifiable, type MailContent } from "@shaferllc/keel/core";
302
+
303
+ export class InvoicePaidNotification extends Notification {
304
+ via(_notifiable: Notifiable): string[] {
305
+ return ["mail"];
306
+ }
307
+
308
+ toMail(_notifiable: Notifiable): MailContent {
309
+ return {
310
+ subject: "InvoicePaidNotification",
311
+ text: "Notification body.",
312
+ };
313
+ }
314
+ }
315
+ ```
316
+
317
+ `via()` returns the channels to deliver on; `toMail()` builds the message for the
318
+ mail channel. See [Notifications](./notifications.md) for sending them.
319
+
320
+ ## Generator safety
321
+
322
+ Generators never clobber your work. Before writing, each one checks whether the
323
+ target file already exists; if it does, it prints an error, sets a non-zero exit
324
+ code, and writes nothing:
325
+
326
+ ```
327
+ ✗ Controller already exists: app/Controllers/PostController.ts
328
+ ```
329
+
330
+ Only when the path is free does it create any missing parent directories and
331
+ write the stub, confirming with:
332
+
333
+ ```
334
+ ✓ Created Controller: app/Controllers/PostController.ts
335
+ ```
336
+
337
+ Delete the existing file first if you truly mean to regenerate it.
338
+
339
+ ## Adding your own commands
340
+
341
+ Commands are defined with [commander](https://github.com/tj/commander.js) in
342
+ [`src/core/cli/index.ts`](../src/core/cli/index.ts). Register a new one on the
343
+ `program`:
344
+
345
+ ```ts
346
+ program
347
+ .command("cache:clear")
348
+ .description("Clear the application cache")
349
+ .action(async () => {
350
+ const app = await createApplication();
351
+ // ...your logic, with full access to the container
352
+ });
353
+ ```
354
+
355
+ Because commands boot the application, they get the same container, config, and
356
+ providers your HTTP requests do.