@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.
Files changed (239) hide show
  1. package/AGENTS.md +169 -0
  2. package/README.md +44 -6
  3. package/bin/keel-mcp.mjs +9 -0
  4. package/dist/api/api.config.stub +9 -0
  5. package/dist/api/config.d.ts +13 -0
  6. package/dist/api/config.js +14 -0
  7. package/dist/api/index.d.ts +16 -0
  8. package/dist/api/index.js +13 -0
  9. package/dist/api/provider.d.ts +10 -0
  10. package/dist/api/provider.js +17 -0
  11. package/dist/api/query.d.ts +35 -0
  12. package/dist/api/query.js +42 -0
  13. package/dist/api/resource.d.ts +91 -0
  14. package/dist/api/resource.js +188 -0
  15. package/dist/core/application.d.ts +5 -5
  16. package/dist/core/application.js +8 -2
  17. package/dist/core/cache.d.ts +81 -5
  18. package/dist/core/cache.js +188 -23
  19. package/dist/core/cli/stubs.d.ts +26 -0
  20. package/dist/core/cli/stubs.js +225 -0
  21. package/dist/core/console-prompt.d.ts +79 -0
  22. package/dist/core/console-prompt.js +239 -0
  23. package/dist/core/console-ui.d.ts +96 -0
  24. package/dist/core/console-ui.js +187 -0
  25. package/dist/core/console.d.ts +188 -0
  26. package/dist/core/console.js +395 -0
  27. package/dist/core/database.d.ts +70 -1
  28. package/dist/core/database.js +174 -15
  29. package/dist/core/env.d.ts +96 -0
  30. package/dist/core/env.js +140 -0
  31. package/dist/core/events.d.ts +129 -5
  32. package/dist/core/events.js +165 -7
  33. package/dist/core/health.d.ts +141 -0
  34. package/dist/core/health.js +226 -0
  35. package/dist/core/helpers.d.ts +9 -3
  36. package/dist/core/helpers.js +11 -3
  37. package/dist/core/http/kernel.d.ts +2 -0
  38. package/dist/core/http/kernel.js +48 -0
  39. package/dist/core/http/router.d.ts +5 -5
  40. package/dist/core/http/router.js +5 -5
  41. package/dist/core/i18n.d.ts +162 -0
  42. package/dist/core/i18n.js +472 -0
  43. package/dist/core/index.d.ts +39 -13
  44. package/dist/core/index.js +19 -6
  45. package/dist/core/instrumentation.d.ts +113 -0
  46. package/dist/core/instrumentation.js +52 -0
  47. package/dist/core/lock.d.ts +139 -0
  48. package/dist/core/lock.js +215 -0
  49. package/dist/core/logger.d.ts +89 -4
  50. package/dist/core/logger.js +167 -22
  51. package/dist/core/mail.d.ts +128 -7
  52. package/dist/core/mail.js +264 -16
  53. package/dist/core/notification.js +10 -1
  54. package/dist/core/package.d.ts +120 -0
  55. package/dist/core/package.js +169 -0
  56. package/dist/core/pages.d.ts +108 -0
  57. package/dist/core/pages.js +199 -0
  58. package/dist/core/queue.d.ts +134 -9
  59. package/dist/core/queue.js +325 -14
  60. package/dist/core/repl.d.ts +33 -0
  61. package/dist/core/repl.js +88 -0
  62. package/dist/core/scheduler.js +6 -0
  63. package/dist/core/social.d.ts +4 -4
  64. package/dist/core/social.js +4 -4
  65. package/dist/core/storage.d.ts +159 -6
  66. package/dist/core/storage.js +299 -7
  67. package/dist/core/telemetry.d.ts +208 -0
  68. package/dist/core/telemetry.js +383 -0
  69. package/dist/core/template.d.ts +2 -3
  70. package/dist/core/template.js +2 -3
  71. package/dist/core/testing.d.ts +170 -1
  72. package/dist/core/testing.js +504 -2
  73. package/dist/db/d1.js +13 -0
  74. package/dist/db/pg.d.ts +13 -0
  75. package/dist/db/pg.js +46 -4
  76. package/dist/mcp/server.d.ts +19 -0
  77. package/dist/mcp/server.js +355 -0
  78. package/dist/openapi/config.d.ts +28 -0
  79. package/dist/openapi/config.js +25 -0
  80. package/dist/openapi/doc.d.ts +40 -0
  81. package/dist/openapi/doc.js +20 -0
  82. package/dist/openapi/export.d.ts +8 -0
  83. package/dist/openapi/export.js +19 -0
  84. package/dist/openapi/gate.d.ts +15 -0
  85. package/dist/openapi/gate.js +27 -0
  86. package/dist/openapi/index.d.ts +19 -0
  87. package/dist/openapi/index.js +15 -0
  88. package/dist/openapi/openapi.config.stub +29 -0
  89. package/dist/openapi/provider.d.ts +18 -0
  90. package/dist/openapi/provider.js +35 -0
  91. package/dist/openapi/routes.d.ts +9 -0
  92. package/dist/openapi/routes.js +23 -0
  93. package/dist/openapi/spec.d.ts +23 -0
  94. package/dist/openapi/spec.js +132 -0
  95. package/dist/openapi/ui.d.ts +8 -0
  96. package/dist/openapi/ui.js +31 -0
  97. package/dist/openapi/zod.d.ts +12 -0
  98. package/dist/openapi/zod.js +46 -0
  99. package/dist/watch/config.d.ts +33 -0
  100. package/dist/watch/config.js +38 -0
  101. package/dist/watch/entry.d.ts +53 -0
  102. package/dist/watch/entry.js +105 -0
  103. package/dist/watch/gate.d.ts +20 -0
  104. package/dist/watch/gate.js +32 -0
  105. package/dist/watch/index.d.ts +21 -0
  106. package/dist/watch/index.js +17 -0
  107. package/dist/watch/migration.d.ts +7 -0
  108. package/dist/watch/migration.js +20 -0
  109. package/dist/watch/provider.d.ts +22 -0
  110. package/dist/watch/provider.js +58 -0
  111. package/dist/watch/prune.d.ts +11 -0
  112. package/dist/watch/prune.js +20 -0
  113. package/dist/watch/recorder.d.ts +24 -0
  114. package/dist/watch/recorder.js +39 -0
  115. package/dist/watch/routes.d.ts +13 -0
  116. package/dist/watch/routes.js +55 -0
  117. package/dist/watch/store.d.ts +54 -0
  118. package/dist/watch/store.js +158 -0
  119. package/dist/watch/ui/dist/watch.css +1 -0
  120. package/dist/watch/ui/dist/watch.js +555 -0
  121. package/dist/watch/ui-shell.d.ts +12 -0
  122. package/dist/watch/ui-shell.js +24 -0
  123. package/dist/watch/watch.config.stub +47 -0
  124. package/dist/watch/watchers.d.ts +12 -0
  125. package/dist/watch/watchers.js +156 -0
  126. package/docs/ai-manifest.json +3206 -0
  127. package/docs/ai.md +128 -0
  128. package/docs/api-resources.md +118 -0
  129. package/docs/architecture.md +331 -0
  130. package/docs/authentication.md +453 -0
  131. package/docs/authorization.md +167 -0
  132. package/docs/broadcasting.md +137 -0
  133. package/docs/broker.md +500 -0
  134. package/docs/cache.md +558 -0
  135. package/docs/configuration.md +385 -0
  136. package/docs/console.md +536 -0
  137. package/docs/container.md +467 -0
  138. package/docs/controllers.md +265 -0
  139. package/docs/cors.md +51 -0
  140. package/docs/database.md +631 -0
  141. package/docs/debugging.md +129 -0
  142. package/docs/decorators.md +127 -0
  143. package/docs/errors.md +395 -0
  144. package/docs/events.md +496 -0
  145. package/docs/examples/architecture-app.ts +27 -0
  146. package/docs/examples/authentication.ts +61 -0
  147. package/docs/examples/authorization.ts +79 -0
  148. package/docs/examples/broadcasting.ts +60 -0
  149. package/docs/examples/broker-cache-validate.ts +34 -0
  150. package/docs/examples/broker-fault-tolerance.ts +29 -0
  151. package/docs/examples/broker-middleware.ts +27 -0
  152. package/docs/examples/broker.ts +203 -0
  153. package/docs/examples/cache.ts +222 -0
  154. package/docs/examples/configuration.ts +121 -0
  155. package/docs/examples/console.ts +134 -0
  156. package/docs/examples/container.ts +134 -0
  157. package/docs/examples/controllers.ts +86 -0
  158. package/docs/examples/database.ts +208 -0
  159. package/docs/examples/debugging.ts +41 -0
  160. package/docs/examples/decorators.ts +40 -0
  161. package/docs/examples/errors.ts +121 -0
  162. package/docs/examples/events.ts +204 -0
  163. package/docs/examples/factories.ts +84 -0
  164. package/docs/examples/hashing.ts +71 -0
  165. package/docs/examples/health.ts +94 -0
  166. package/docs/examples/helpers.ts +171 -0
  167. package/docs/examples/hooks.ts +54 -0
  168. package/docs/examples/i18n.ts +117 -0
  169. package/docs/examples/inertia.ts +81 -0
  170. package/docs/examples/locks.ts +120 -0
  171. package/docs/examples/logger.ts +166 -0
  172. package/docs/examples/mail.ts +263 -0
  173. package/docs/examples/middleware.ts +119 -0
  174. package/docs/examples/migrations.ts +126 -0
  175. package/docs/examples/models.ts +239 -0
  176. package/docs/examples/notification.ts +124 -0
  177. package/docs/examples/pages.ts +82 -0
  178. package/docs/examples/providers.ts +123 -0
  179. package/docs/examples/queues.ts +254 -0
  180. package/docs/examples/rate-limiting.ts +42 -0
  181. package/docs/examples/redis.ts +99 -0
  182. package/docs/examples/request-response.ts +197 -0
  183. package/docs/examples/routing.ts +186 -0
  184. package/docs/examples/scheduling.ts +62 -0
  185. package/docs/examples/sessions.ts +102 -0
  186. package/docs/examples/static-files.ts +63 -0
  187. package/docs/examples/storage.ts +132 -0
  188. package/docs/examples/telemetry.ts +127 -0
  189. package/docs/examples/templates.ts +58 -0
  190. package/docs/examples/testing.ts +215 -0
  191. package/docs/examples/transformer.ts +141 -0
  192. package/docs/examples/transformers.ts +49 -0
  193. package/docs/examples/url-builder.ts +86 -0
  194. package/docs/examples/validation.ts +102 -0
  195. package/docs/examples/views.tsx +62 -0
  196. package/docs/examples/vite.ts +106 -0
  197. package/docs/factories.md +166 -0
  198. package/docs/getting-started.md +290 -0
  199. package/docs/hashing.md +259 -0
  200. package/docs/health.md +225 -0
  201. package/docs/helpers.md +347 -0
  202. package/docs/hono.md +186 -0
  203. package/docs/hooks.md +118 -0
  204. package/docs/i18n.md +302 -0
  205. package/docs/inertia.md +241 -0
  206. package/docs/locks.md +323 -0
  207. package/docs/logger.md +436 -0
  208. package/docs/mail.md +751 -0
  209. package/docs/middleware.md +425 -0
  210. package/docs/migrations.md +476 -0
  211. package/docs/models.md +810 -0
  212. package/docs/notifications.md +474 -0
  213. package/docs/openapi.md +111 -0
  214. package/docs/packages.md +118 -0
  215. package/docs/pages.md +217 -0
  216. package/docs/providers.md +363 -0
  217. package/docs/queues.md +679 -0
  218. package/docs/rate-limiting.md +155 -0
  219. package/docs/redis.md +178 -0
  220. package/docs/request-response.md +953 -0
  221. package/docs/routing.md +804 -0
  222. package/docs/scheduling.md +110 -0
  223. package/docs/security.md +85 -0
  224. package/docs/sessions.md +354 -0
  225. package/docs/social-auth.md +174 -0
  226. package/docs/static-files.md +211 -0
  227. package/docs/storage.md +452 -0
  228. package/docs/telemetry.md +263 -0
  229. package/docs/templates.md +314 -0
  230. package/docs/testing.md +376 -0
  231. package/docs/transformers.md +381 -0
  232. package/docs/url-builder.md +295 -0
  233. package/docs/validation.md +288 -0
  234. package/docs/views.md +267 -0
  235. package/docs/vite.md +434 -0
  236. package/docs/watch.md +118 -0
  237. package/llms-full.txt +19816 -0
  238. package/llms.txt +127 -0
  239. 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, edge-safe |
183
- | [Queues & Jobs](./docs/queues.md) | Dispatch jobs, pluggable drivers, workers |
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), edge-safe |
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, fluent response assertions |
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) | `serve`, `routes`, `make:*` generators |
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
 
@@ -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,9 @@
1
+ /**
2
+ * Keel API-resource defaults.
3
+ * Published with `keel vendor:publish --tag api-config`.
4
+ */
5
+ export default {
6
+ // Default list page size, and the hard ceiling on ?perPage=.
7
+ perPage: 25,
8
+ maxPerPage: 100,
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;