@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
@@ -0,0 +1,265 @@
1
+ # Controllers
2
+
3
+ Controllers are plain classes in `app/Controllers/`. Each public method is an
4
+ action bound to a route. They're resolved from the [container](./container.md),
5
+ so they get dependency injection and a fresh instance per request.
6
+
7
+ ```ts
8
+ import type { Ctx } from "@shaferllc/keel/core";
9
+ import { json, param } from "@shaferllc/keel/core";
10
+
11
+ export class PostController {
12
+ index() {
13
+ return json({ posts: [] });
14
+ }
15
+
16
+ show() {
17
+ return json({ id: param("id") });
18
+ }
19
+ }
20
+ ```
21
+
22
+ Bind actions in your routes with a `[Controller, method]` tuple:
23
+
24
+ ```ts
25
+ router.get("/posts", [PostController, "index"]);
26
+ router.get("/posts/:id", [PostController, "show"]);
27
+ ```
28
+
29
+ A controller action is just one of the [three handler forms](./routing.md#three-kinds-of-handler)
30
+ the router accepts — the array form. The other two (a closure, or a ready-made
31
+ `Response`) live inline on the route; controllers are for anything with enough
32
+ weight to earn its own class.
33
+
34
+ ## The request context
35
+
36
+ Every action is called with the request [context](./request-response.md) as its
37
+ first argument — the `Ctx` type, which is Hono's `Context`. You can read from it
38
+ directly, or ignore it and reach for the ambient request helpers (`param`,
39
+ `query`, `body`) that resolve the current request from async-context storage.
40
+ Both styles work; pick whichever reads better.
41
+
42
+ ```ts
43
+ import type { Ctx } from "@shaferllc/keel/core";
44
+ import { param } from "@shaferllc/keel/core";
45
+
46
+ export class PostController {
47
+ // Take the context explicitly…
48
+ show(c: Ctx) {
49
+ return c.json({ id: c.req.param("id") });
50
+ }
51
+
52
+ // …or lean on the ambient helpers and drop the argument.
53
+ edit() {
54
+ return json({ id: param("id") });
55
+ }
56
+ }
57
+ ```
58
+
59
+ The context is what Keel hands the action under the hood — the router resolves
60
+ your controller from the container, then calls
61
+ `action.call(controller, c)`, so `this` is the controller instance and the sole
62
+ argument is the `Ctx`.
63
+
64
+ ## Dependency injection
65
+
66
+ A controller's constructor receives the container, so it can resolve anything:
67
+
68
+ ```ts
69
+ import type { Container, Ctx } from "@shaferllc/keel/core";
70
+ import { Mailer } from "../Services/Mailer.js";
71
+
72
+ export class UserController {
73
+ constructor(private app: Container) {}
74
+
75
+ store() {
76
+ const mailer = this.app.make(Mailer);
77
+ // …
78
+ }
79
+ }
80
+ ```
81
+
82
+ The container instantiates the controller with `new Controller(container)` — an
83
+ unbound class is auto-built, no registration needed. That happens **per
84
+ request**: each hit resolves a fresh instance, so it's safe to stash
85
+ request-scoped state on `this` without leaking it across requests.
86
+
87
+ If you'd rather inject specific services than the whole container, give the
88
+ controller a constructor that takes them and bind it in a
89
+ [service provider](./providers.md), pulling each dependency out of the container:
90
+
91
+ ```ts
92
+ // InvoiceController's constructor takes a Mailer, not the container.
93
+ app.bind(InvoiceController, (c) => new InvoiceController(c.make(Mailer)));
94
+ ```
95
+
96
+ ## Single-action controllers
97
+
98
+ For a controller that does one thing, define a `handle` method and reference the
99
+ class with no method name:
100
+
101
+ ```ts
102
+ export class PublishPost {
103
+ handle() {
104
+ return json({ published: true });
105
+ }
106
+ }
107
+
108
+ router.post("/posts/:id/publish", [PublishPost]); // calls handle()
109
+ ```
110
+
111
+ `[Controller]` and `[Controller, "handle"]` are equivalent — the method name
112
+ defaults to `"handle"` when the tuple has just one element. Referencing a method
113
+ the controller doesn't define throws at request time:
114
+ `Controller [PublishPost] has no method [handle].`
115
+
116
+ ## Lazy-loaded controllers
117
+
118
+ Pass a `() => import(...)` loader instead of the class, and the controller is
119
+ only imported when its route is first hit — handy for large apps and cold
120
+ starts:
121
+
122
+ ```ts
123
+ router.get("/reports", [() => import("../Controllers/ReportController.js"), "index"]);
124
+ ```
125
+
126
+ The loader may resolve to a default export or the class itself — Keel unwraps
127
+ `.default` if present, otherwise uses the module value directly. Both work:
128
+
129
+ ```ts
130
+ // default export
131
+ export default class ReportController { index() { /* … */ } }
132
+
133
+ // named export — point the loader at the property
134
+ router.get("/reports", [
135
+ () => import("../Controllers/ReportController.js").then((m) => m.ReportController),
136
+ "index",
137
+ ]);
138
+ ```
139
+
140
+ The loader must be an **arrow function** (or any function with no `prototype`).
141
+ Keel distinguishes an eager controller from a lazy loader by checking for a
142
+ `prototype` — classes have one, arrow functions don't — so a lazy controller
143
+ written as a `function` declaration would be mistaken for a class. Stick to
144
+ `() => import(...)`.
145
+
146
+ ## Resource controllers
147
+
148
+ Generate a RESTful controller with all seven actions:
149
+
150
+ ```bash
151
+ npm run keel make:controller Post --resource
152
+ ```
153
+
154
+ That writes `app/Controllers/PostController.ts` with the conventional set —
155
+ `index`, `create`, `store`, `show`, `edit`, `update`, `destroy`:
156
+
157
+ ```ts
158
+ import type { Ctx } from "@shaferllc/keel/core";
159
+
160
+ export class PostController {
161
+ index(c: Ctx) { return c.json({ action: "index" }); }
162
+ create(c: Ctx) { return c.json({ action: "create" }); }
163
+ store(c: Ctx) { return c.json({ action: "store" }); }
164
+ show(c: Ctx) { return c.json({ action: "show" }); }
165
+ edit(c: Ctx) { return c.json({ action: "edit" }); }
166
+ update(c: Ctx) { return c.json({ action: "update" }); }
167
+ destroy(c: Ctx) { return c.json({ action: "destroy" }); }
168
+ }
169
+ ```
170
+
171
+ Drop `--resource` (or `-r`) for a bare controller with a single `index` action.
172
+
173
+ Then wire the whole set up in one line (see [Routing → Resource routes](./routing.md#resource-routes)):
174
+
175
+ ```ts
176
+ router.resource("posts", PostController);
177
+ router.resource("posts.comments", CommentController); // nested
178
+ router.resource("posts", PostController)
179
+ .apiOnly()
180
+ .as("articles")
181
+ .params({ posts: "post" })
182
+ .use(["store", "update", "destroy"], auth);
183
+ ```
184
+
185
+ `router.resource` maps each of the seven route entries onto the matching
186
+ controller method by name — so a resource controller just needs methods with
187
+ those names. Trim the set with `.only()` / `.except()` / `.apiOnly()` when you
188
+ don't implement all seven.
189
+
190
+ ## Related
191
+
192
+ - [Routing](./routing.md) — the `Router` methods (`get`, `post`, `resource`, …)
193
+ that bind these controllers, plus closures and static-response handlers.
194
+ - [Container](./container.md) — how controllers (and their dependencies) are
195
+ resolved and constructed.
196
+ - [Request & response](./request-response.md) — the `Ctx` object and the ambient
197
+ `param` / `query` / `json` helpers actions use.
198
+
199
+ ---
200
+
201
+ ## API reference
202
+
203
+ Controllers are a usage pattern, not an exported API — you write the classes, and
204
+ the [`Router`](./routing.md) binds them. The two exported types you touch when
205
+ typing an action or a route are `Ctx` and `RouteHandler`, both from
206
+ `@shaferllc/keel/core`.
207
+
208
+ ### Types
209
+
210
+ #### `Ctx`
211
+
212
+ `type Ctx = Context` (Hono's request `Context`)
213
+
214
+ The request context passed as the first argument to every route handler and
215
+ controller action. Read params/headers/body off it and build responses with it —
216
+ or ignore it and use the ambient request helpers.
217
+
218
+ ```ts
219
+ import type { Ctx } from "@shaferllc/keel/core";
220
+
221
+ export class UserController {
222
+ show(c: Ctx) {
223
+ const id = c.req.param("id"); // route param
224
+ const q = c.req.query("expand"); // query string
225
+ return c.json({ id, expand: q }); // JSON response
226
+ }
227
+ }
228
+ ```
229
+
230
+ **Notes:** it's an alias for Hono's `Context`, so anything in Hono's context API
231
+ (`c.req`, `c.json`, `c.html`, `c.header`, `c.get`/`c.set`, `c.env`) is available.
232
+ An action may also take no argument and use `param()`/`query()`/`json()` instead,
233
+ which resolve the current request from async-context storage — see
234
+ [request & response](./request-response.md).
235
+
236
+ #### `RouteHandler`
237
+
238
+ `type RouteHandler = HandlerFn | ControllerAction | Response`
239
+
240
+ The union every `Router` verb accepts as its handler. A controller action is the
241
+ `ControllerAction` arm: `[Controller]`, `[Controller, "method"]`, or a lazy
242
+ `[() => import(...), "method"]`.
243
+
244
+ ```ts
245
+ import type { RouteHandler } from "@shaferllc/keel/core";
246
+ import { json } from "@shaferllc/keel/core";
247
+
248
+ // each arm of the union is a valid handler
249
+ const closure: RouteHandler = () => json({ ok: true });
250
+ const staticResp: RouteHandler = json({ status: "ok" });
251
+ const action: RouteHandler = [UserController, "show"];
252
+ const single: RouteHandler = [PublishPost]; // calls handle()
253
+ const lazy: RouteHandler = [
254
+ () => import("../Controllers/ReportController.js"),
255
+ "index",
256
+ ];
257
+ ```
258
+
259
+ **Notes:** the constituent types (`HandlerFn`, `ControllerAction`,
260
+ `ControllerRef`, `LazyController`) are internal to the router and not exported —
261
+ annotate values as `RouteHandler` when you need an explicit type. The router
262
+ turns any of these into an executable function at boot; a controller arm is
263
+ resolved from the container per request. For the verbs that consume a
264
+ `RouteHandler` (`get`, `post`, `put`, `patch`, `delete`, `any`, `route`,
265
+ `resource`, …), see [Routing → API reference](./routing.md#api-reference).
package/docs/cors.md ADDED
@@ -0,0 +1,51 @@
1
+ # CORS
2
+
3
+ Cross-Origin Resource Sharing lets browsers on other origins call your API. The
4
+ `cors()` middleware sets the `Access-Control-*` headers and answers preflight
5
+ `OPTIONS` requests for you.
6
+
7
+ ## Enabling
8
+
9
+ Register it in your [HTTP kernel](./middleware.md) (app-wide) or on a route group:
10
+
11
+ ```ts
12
+ import { cors } from "@shaferllc/keel/core";
13
+
14
+ // In the kernel — applies to every route
15
+ this.use(cors());
16
+
17
+ // Or scoped to an API group
18
+ router.group(() => { /* … */ }).use(cors({ origin: ["https://app.example.com"] }));
19
+ ```
20
+
21
+ With no options, `cors()` reflects the caller's origin — convenient in
22
+ development. **Lock it down in production** with an explicit allowlist.
23
+
24
+ ## Options
25
+
26
+ ```ts
27
+ cors({
28
+ origin: ["https://app.example.com"], // true (reflect) | false | "*" | string[] | (origin, c) => …
29
+ methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
30
+ headers: true, // true (reflect requested) | string[]
31
+ exposeHeaders: ["X-Request-Id"], // response headers JS may read
32
+ credentials: true, // send Access-Control-Allow-Credentials
33
+ maxAge: 86400, // preflight cache seconds; null to omit
34
+ });
35
+ ```
36
+
37
+ - **`origin`** — `true` reflects the request origin, `false` blocks everything,
38
+ `"*"` allows any, an array is an allowlist, and a `(origin, c) => …` predicate
39
+ returns `true`/`false`/a specific origin for dynamic decisions (e.g. any
40
+ `localhost` port in dev).
41
+ - **`credentials`** — when on, the spec forbids `"*"`, so `cors()` automatically
42
+ reflects the concrete origin and adds `Vary: Origin`.
43
+ - **`headers`** — `true` echoes whatever the browser asks for in the preflight;
44
+ an array pins an explicit allowlist.
45
+
46
+ ## Preflight
47
+
48
+ Browsers send an `OPTIONS` request with `Access-Control-Request-Method` before
49
+ certain cross-origin calls. `cors()` detects these and responds `204` with the
50
+ allow headers directly — your route never runs. Everything else falls through to
51
+ your handler with the CORS response headers attached.