@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/AGENTS.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# AGENTS.md — working in Keel with an AI agent
|
|
2
|
+
|
|
3
|
+
Guidance for AI coding agents (Claude Code, Cursor, …) editing **Keel** — the
|
|
4
|
+
house framework for Node.js — or an app built on it. Humans: this is also a fast
|
|
5
|
+
orientation. For prose guides see [`docs/`](./docs); for a machine-readable
|
|
6
|
+
surface, run the [MCP server](#mcp-server-recommended).
|
|
7
|
+
|
|
8
|
+
> **The fastest path:** connect the Keel MCP server (below) and call
|
|
9
|
+
> `keel_overview` first. It returns conventions, the folder map, every doc
|
|
10
|
+
> topic, and the generators. Then `keel_search_docs` / `keel_read_doc` for
|
|
11
|
+
> depth, `keel_search_api` to find exports, `keel_scaffold` to generate code.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## What Keel is
|
|
16
|
+
|
|
17
|
+
A small, legible MVC framework: a real **service container**, **service
|
|
18
|
+
providers**, dot-notation **config**, expressive **routing**, active-record
|
|
19
|
+
**models**, a **queue**, **auth**, and a code-generating **console**.
|
|
20
|
+
[Hono](https://hono.dev) powers HTTP; everything above it is Keel's. The whole
|
|
21
|
+
core is a few hundred lines per file in `src/core/` — read it, nothing is magic.
|
|
22
|
+
|
|
23
|
+
## The one import rule
|
|
24
|
+
|
|
25
|
+
Everything userland comes from a single entry point:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { Router, Model, config, ServiceProvider } from "@shaferllc/keel/core";
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- **In a consuming app** (the `keel-app` starter, or your own): use
|
|
32
|
+
`@shaferllc/keel/core`. This is what generated stubs emit.
|
|
33
|
+
- **Inside this framework repo's example app** (`app/`, `routes/`, `bootstrap/`):
|
|
34
|
+
the code uses the `@keel/core` tsconfig **path alias** that points at
|
|
35
|
+
`src/core/`. Match the file you're editing — don't "fix" `@keel/core` to the
|
|
36
|
+
package name inside this repo.
|
|
37
|
+
|
|
38
|
+
Never deep-import `@shaferllc/keel/core/model.js`. If a symbol isn't exported
|
|
39
|
+
from `/core`, that's intentional. Find exports with `keel_search_api` or read
|
|
40
|
+
`src/core/index.ts`.
|
|
41
|
+
|
|
42
|
+
## Folder layout of a Keel app
|
|
43
|
+
|
|
44
|
+
| Path | What lives there |
|
|
45
|
+
|------|------------------|
|
|
46
|
+
| `app/Controllers/` | Controller classes (resolved from the container → DI) |
|
|
47
|
+
| `app/Providers/` | Service providers (`register()` then `boot()`) |
|
|
48
|
+
| `app/Http/Middleware/` | Hono middleware handlers |
|
|
49
|
+
| `app/Http/Kernel.ts` | The HTTP kernel — global middleware, error handling |
|
|
50
|
+
| `app/Models/` | Active-record models extending `Model` |
|
|
51
|
+
| `app/Jobs/`, `app/Notifications/`, `app/Transformers/` | Queue jobs, notifications, API serializers |
|
|
52
|
+
| `config/*.ts` | Config files; read via `config("app.name")` |
|
|
53
|
+
| `routes/web.ts` | Route definitions — default export receives the `Router` |
|
|
54
|
+
| `database/factories/`, `database/seeders/`, `database/migrations/` | Data tooling |
|
|
55
|
+
| `bootstrap/app.ts` | Assembles the `Application`, boots providers, loads routes |
|
|
56
|
+
| `.env` | Environment, loaded into `env(...)` and config |
|
|
57
|
+
|
|
58
|
+
## Core mental model
|
|
59
|
+
|
|
60
|
+
1. **Container.** Every service is registered in and resolved out of the
|
|
61
|
+
container. `bind` (transient), `singleton` (once), `instance` (existing
|
|
62
|
+
value), `make(Token)` (resolve). Constructor injection: a controller/service
|
|
63
|
+
with `constructor(private app: Container)` gets it automatically.
|
|
64
|
+
2. **Providers.** `register()` binds things into the container; `boot()` runs
|
|
65
|
+
after all providers registered, to wire things together. Register providers
|
|
66
|
+
in `bootstrap/providers.ts`.
|
|
67
|
+
3. **Config.** `config("app.port", 3000)` reads `config/app.ts` merged with
|
|
68
|
+
`.env`. Dot-notation, with a default.
|
|
69
|
+
4. **Routing.** `router.get("/x", [Controller, "method"])` or a closure. Name
|
|
70
|
+
routes with `.name()`, group with `.group()`, constrain params with
|
|
71
|
+
`.where()`, build URLs with `router.url("name", params)`.
|
|
72
|
+
5. **Request context.** Inside a handler, `param()`, `query()`, `body()`,
|
|
73
|
+
`json()`, `text()` read/write the current request via async-local context —
|
|
74
|
+
no need to thread `c` everywhere (though the Hono `Ctx` is available).
|
|
75
|
+
|
|
76
|
+
## How to add things (prefer the generators)
|
|
77
|
+
|
|
78
|
+
Use `keel_scaffold` (MCP) or the console — both emit the correct stub with the
|
|
79
|
+
right imports and target path. Then wire it up.
|
|
80
|
+
|
|
81
|
+
| Goal | Command | Then |
|
|
82
|
+
|------|---------|------|
|
|
83
|
+
| Controller | `keel make:controller Post` (`-r` for REST) | add a route in `routes/web.ts` |
|
|
84
|
+
| Provider | `keel make:provider Billing` | register in `bootstrap/providers.ts` |
|
|
85
|
+
| Middleware | `keel make:middleware Auth` | attach in `app/Http/Kernel.ts` or per-route |
|
|
86
|
+
| Model | *(no generator — extend `Model`)* | set `static table`; add relations |
|
|
87
|
+
| Factory | `keel make:factory User` | reference the model |
|
|
88
|
+
| Seeder | `keel make:seeder Database` | call factories in `run()` |
|
|
89
|
+
| Job | `keel make:job SendWelcome` | `dispatch(new SendWelcomeJob())` |
|
|
90
|
+
| Notification | `keel make:notification Paid` | `notify(user, new PaidNotification())` |
|
|
91
|
+
| Transformer | `keel make:transformer User` | return it from a controller |
|
|
92
|
+
| Package | `keel make:package billing` | a `PackageProvider` — see [`docs/packages.md`](./docs/packages.md) |
|
|
93
|
+
|
|
94
|
+
**Scaffolding does not write files** via MCP — it returns code + path; you write
|
|
95
|
+
it. The console (`keel make:*`) does write, and refuses to overwrite.
|
|
96
|
+
|
|
97
|
+
A minimal controller and route:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
// app/Controllers/PostController.ts
|
|
101
|
+
import type { Ctx } from "@shaferllc/keel/core";
|
|
102
|
+
export class PostController {
|
|
103
|
+
index(c: Ctx) { return c.json({ ok: true }); }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// routes/web.ts
|
|
107
|
+
router.get("/posts", [PostController, "index"]).name("posts.index");
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Commands
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npm run dev # example app on http://localhost:3000 (tsx watch)
|
|
114
|
+
npm run serve # keel serve
|
|
115
|
+
npm run keel -- routes # list registered routes
|
|
116
|
+
npm run keel -- migrate # run pending app + package migrations (also migrate:status, migrate:rollback)
|
|
117
|
+
npm run keel -- make:controller Foo
|
|
118
|
+
npm run mcp # start the MCP server over stdio (dev)
|
|
119
|
+
npm test # node --test over tests/*.test.ts
|
|
120
|
+
npm run typecheck # tsc --noEmit — run before you finish
|
|
121
|
+
npm run build # regenerate AI artifacts + compile to dist/
|
|
122
|
+
npm run build:ai # regenerate llms.txt, llms-full.txt, docs/ai-manifest.json
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Conventions & guardrails
|
|
126
|
+
|
|
127
|
+
- **TypeScript, strict, ESM.** `.js` extensions in relative imports (NodeNext).
|
|
128
|
+
No default exports except where the pattern already uses them (routes).
|
|
129
|
+
- **Match the surrounding file** — comment density, naming, idioms. Keel prizes
|
|
130
|
+
legibility; write code that reads like `src/core/`.
|
|
131
|
+
- **Test with the real thing.** `TestClient`/`testClient` drives the HTTP stack
|
|
132
|
+
in-process; `hash.fake()`, `fakeDisk()`, `ArrayTransport`, `MemoryDriver`, and
|
|
133
|
+
`EventBuffer` make side-effecting subsystems deterministic. See
|
|
134
|
+
[`docs/testing.md`](./docs/testing.md).
|
|
135
|
+
- **Run `npm run typecheck` before finishing.** The whole framework is typed end
|
|
136
|
+
to end; a green `tsc` is the bar.
|
|
137
|
+
- **After editing docs or the export surface, run `npm run build:ai`** so
|
|
138
|
+
`llms.txt`, `llms-full.txt`, and `docs/ai-manifest.json` stay in sync (the MCP
|
|
139
|
+
server reads the manifest).
|
|
140
|
+
|
|
141
|
+
## MCP server (recommended)
|
|
142
|
+
|
|
143
|
+
Keel ships an MCP server exposing its docs, the full public API surface, the
|
|
144
|
+
generators, and framework conventions to any MCP-capable agent.
|
|
145
|
+
|
|
146
|
+
**Claude Code:**
|
|
147
|
+
```bash
|
|
148
|
+
claude mcp add keel -- npx -y keel-mcp # in an app that depends on @shaferllc/keel
|
|
149
|
+
```
|
|
150
|
+
Or, hacking on the framework itself:
|
|
151
|
+
```bash
|
|
152
|
+
claude mcp add keel -- npm --prefix /path/to/keel run mcp
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**`.mcp.json` / Cursor / other clients:**
|
|
156
|
+
```json
|
|
157
|
+
{ "mcpServers": { "keel": { "command": "npx", "args": ["-y", "keel-mcp"] } } }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Tools: `keel_overview`, `keel_search_docs`, `keel_read_doc`, `keel_search_api`,
|
|
161
|
+
`keel_list_generators`, `keel_scaffold`. Resources: `keel://overview`,
|
|
162
|
+
`keel://llms-full`, `keel://docs/<slug>`. See [`docs/ai.md`](./docs/ai.md).
|
|
163
|
+
|
|
164
|
+
## Where to read next
|
|
165
|
+
|
|
166
|
+
- [`docs/getting-started.md`](./docs/getting-started.md) — a guided first hour
|
|
167
|
+
- [`docs/architecture.md`](./docs/architecture.md) — a request from socket to response
|
|
168
|
+
- [`docs/container.md`](./docs/container.md) — the DI core everything rests on
|
|
169
|
+
- [`llms-full.txt`](./llms-full.txt) — every guide in one file, for a fresh context
|
package/README.md
CHANGED
|
@@ -108,11 +108,41 @@ npm run keel serve --port 8080 # start the server on a chosen port
|
|
|
108
108
|
npm run keel make:controller Post # -> app/Controllers/PostController.ts
|
|
109
109
|
npm run keel make:provider Billing # -> app/Providers/BillingServiceProvider.ts
|
|
110
110
|
npm run keel make:middleware Auth # -> app/Http/Middleware/authMiddleware.ts
|
|
111
|
+
npm run keel make:page users/[id] # -> resources/pages/users/[id].tsx
|
|
112
|
+
npm run keel make:command greet # -> app/Commands/greet.ts
|
|
113
|
+
npm run keel repl # a shell with the app booted
|
|
114
|
+
npm run keel mcp # start the MCP server (docs + API for AI agents)
|
|
111
115
|
```
|
|
112
116
|
|
|
113
117
|
Under the hood the console binary is `bin/keel.ts`; the npm scripts wrap it with
|
|
114
118
|
`tsx`.
|
|
115
119
|
|
|
120
|
+
## Built for AI ⚓🤖
|
|
121
|
+
|
|
122
|
+
Keel is designed to be **written with an AI agent**. Alongside the human docs it
|
|
123
|
+
ships a machine-readable surface that stays generated-in-sync, never stale:
|
|
124
|
+
|
|
125
|
+
- **An MCP server.** `keel-mcp` exposes Keel's docs, its full public API (380+
|
|
126
|
+
exports), the generators, and its conventions to any [MCP](https://modelcontextprotocol.io)
|
|
127
|
+
client. Connect it in Claude Code:
|
|
128
|
+
```bash
|
|
129
|
+
claude mcp add keel -- npx -y keel-mcp
|
|
130
|
+
```
|
|
131
|
+
Tools: `keel_overview`, `keel_search_docs`, `keel_read_doc`, `keel_search_api`,
|
|
132
|
+
`keel_list_generators`, `keel_scaffold`. Resources: `keel://overview`,
|
|
133
|
+
`keel://llms-full`, `keel://docs/<slug>`.
|
|
134
|
+
- **[`AGENTS.md`](./AGENTS.md).** The agent playbook — the one import rule, the
|
|
135
|
+
folder map, the container/provider model, a "how to add X" table, and the
|
|
136
|
+
guardrails. `CLAUDE.md` points to it.
|
|
137
|
+
- **[`llms.txt`](./llms.txt) + [`llms-full.txt`](./llms-full.txt).** A
|
|
138
|
+
[spec-compliant](https://llmstxt.org) doc index and a one-file concatenation of
|
|
139
|
+
every guide, both shipped in the npm package for drop-in context.
|
|
140
|
+
- **Generators an agent can drive.** `keel_scaffold` (or `keel make:*`) emits the
|
|
141
|
+
correct stub with the right imports and path for every construct.
|
|
142
|
+
|
|
143
|
+
Full guide: **[docs/ai.md](./docs/ai.md)**. Regenerate the surface after doc or
|
|
144
|
+
export changes with `npm run build:ai` (also runs automatically on `npm run build`).
|
|
145
|
+
|
|
116
146
|
## Project layout
|
|
117
147
|
|
|
118
148
|
```
|
|
@@ -179,8 +209,8 @@ See [docs/architecture.md](./docs/architecture.md) for the full picture.
|
|
|
179
209
|
| [Models](./docs/models.md) | Active-record: find/create/save, casts, relations |
|
|
180
210
|
| [Migrations](./docs/migrations.md) | Schema builder + migrator, dialect-aware |
|
|
181
211
|
| [Factories & Seeders](./docs/factories.md) | Built-in Faker, model factories, seeders |
|
|
182
|
-
| [Mail](./docs/mail.md) | Fluent mailer, pluggable transports,
|
|
183
|
-
| [Queues & Jobs](./docs/queues.md) | Dispatch jobs,
|
|
212
|
+
| [Mail](./docs/mail.md) | Fluent mailer, pluggable transports, `sendLater()`, attachments |
|
|
213
|
+
| [Queues & Jobs](./docs/queues.md) | Dispatch jobs, retries + backoff, dead-letter, workers |
|
|
184
214
|
| [Task Scheduling](./docs/scheduling.md) | Cron-style recurring tasks, one trigger |
|
|
185
215
|
| [Notifications](./docs/notifications.md) | Multi-channel (mail/db), queueable |
|
|
186
216
|
| [Broadcasting](./docs/broadcasting.md) | Real-time channels, pluggable, presence auth |
|
|
@@ -188,23 +218,31 @@ See [docs/architecture.md](./docs/architecture.md) for the full picture.
|
|
|
188
218
|
| [Events](./docs/events.md) | Emit/listen decoupling, async listeners |
|
|
189
219
|
| [Service Broker](./docs/broker.md) | Moleculer-style services, call/emit, pluggable transport |
|
|
190
220
|
| [Cache](./docs/cache.md) | TTLs, the remember pattern, pluggable stores |
|
|
221
|
+
| [Locks](./docs/locks.md) | Distributed locks with ownership + TTL, pluggable stores |
|
|
191
222
|
| [Redis](./docs/redis.md) | Pluggable client, memory driver, cache adapter |
|
|
192
|
-
| [Logger](./docs/logger.md) | Structured logging, per-request `reqId`, redaction |
|
|
223
|
+
| [Logger](./docs/logger.md) | Structured logging, sinks, per-request `reqId`, redaction |
|
|
193
224
|
| [Static Files](./docs/static-files.md) | serveStatic(), caching, dot-file safety |
|
|
194
|
-
| [Storage](./docs/storage.md) | Pluggable disks (local/S3/R2),
|
|
225
|
+
| [Storage](./docs/storage.md) | Pluggable disks (local/S3/R2), signed URLs, direct uploads |
|
|
226
|
+
| [Health Checks](./docs/health.md) | `/health/live` + `/health/ready`, pluggable checks |
|
|
227
|
+
| [Telemetry](./docs/telemetry.md) | Tracing, W3C context, OTLP export — no SDK |
|
|
228
|
+
| [Internationalization](./docs/i18n.md) | ICU messages, `Intl` formatters, locale detection |
|
|
229
|
+
| [Pages](./docs/pages.md) | Page-based routing — a file is a route |
|
|
230
|
+
| [Packages](./docs/packages.md) | Redistributable slices of an app: routes, migrations, commands |
|
|
231
|
+
| [Watch](./docs/watch.md) | Debug dashboard — requests, queries, jobs, logs at `/watch` |
|
|
195
232
|
| [Views](./docs/views.md) | Hono JSX components, layouts, the View service |
|
|
196
233
|
| [Templates](./docs/templates.md) | `{{ }}` + `@`-tag templating engine, edge-safe |
|
|
197
234
|
| [Middleware](./docs/middleware.md) | Global middleware, writing your own |
|
|
198
235
|
| [Rate Limiting](./docs/rate-limiting.md) | rateLimiter() middleware, per-key buckets |
|
|
199
236
|
| [Errors](./docs/errors.md) | HTTP exceptions, debug page, custom handlers |
|
|
200
|
-
| [Testing](./docs/testing.md) | Inject requests,
|
|
237
|
+
| [Testing](./docs/testing.md) | Inject requests, fakes, spies, time travel, db assertions |
|
|
201
238
|
| [Debugging](./docs/debugging.md) | dump() and dd() (dump-and-die) |
|
|
202
239
|
| [Validation](./docs/validation.md) | `validate()` with Zod, auto-422 field errors |
|
|
203
240
|
| [Inertia](./docs/inertia.md) | Server-side Inertia.js adapter |
|
|
204
241
|
| [Vite](./docs/vite.md) | Frontend build: HMR in dev, hashed manifest in prod |
|
|
205
|
-
| [The Console](./docs/console.md) |
|
|
242
|
+
| [The Console](./docs/console.md) | Typed commands, prompts, terminal UI, REPL, `make:*` |
|
|
206
243
|
| [Architecture](./docs/architecture.md) | Container, kernel, request lifecycle |
|
|
207
244
|
| [Built on Hono](./docs/hono.md) | The Hono layer underneath, and using it directly |
|
|
245
|
+
| [Building with AI](./docs/ai.md) | MCP server, `AGENTS.md`, `llms.txt`, agent workflow |
|
|
208
246
|
|
|
209
247
|
## Testing
|
|
210
248
|
|
package/bin/keel-mcp.mjs
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Published entry for the Keel MCP server. Registered as the `keel-mcp` bin,
|
|
3
|
+
// so an installed app can wire it up with `npx keel-mcp` (see docs/ai.md).
|
|
4
|
+
import { runMcpServer } from "../dist/mcp/server.js";
|
|
5
|
+
|
|
6
|
+
runMcpServer().catch((err) => {
|
|
7
|
+
console.error(err);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API-resource defaults. Read off `config("api")`, so an app can set global
|
|
3
|
+
* pagination limits in `config/api.ts`; every `apiResource()` inherits them and
|
|
4
|
+
* can override per-resource.
|
|
5
|
+
*/
|
|
6
|
+
export interface ApiConfig {
|
|
7
|
+
/** Default page size for list endpoints. */
|
|
8
|
+
perPage: number;
|
|
9
|
+
/** Hard ceiling on `?perPage=` — the guard against "give me everything". */
|
|
10
|
+
maxPerPage: number;
|
|
11
|
+
}
|
|
12
|
+
export declare const defaultConfig: ApiConfig;
|
|
13
|
+
export declare function apiDefaults(): ApiConfig;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API-resource defaults. Read off `config("api")`, so an app can set global
|
|
3
|
+
* pagination limits in `config/api.ts`; every `apiResource()` inherits them and
|
|
4
|
+
* can override per-resource.
|
|
5
|
+
*/
|
|
6
|
+
import { config } from "../core/helpers.js";
|
|
7
|
+
export const defaultConfig = {
|
|
8
|
+
perPage: 25,
|
|
9
|
+
maxPerPage: 100,
|
|
10
|
+
};
|
|
11
|
+
export function apiDefaults() {
|
|
12
|
+
const raw = config("api", {});
|
|
13
|
+
return { ...defaultConfig, ...raw };
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keel API resources — a full CRUD REST API from a model, imported from
|
|
3
|
+
* `@shaferllc/keel/api`.
|
|
4
|
+
*
|
|
5
|
+
* import { apiResource } from "@shaferllc/keel/api";
|
|
6
|
+
* apiResource(router, Post, { filter: ["status"], body: PostSchema, access: { read: true } });
|
|
7
|
+
*
|
|
8
|
+
* The generated routes are documented automatically by `@shaferllc/keel/openapi`.
|
|
9
|
+
*/
|
|
10
|
+
export { apiResource } from "./resource.js";
|
|
11
|
+
export type { ApiResourceOptions, ApiAccess, Access, ApiAction, ApiTransform, ModelStatic, } from "./resource.js";
|
|
12
|
+
export { ApiServiceProvider } from "./provider.js";
|
|
13
|
+
export { defaultConfig, apiDefaults } from "./config.js";
|
|
14
|
+
export type { ApiConfig } from "./config.js";
|
|
15
|
+
export { parseListParams, applyListParams } from "./query.js";
|
|
16
|
+
export type { ListParams, ListQueryOptions } from "./query.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keel API resources — a full CRUD REST API from a model, imported from
|
|
3
|
+
* `@shaferllc/keel/api`.
|
|
4
|
+
*
|
|
5
|
+
* import { apiResource } from "@shaferllc/keel/api";
|
|
6
|
+
* apiResource(router, Post, { filter: ["status"], body: PostSchema, access: { read: true } });
|
|
7
|
+
*
|
|
8
|
+
* The generated routes are documented automatically by `@shaferllc/keel/openapi`.
|
|
9
|
+
*/
|
|
10
|
+
export { apiResource } from "./resource.js";
|
|
11
|
+
export { ApiServiceProvider } from "./provider.js";
|
|
12
|
+
export { defaultConfig, apiDefaults } from "./config.js";
|
|
13
|
+
export { parseListParams, applyListParams } from "./query.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional provider for the API-resource layer. `apiResource()` works without it —
|
|
3
|
+
* it just reads sensible defaults. Register this to override the pagination
|
|
4
|
+
* defaults in `config/api.ts` and to publish that stub.
|
|
5
|
+
*/
|
|
6
|
+
import { PackageProvider } from "../core/package.js";
|
|
7
|
+
export declare class ApiServiceProvider extends PackageProvider {
|
|
8
|
+
readonly name = "api";
|
|
9
|
+
register(): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional provider for the API-resource layer. `apiResource()` works without it —
|
|
3
|
+
* it just reads sensible defaults. Register this to override the pagination
|
|
4
|
+
* defaults in `config/api.ts` and to publish that stub.
|
|
5
|
+
*/
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { PackageProvider } from "../core/package.js";
|
|
9
|
+
import { defaultConfig } from "./config.js";
|
|
10
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
export class ApiServiceProvider extends PackageProvider {
|
|
12
|
+
name = "api";
|
|
13
|
+
register() {
|
|
14
|
+
this.mergeConfig("api", defaultConfig);
|
|
15
|
+
this.publishes({ [join(here, "api.config.stub")]: "config/api.ts" }, "api-config");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn the query string of a list request into filters, sorting, and a page —
|
|
3
|
+
* safely. The cardinal rule of an auto-generated API: **never** let the client
|
|
4
|
+
* filter or sort by an arbitrary column. Everything here is checked against an
|
|
5
|
+
* allow-list; anything not on it is silently ignored, not passed to SQL. That's
|
|
6
|
+
* what keeps `?password=…` or `?sort=secret_column` from doing anything.
|
|
7
|
+
*/
|
|
8
|
+
import type { Ctx } from "../core/http/router.js";
|
|
9
|
+
import type { QueryBuilder } from "../core/database.js";
|
|
10
|
+
export interface ListQueryOptions {
|
|
11
|
+
/** Columns a client may filter on (`?status=published`). */
|
|
12
|
+
filter: string[];
|
|
13
|
+
/** Columns a client may sort by (`?sort=title,-createdAt`). */
|
|
14
|
+
sort: string[];
|
|
15
|
+
/** Default page size. */
|
|
16
|
+
perPage: number;
|
|
17
|
+
/** Maximum `?perPage=`. */
|
|
18
|
+
maxPerPage: number;
|
|
19
|
+
}
|
|
20
|
+
export interface ListParams {
|
|
21
|
+
page: number;
|
|
22
|
+
perPage: number;
|
|
23
|
+
filters: Array<{
|
|
24
|
+
column: string;
|
|
25
|
+
value: string;
|
|
26
|
+
}>;
|
|
27
|
+
sort: Array<{
|
|
28
|
+
column: string;
|
|
29
|
+
direction: "asc" | "desc";
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
/** Parse (and allow-list) the list query params. */
|
|
33
|
+
export declare function parseListParams(c: Ctx, opts: ListQueryOptions): ListParams;
|
|
34
|
+
/** Apply parsed filters and sorting to a query builder. */
|
|
35
|
+
export declare function applyListParams(query: QueryBuilder, params: ListParams): QueryBuilder;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn the query string of a list request into filters, sorting, and a page —
|
|
3
|
+
* safely. The cardinal rule of an auto-generated API: **never** let the client
|
|
4
|
+
* filter or sort by an arbitrary column. Everything here is checked against an
|
|
5
|
+
* allow-list; anything not on it is silently ignored, not passed to SQL. That's
|
|
6
|
+
* what keeps `?password=…` or `?sort=secret_column` from doing anything.
|
|
7
|
+
*/
|
|
8
|
+
/** Query keys that control pagination/sorting, never treated as filters. */
|
|
9
|
+
const RESERVED = new Set(["page", "perPage", "per_page", "sort", "q", "limit", "offset"]);
|
|
10
|
+
function toInt(value, fallback) {
|
|
11
|
+
const n = Number(value);
|
|
12
|
+
return Number.isFinite(n) ? Math.trunc(n) : fallback;
|
|
13
|
+
}
|
|
14
|
+
/** Parse (and allow-list) the list query params. */
|
|
15
|
+
export function parseListParams(c, opts) {
|
|
16
|
+
const q = c.req.query();
|
|
17
|
+
const page = Math.max(1, toInt(q.page, 1));
|
|
18
|
+
const perPage = Math.min(Math.max(1, toInt(q.perPage ?? q.per_page, opts.perPage)), opts.maxPerPage);
|
|
19
|
+
const allowFilter = new Set(opts.filter);
|
|
20
|
+
const filters = Object.entries(q)
|
|
21
|
+
.filter(([key]) => !RESERVED.has(key) && allowFilter.has(key))
|
|
22
|
+
.map(([column, value]) => ({ column, value }));
|
|
23
|
+
const allowSort = new Set(opts.sort);
|
|
24
|
+
const sort = (q.sort ?? "")
|
|
25
|
+
.split(",")
|
|
26
|
+
.map((s) => s.trim())
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
.map((s) => s.startsWith("-")
|
|
29
|
+
? { column: s.slice(1), direction: "desc" }
|
|
30
|
+
: { column: s, direction: "asc" })
|
|
31
|
+
.filter((s) => allowSort.has(s.column));
|
|
32
|
+
return { page, perPage, filters, sort };
|
|
33
|
+
}
|
|
34
|
+
/** Apply parsed filters and sorting to a query builder. */
|
|
35
|
+
export function applyListParams(query, params) {
|
|
36
|
+
let q = query;
|
|
37
|
+
for (const f of params.filters)
|
|
38
|
+
q = q.where(f.column, f.value);
|
|
39
|
+
for (const s of params.sort)
|
|
40
|
+
q = q.orderBy(s.column, s.direction);
|
|
41
|
+
return q;
|
|
42
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `apiResource(router, Model, options)` — a full CRUD REST API from a Keel model,
|
|
3
|
+
* the Remult idea done the Keel way: explicit, server-side, and composed from
|
|
4
|
+
* pieces you already have. It registers real routes (so the OpenAPI package
|
|
5
|
+
* documents them for free), runs writes through the model's mass-assignment guard
|
|
6
|
+
* and your Zod schema, paginates and allow-list-filters reads, and gates every
|
|
7
|
+
* action.
|
|
8
|
+
*
|
|
9
|
+
* apiResource(router, Post, {
|
|
10
|
+
* filter: ["status", "authorId"],
|
|
11
|
+
* sort: ["createdAt", "title"],
|
|
12
|
+
* body: PostSchema,
|
|
13
|
+
* access: { read: true, write: (c) => isEditor(c) },
|
|
14
|
+
* scope: (q) => q.where("deleted", false),
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* Access is **deny by default**: an action with no `access` rule returns 403.
|
|
18
|
+
* That's the safe default for a generated API — you opt routes open, never shut.
|
|
19
|
+
*/
|
|
20
|
+
import type { Router, Ctx } from "../core/http/router.js";
|
|
21
|
+
import type { QueryBuilder, Row } from "../core/database.js";
|
|
22
|
+
import type { Model } from "../core/model.js";
|
|
23
|
+
import type { Schema } from "../core/validation.js";
|
|
24
|
+
/** A Model subclass (the class itself, with its statics). */
|
|
25
|
+
export type ModelStatic = typeof Model;
|
|
26
|
+
/** A CRUD action. */
|
|
27
|
+
export type ApiAction = "list" | "read" | "create" | "update" | "delete";
|
|
28
|
+
/** An access rule: a flat allow/deny, or a per-request predicate. */
|
|
29
|
+
export type Access = boolean | ((c: Ctx) => boolean | Promise<boolean>);
|
|
30
|
+
/**
|
|
31
|
+
* Access rules per action, with shorthands. Resolution for an action prefers its
|
|
32
|
+
* own key, then the read/write shorthand, then `all`; absent → denied.
|
|
33
|
+
*/
|
|
34
|
+
export interface ApiAccess {
|
|
35
|
+
/** Fallback for every action. */
|
|
36
|
+
all?: Access;
|
|
37
|
+
/** Shorthand for `list` + `get`. */
|
|
38
|
+
read?: Access;
|
|
39
|
+
/** Shorthand for `create` + `update` + `delete`. */
|
|
40
|
+
write?: Access;
|
|
41
|
+
list?: Access;
|
|
42
|
+
/** The single-item read (GET /:id). */
|
|
43
|
+
get?: Access;
|
|
44
|
+
create?: Access;
|
|
45
|
+
update?: Access;
|
|
46
|
+
delete?: Access;
|
|
47
|
+
}
|
|
48
|
+
/** How a model is turned into API output — a function, or a Keel Transformer. */
|
|
49
|
+
export type ApiTransform = ((model: Model, c: Ctx) => unknown) | {
|
|
50
|
+
item(value: Model): unknown;
|
|
51
|
+
collection(values: Model[]): unknown[];
|
|
52
|
+
};
|
|
53
|
+
export interface ApiResourceOptions {
|
|
54
|
+
/** Base path. Default: the model's table name. */
|
|
55
|
+
path?: string;
|
|
56
|
+
/** Route-name prefix. Default: the path. */
|
|
57
|
+
name?: string;
|
|
58
|
+
/** Only expose these actions. */
|
|
59
|
+
only?: ApiAction[];
|
|
60
|
+
/** Expose everything except these. */
|
|
61
|
+
except?: ApiAction[];
|
|
62
|
+
/** Columns clients may filter on. Empty = no filtering. */
|
|
63
|
+
filter?: string[];
|
|
64
|
+
/** Columns clients may sort by. Empty = no sorting. */
|
|
65
|
+
sort?: string[];
|
|
66
|
+
/** Default page size (overrides `config("api.perPage")`). */
|
|
67
|
+
perPage?: number;
|
|
68
|
+
/** Max page size (overrides `config("api.maxPerPage")`). */
|
|
69
|
+
maxPerPage?: number;
|
|
70
|
+
/** Zod (or Zod-like) schema validating create + update bodies. */
|
|
71
|
+
body?: Schema<unknown>;
|
|
72
|
+
/** Schema for create only (overrides `body`). */
|
|
73
|
+
createBody?: Schema<unknown>;
|
|
74
|
+
/** Schema for update only (overrides `body`). */
|
|
75
|
+
updateBody?: Schema<unknown>;
|
|
76
|
+
/** Access rules. Deny by default. */
|
|
77
|
+
access?: ApiAccess;
|
|
78
|
+
/**
|
|
79
|
+
* Constrain the base query for **every** row operation — list, read, update,
|
|
80
|
+
* delete. This is row-level security: a row outside the scope reads as 404, so
|
|
81
|
+
* it can't be fetched, changed, or removed.
|
|
82
|
+
*/
|
|
83
|
+
scope?: (query: QueryBuilder, c: Ctx) => QueryBuilder | void;
|
|
84
|
+
/** Shape the output. Default: `model.toJSON()`. */
|
|
85
|
+
transform?: ApiTransform;
|
|
86
|
+
/** Mutate the write payload before it's saved (set an owner id, timestamps…). */
|
|
87
|
+
beforeWrite?: (data: Row, c: Ctx, action: "create" | "update") => Row | Promise<Row>;
|
|
88
|
+
/** OpenAPI tags for these routes. Default: `[path]`. */
|
|
89
|
+
tags?: string[];
|
|
90
|
+
}
|
|
91
|
+
export declare function apiResource(router: Router, model: ModelStatic, options?: ApiResourceOptions): void;
|