@shaferllc/keel 0.66.0 → 0.74.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/AGENTS.md +169 -0
- package/README.md +44 -6
- package/bin/keel-mcp.mjs +9 -0
- package/dist/api/api.config.stub +9 -0
- package/dist/api/config.d.ts +13 -0
- package/dist/api/config.js +14 -0
- package/dist/api/index.d.ts +16 -0
- package/dist/api/index.js +13 -0
- package/dist/api/provider.d.ts +10 -0
- package/dist/api/provider.js +17 -0
- package/dist/api/query.d.ts +35 -0
- package/dist/api/query.js +42 -0
- package/dist/api/resource.d.ts +91 -0
- package/dist/api/resource.js +188 -0
- package/dist/core/application.d.ts +5 -5
- package/dist/core/application.js +8 -2
- package/dist/core/cache.d.ts +81 -5
- package/dist/core/cache.js +188 -23
- package/dist/core/cli/stubs.d.ts +26 -0
- package/dist/core/cli/stubs.js +225 -0
- package/dist/core/console-prompt.d.ts +79 -0
- package/dist/core/console-prompt.js +239 -0
- package/dist/core/console-ui.d.ts +96 -0
- package/dist/core/console-ui.js +187 -0
- package/dist/core/console.d.ts +188 -0
- package/dist/core/console.js +395 -0
- package/dist/core/database.d.ts +70 -1
- package/dist/core/database.js +174 -15
- package/dist/core/env.d.ts +96 -0
- package/dist/core/env.js +140 -0
- package/dist/core/events.d.ts +129 -5
- package/dist/core/events.js +165 -7
- package/dist/core/health.d.ts +141 -0
- package/dist/core/health.js +226 -0
- package/dist/core/helpers.d.ts +9 -3
- package/dist/core/helpers.js +11 -3
- package/dist/core/http/kernel.d.ts +2 -0
- package/dist/core/http/kernel.js +48 -0
- package/dist/core/http/router.d.ts +5 -5
- package/dist/core/http/router.js +5 -5
- package/dist/core/i18n.d.ts +162 -0
- package/dist/core/i18n.js +472 -0
- package/dist/core/index.d.ts +39 -13
- package/dist/core/index.js +19 -6
- package/dist/core/instrumentation.d.ts +113 -0
- package/dist/core/instrumentation.js +52 -0
- package/dist/core/lock.d.ts +139 -0
- package/dist/core/lock.js +215 -0
- package/dist/core/logger.d.ts +89 -4
- package/dist/core/logger.js +167 -22
- package/dist/core/mail.d.ts +128 -7
- package/dist/core/mail.js +264 -16
- package/dist/core/notification.js +10 -1
- package/dist/core/package.d.ts +120 -0
- package/dist/core/package.js +169 -0
- package/dist/core/pages.d.ts +108 -0
- package/dist/core/pages.js +199 -0
- package/dist/core/queue.d.ts +134 -9
- package/dist/core/queue.js +325 -14
- package/dist/core/repl.d.ts +33 -0
- package/dist/core/repl.js +88 -0
- package/dist/core/scheduler.js +6 -0
- package/dist/core/social.d.ts +4 -4
- package/dist/core/social.js +4 -4
- package/dist/core/storage.d.ts +159 -6
- package/dist/core/storage.js +299 -7
- package/dist/core/telemetry.d.ts +208 -0
- package/dist/core/telemetry.js +383 -0
- package/dist/core/template.d.ts +2 -3
- package/dist/core/template.js +2 -3
- package/dist/core/testing.d.ts +170 -1
- package/dist/core/testing.js +504 -2
- package/dist/db/d1.js +13 -0
- package/dist/db/pg.d.ts +13 -0
- package/dist/db/pg.js +46 -4
- package/dist/mcp/server.d.ts +19 -0
- package/dist/mcp/server.js +355 -0
- package/dist/openapi/config.d.ts +28 -0
- package/dist/openapi/config.js +25 -0
- package/dist/openapi/doc.d.ts +40 -0
- package/dist/openapi/doc.js +20 -0
- package/dist/openapi/export.d.ts +8 -0
- package/dist/openapi/export.js +19 -0
- package/dist/openapi/gate.d.ts +15 -0
- package/dist/openapi/gate.js +27 -0
- package/dist/openapi/index.d.ts +19 -0
- package/dist/openapi/index.js +15 -0
- package/dist/openapi/openapi.config.stub +29 -0
- package/dist/openapi/provider.d.ts +18 -0
- package/dist/openapi/provider.js +35 -0
- package/dist/openapi/routes.d.ts +9 -0
- package/dist/openapi/routes.js +23 -0
- package/dist/openapi/spec.d.ts +23 -0
- package/dist/openapi/spec.js +132 -0
- package/dist/openapi/ui.d.ts +8 -0
- package/dist/openapi/ui.js +31 -0
- package/dist/openapi/zod.d.ts +12 -0
- package/dist/openapi/zod.js +46 -0
- package/dist/watch/config.d.ts +33 -0
- package/dist/watch/config.js +38 -0
- package/dist/watch/entry.d.ts +53 -0
- package/dist/watch/entry.js +105 -0
- package/dist/watch/gate.d.ts +20 -0
- package/dist/watch/gate.js +32 -0
- package/dist/watch/index.d.ts +21 -0
- package/dist/watch/index.js +17 -0
- package/dist/watch/migration.d.ts +7 -0
- package/dist/watch/migration.js +20 -0
- package/dist/watch/provider.d.ts +22 -0
- package/dist/watch/provider.js +58 -0
- package/dist/watch/prune.d.ts +11 -0
- package/dist/watch/prune.js +20 -0
- package/dist/watch/recorder.d.ts +24 -0
- package/dist/watch/recorder.js +39 -0
- package/dist/watch/routes.d.ts +13 -0
- package/dist/watch/routes.js +55 -0
- package/dist/watch/store.d.ts +54 -0
- package/dist/watch/store.js +158 -0
- package/dist/watch/ui/dist/watch.css +1 -0
- package/dist/watch/ui/dist/watch.js +555 -0
- package/dist/watch/ui-shell.d.ts +12 -0
- package/dist/watch/ui-shell.js +24 -0
- package/dist/watch/watch.config.stub +47 -0
- package/dist/watch/watchers.d.ts +12 -0
- package/dist/watch/watchers.js +156 -0
- package/docs/ai-manifest.json +3206 -0
- package/docs/ai.md +128 -0
- package/docs/api-resources.md +118 -0
- package/docs/architecture.md +331 -0
- package/docs/authentication.md +453 -0
- package/docs/authorization.md +167 -0
- package/docs/broadcasting.md +137 -0
- package/docs/broker.md +500 -0
- package/docs/cache.md +558 -0
- package/docs/configuration.md +385 -0
- package/docs/console.md +536 -0
- package/docs/container.md +467 -0
- package/docs/controllers.md +265 -0
- package/docs/cors.md +51 -0
- package/docs/database.md +631 -0
- package/docs/debugging.md +129 -0
- package/docs/decorators.md +127 -0
- package/docs/errors.md +395 -0
- package/docs/events.md +496 -0
- package/docs/examples/architecture-app.ts +27 -0
- package/docs/examples/authentication.ts +61 -0
- package/docs/examples/authorization.ts +79 -0
- package/docs/examples/broadcasting.ts +60 -0
- package/docs/examples/broker-cache-validate.ts +34 -0
- package/docs/examples/broker-fault-tolerance.ts +29 -0
- package/docs/examples/broker-middleware.ts +27 -0
- package/docs/examples/broker.ts +203 -0
- package/docs/examples/cache.ts +222 -0
- package/docs/examples/configuration.ts +121 -0
- package/docs/examples/console.ts +134 -0
- package/docs/examples/container.ts +134 -0
- package/docs/examples/controllers.ts +86 -0
- package/docs/examples/database.ts +208 -0
- package/docs/examples/debugging.ts +41 -0
- package/docs/examples/decorators.ts +40 -0
- package/docs/examples/errors.ts +121 -0
- package/docs/examples/events.ts +204 -0
- package/docs/examples/factories.ts +84 -0
- package/docs/examples/hashing.ts +71 -0
- package/docs/examples/health.ts +94 -0
- package/docs/examples/helpers.ts +171 -0
- package/docs/examples/hooks.ts +54 -0
- package/docs/examples/i18n.ts +117 -0
- package/docs/examples/inertia.ts +81 -0
- package/docs/examples/locks.ts +120 -0
- package/docs/examples/logger.ts +166 -0
- package/docs/examples/mail.ts +263 -0
- package/docs/examples/middleware.ts +119 -0
- package/docs/examples/migrations.ts +126 -0
- package/docs/examples/models.ts +239 -0
- package/docs/examples/notification.ts +124 -0
- package/docs/examples/pages.ts +82 -0
- package/docs/examples/providers.ts +123 -0
- package/docs/examples/queues.ts +254 -0
- package/docs/examples/rate-limiting.ts +42 -0
- package/docs/examples/redis.ts +99 -0
- package/docs/examples/request-response.ts +197 -0
- package/docs/examples/routing.ts +186 -0
- package/docs/examples/scheduling.ts +62 -0
- package/docs/examples/sessions.ts +102 -0
- package/docs/examples/static-files.ts +63 -0
- package/docs/examples/storage.ts +132 -0
- package/docs/examples/telemetry.ts +127 -0
- package/docs/examples/templates.ts +58 -0
- package/docs/examples/testing.ts +215 -0
- package/docs/examples/transformer.ts +141 -0
- package/docs/examples/transformers.ts +49 -0
- package/docs/examples/url-builder.ts +86 -0
- package/docs/examples/validation.ts +102 -0
- package/docs/examples/views.tsx +62 -0
- package/docs/examples/vite.ts +106 -0
- package/docs/factories.md +166 -0
- package/docs/getting-started.md +290 -0
- package/docs/hashing.md +259 -0
- package/docs/health.md +225 -0
- package/docs/helpers.md +347 -0
- package/docs/hono.md +186 -0
- package/docs/hooks.md +118 -0
- package/docs/i18n.md +302 -0
- package/docs/inertia.md +241 -0
- package/docs/locks.md +323 -0
- package/docs/logger.md +436 -0
- package/docs/mail.md +751 -0
- package/docs/middleware.md +425 -0
- package/docs/migrations.md +476 -0
- package/docs/models.md +810 -0
- package/docs/notifications.md +474 -0
- package/docs/openapi.md +111 -0
- package/docs/packages.md +118 -0
- package/docs/pages.md +217 -0
- package/docs/providers.md +363 -0
- package/docs/queues.md +679 -0
- package/docs/rate-limiting.md +155 -0
- package/docs/redis.md +178 -0
- package/docs/request-response.md +953 -0
- package/docs/routing.md +804 -0
- package/docs/scheduling.md +110 -0
- package/docs/security.md +85 -0
- package/docs/sessions.md +354 -0
- package/docs/social-auth.md +174 -0
- package/docs/static-files.md +211 -0
- package/docs/storage.md +452 -0
- package/docs/telemetry.md +263 -0
- package/docs/templates.md +314 -0
- package/docs/testing.md +376 -0
- package/docs/transformers.md +381 -0
- package/docs/url-builder.md +295 -0
- package/docs/validation.md +288 -0
- package/docs/views.md +267 -0
- package/docs/vite.md +434 -0
- package/docs/watch.md +118 -0
- package/llms-full.txt +19816 -0
- package/llms.txt +127 -0
- package/package.json +42 -7
package/docs/console.md
ADDED
|
@@ -0,0 +1,536 @@
|
|
|
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
|
+
## Writing your own commands
|
|
340
|
+
|
|
341
|
+
`keel make:command greet` scaffolds `app/Commands/greet.ts`. Everything in
|
|
342
|
+
`app/Commands` is discovered automatically — no registration step.
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
import { defineCommand, arg, flag } from "@shaferllc/keel/core";
|
|
346
|
+
|
|
347
|
+
export const greet = defineCommand({
|
|
348
|
+
name: "greet",
|
|
349
|
+
description: "Greet someone",
|
|
350
|
+
|
|
351
|
+
args: { name: arg.string({ description: "who to greet" }) },
|
|
352
|
+
flags: { loud: flag.boolean({ alias: "l", description: "SHOUT IT" }) },
|
|
353
|
+
|
|
354
|
+
async run({ args, flags, ui }) {
|
|
355
|
+
const message = `Hello, ${args.name}!`;
|
|
356
|
+
ui.success(flags.loud ? message.toUpperCase() : message);
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
keel greet Ada --loud # ✔ HELLO, ADA!
|
|
363
|
+
keel greet --help # generated usage, args, and options
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**`args.name` is a `string` and `flags.loud` is a `boolean` — inferred, not cast.**
|
|
367
|
+
That's the point of declaring them: the parsing is generated from the types, so the
|
|
368
|
+
two can't drift apart. Make an arg optional and its type becomes
|
|
369
|
+
`string | undefined`; give it a default and it's a `string` again.
|
|
370
|
+
|
|
371
|
+
Commands run with the application booted, so they get the same container, config,
|
|
372
|
+
and providers your HTTP requests do.
|
|
373
|
+
|
|
374
|
+
### Arguments
|
|
375
|
+
|
|
376
|
+
Positional, in declaration order. Required by default.
|
|
377
|
+
|
|
378
|
+
| Builder | Value |
|
|
379
|
+
|---------|-------|
|
|
380
|
+
| `arg.string()` | `string` |
|
|
381
|
+
| `arg.number()` | `number` — rejected with a clear error if it isn't one |
|
|
382
|
+
| `arg.spread()` | `string[]` — swallows the rest; must be last |
|
|
383
|
+
|
|
384
|
+
Options: `description`, `required: false`, `default`, `parse`.
|
|
385
|
+
|
|
386
|
+
### Flags
|
|
387
|
+
|
|
388
|
+
| Builder | Value |
|
|
389
|
+
|---------|-------|
|
|
390
|
+
| `flag.boolean()` | `boolean` — defaults to `false`, so it's never `undefined` |
|
|
391
|
+
| `flag.string()` | `string \| undefined` |
|
|
392
|
+
| `flag.number()` | `number \| undefined` |
|
|
393
|
+
| `flag.array()` | `string[]` — repeatable, defaults to `[]` |
|
|
394
|
+
|
|
395
|
+
Options: `description`, `alias` (a single letter), `required`, `default`, `parse`.
|
|
396
|
+
|
|
397
|
+
The parser understands `--flag value`, `--flag=value`, `--no-flag`, `-f value`,
|
|
398
|
+
bundled shorthands (`-lt 5`), and `--`, after which everything is passed through
|
|
399
|
+
untouched in `rest`.
|
|
400
|
+
|
|
401
|
+
An **unknown flag is an error**, not a shrug — a typo'd `--forse` should tell you,
|
|
402
|
+
not silently do nothing. Set `allowUnknownFlags: true` if a command genuinely needs
|
|
403
|
+
to pass flags on to something else.
|
|
404
|
+
|
|
405
|
+
### Exit codes
|
|
406
|
+
|
|
407
|
+
Return a number to set the exit code; return nothing for `0`. A thrown error is
|
|
408
|
+
caught, reported, and exits `1` — a console is a bad place to show a user a stack
|
|
409
|
+
trace because they mistyped a flag. A **usage** error (missing arg, bad flag) prints
|
|
410
|
+
what's wrong *and the command's help*.
|
|
411
|
+
|
|
412
|
+
## Terminal UI
|
|
413
|
+
|
|
414
|
+
Every command gets a `ui`:
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
ui.info("Checking…");
|
|
418
|
+
ui.success("Migrated 3 tables");
|
|
419
|
+
ui.warning("Nothing to do");
|
|
420
|
+
ui.error("Failed"); // stderr
|
|
421
|
+
ui.debug("verbose detail");
|
|
422
|
+
|
|
423
|
+
ui.action("create", "app/Models/User.ts"); // CREATE app/Models/User.ts
|
|
424
|
+
ui.action("skip", "app/Models/Post.ts", "skipped");
|
|
425
|
+
|
|
426
|
+
ui.table(["Name", "Rows"]).row(["users", "42"]).row(["orgs", "7"]).render();
|
|
427
|
+
|
|
428
|
+
ui.sticker(["http://localhost:3000"], "Server running");
|
|
429
|
+
ui.instructions(["cd my-app", "npm install", "keel serve"], "Next steps");
|
|
430
|
+
|
|
431
|
+
ui.colors("green", "done"); // paint a string yourself
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Tasks
|
|
435
|
+
|
|
436
|
+
For a command that does several things in a row:
|
|
437
|
+
|
|
438
|
+
```ts
|
|
439
|
+
await ui
|
|
440
|
+
.tasks()
|
|
441
|
+
.add("Install dependencies", async (task) => {
|
|
442
|
+
task.update("resolving…");
|
|
443
|
+
return "42 packages";
|
|
444
|
+
})
|
|
445
|
+
.add("Run migrations", async () => "3 tables")
|
|
446
|
+
.run();
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
It **stops at the first failure**, because the tasks after it almost certainly
|
|
450
|
+
depended on it and a cascade of red tells you nothing new. `run()` resolves to
|
|
451
|
+
`false` if anything failed.
|
|
452
|
+
|
|
453
|
+
## Prompts
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
const name = await prompt.ask("Project name?", { default: "my-app" });
|
|
457
|
+
const secret = await prompt.secure("API key?");
|
|
458
|
+
const ok = await prompt.confirm("Delete everything?");
|
|
459
|
+
const driver = await prompt.choice("Database?", ["sqlite", "postgres"]);
|
|
460
|
+
const features = await prompt.multiple("Features?", ["auth", "queue", "mail"]);
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
`ask` re-asks on a failed `validate` rather than dying — a typo shouldn't cost
|
|
464
|
+
someone the whole command. Every prompt takes `default`, `hint`, `validate`, and
|
|
465
|
+
`result`.
|
|
466
|
+
|
|
467
|
+
## Testing a command
|
|
468
|
+
|
|
469
|
+
A command that asks questions is normally a command you can't test. So prompts can
|
|
470
|
+
be **trapped**: script the answers up front, and nothing touches the terminal.
|
|
471
|
+
|
|
472
|
+
```ts
|
|
473
|
+
import { ConsoleKernel, createUi, createPrompt } from "@shaferllc/keel/core";
|
|
474
|
+
|
|
475
|
+
const ui = createUi({ raw: true }); // buffer the output, drop the colors
|
|
476
|
+
const prompt = createPrompt({ trap: true });
|
|
477
|
+
const kernel = new ConsoleKernel({ ui, prompt }).register(setup);
|
|
478
|
+
|
|
479
|
+
prompt.trap("Project name?").replyWith("keel-app");
|
|
480
|
+
prompt.trap("Database?").chooseOption(1);
|
|
481
|
+
prompt.trap("Write the config?").accept();
|
|
482
|
+
|
|
483
|
+
const code = await kernel.run(["setup"]);
|
|
484
|
+
|
|
485
|
+
assert.equal(code, 0);
|
|
486
|
+
assert.match(ui.logs.join("\n"), /keel-app on postgres/);
|
|
487
|
+
prompt.assertAllTrapsUsed(); // every scripted question was actually asked
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
An **untrapped prompt throws** instead of hanging. That matters more than it
|
|
491
|
+
sounds: without it, the test would block forever on stdin no test will ever
|
|
492
|
+
provide, and your suite would simply stop — with no failure to read.
|
|
493
|
+
|
|
494
|
+
A trap can also assert the prompt's own validation:
|
|
495
|
+
|
|
496
|
+
```ts
|
|
497
|
+
prompt
|
|
498
|
+
.trap("Email?")
|
|
499
|
+
.assertFails("", "Email is required")
|
|
500
|
+
.assertPasses("ada@example.com")
|
|
501
|
+
.replyWith("ada@example.com");
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
`ui.logs` and `ui.errors` hold every line written, colorless, so you can assert on
|
|
505
|
+
exactly what the command said.
|
|
506
|
+
|
|
507
|
+
## The REPL
|
|
508
|
+
|
|
509
|
+
```bash
|
|
510
|
+
keel repl
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
An interactive shell with the **application booted** — the container is up, the
|
|
514
|
+
providers have run, and the helpers are in scope:
|
|
515
|
+
|
|
516
|
+
```
|
|
517
|
+
keel > await db("users").where("active", 1).get()
|
|
518
|
+
keel > make(Router).all()
|
|
519
|
+
keel > await cache().get("stats")
|
|
520
|
+
keel > .ls # what's in scope
|
|
521
|
+
keel > .exit
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
Poking at a model in a REPL is the fastest debugging loop there is, and it
|
|
525
|
+
shouldn't cost you a throwaway script to get one. History persists in
|
|
526
|
+
`.keel_repl_history`.
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## A note on the built-ins
|
|
531
|
+
|
|
532
|
+
The commands *above* (`serve`, `routes`, `make:*`, `migrate:*`) still run through
|
|
533
|
+
Keel's original console wrapper, and package-contributed commands do too. Your
|
|
534
|
+
commands — anything in `app/Commands` — run on the system documented here, and take
|
|
535
|
+
precedence over a built-in of the same name. Migrating the built-ins across is
|
|
536
|
+
mechanical and will happen; nothing about the API here changes when it does.
|