@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,263 @@
1
+ // Type-check harness for docs/mail.md. Every type-checkable snippet in the doc
2
+ // is exercised here against the real exports, so a renamed method or wrong
3
+ // argument type fails `npm run typecheck:docs`. Compile-only — never executed.
4
+ import {
5
+ mail,
6
+ setMailer,
7
+ getMailer,
8
+ fetchTransport,
9
+ Mailer,
10
+ PendingMail,
11
+ ArrayTransport,
12
+ LogTransport,
13
+ type Message,
14
+ type Transport,
15
+ type MailerOptions,
16
+ type FetchTransportOptions,
17
+ } from "@shaferllc/keel/core";
18
+
19
+ declare const env: (key: string) => string;
20
+ declare const apiKey: string;
21
+ declare const message: Message;
22
+ declare const myProviderSdk: { emails: { send(m: Message): Promise<void> } };
23
+
24
+ export async function sending() {
25
+ await mail()
26
+ .to("ada@example.com")
27
+ .subject("Welcome aboard")
28
+ .html("<h1>Hi Ada</h1>")
29
+ .send();
30
+
31
+ await mail()
32
+ .to("a@x.com", "b@x.com")
33
+ .cc("team@x.com")
34
+ .bcc("audit@x.com")
35
+ .replyTo("support@x.com")
36
+ .from("hello@x.com")
37
+ .subject("Report")
38
+ .text("Plain-text body")
39
+ .html("<p>HTML body</p>")
40
+ .header("X-Campaign", "weekly")
41
+ .send();
42
+
43
+ await mail().fill({ to: "a@x.com", subject: "Hi", text: "body" }).send();
44
+
45
+ const sent = await mail().to("ada@example.com").subject("Hi").text("hey").send();
46
+ return { from: sent.from, to: sent.to };
47
+ }
48
+
49
+ export function configuring() {
50
+ setMailer(
51
+ fetchTransport({
52
+ url: "https://api.resend.com/emails",
53
+ headers: { Authorization: `Bearer ${env("RESEND_API_KEY")}` },
54
+ body: (m) => ({ from: m.from, to: m.to, subject: m.subject, html: m.html }),
55
+ }),
56
+ { from: "hello@myapp.com" },
57
+ );
58
+ }
59
+
60
+ export function ownTransport() {
61
+ const transport: Transport = {
62
+ async send(message) {
63
+ void message;
64
+ },
65
+ };
66
+ setMailer(transport, { from: "hello@myapp.com" });
67
+ }
68
+
69
+ export async function inTests() {
70
+ const transport = new ArrayTransport();
71
+ setMailer(transport, { from: "hi@app.com" });
72
+
73
+ await mail().to("ada@example.com").subject("Welcome").text("hi").send();
74
+
75
+ return { count: transport.sent.length, subject: transport.sent[0]?.subject };
76
+ }
77
+
78
+ export async function scopedMailer() {
79
+ const mailer = new Mailer(new ArrayTransport(), { from: "hi@app.com" });
80
+ await mailer.message().to("ada@example.com").subject("Hi").text("hey").send();
81
+ }
82
+
83
+ // ------------------------------- reference ---------------------------------
84
+
85
+ export async function referenceFunctions() {
86
+ await mail().to("ada@example.com").subject("Hi").text("hey").send();
87
+
88
+ const m1: Mailer = setMailer(fetchTransport({ url: "https://x/emails" }), {
89
+ from: "hello@myapp.com",
90
+ });
91
+
92
+ const mailer: Mailer = getMailer();
93
+ await mailer.message().to("ada@example.com").subject("Hi").text("hey").send();
94
+
95
+ const transport: Transport = fetchTransport({
96
+ url: "https://api.resend.com/emails",
97
+ headers: { Authorization: `Bearer ${apiKey}` },
98
+ body: (m) => ({ from: m.from, to: m.to, subject: m.subject, html: m.html }),
99
+ });
100
+
101
+ return { m1, transport };
102
+ }
103
+
104
+ export async function referenceMailer() {
105
+ const mailer = new Mailer(new ArrayTransport(), { from: "hi@app.com" });
106
+ const pending: PendingMail = mailer.message();
107
+ const sent: Message = await mailer.send({
108
+ to: ["ada@x.com"],
109
+ subject: "Hi",
110
+ text: "hey",
111
+ });
112
+ return { pending, sent };
113
+ }
114
+
115
+ export function referencePendingMail() {
116
+ mail().to("a@x.com", "b@x.com");
117
+ mail().from("hello@x.com");
118
+ mail().cc("team@x.com").bcc("audit@x.com");
119
+ mail().replyTo("support@x.com");
120
+ mail().subject("Welcome aboard");
121
+ mail().text("Plain body").html("<p>Rich body</p>");
122
+ mail().header("X-Campaign", "weekly");
123
+ mail().fill({ to: ["a@x.com", "b@x.com"], subject: "Hi", text: "body" });
124
+ }
125
+
126
+ export async function referenceTransports() {
127
+ const transport = new ArrayTransport();
128
+ setMailer(transport);
129
+ transport.sent.length;
130
+ transport.sent[0]?.subject;
131
+ await new ArrayTransport().send(message);
132
+
133
+ setMailer(new LogTransport(), { from: "dev@localhost" });
134
+ await new LogTransport().send(message);
135
+ }
136
+
137
+ // ---------------------------- interface / type seams -----------------------
138
+
139
+ const seamMessage: Message = {
140
+ to: ["ada@x.com"],
141
+ from: "hi@app.com",
142
+ subject: "Hi",
143
+ text: "hey",
144
+ };
145
+
146
+ const seamTransport: Transport = {
147
+ async send(message) {
148
+ await myProviderSdk.emails.send(message);
149
+ },
150
+ };
151
+
152
+ const seamOptions: MailerOptions = { from: "hello@myapp.com" };
153
+
154
+ const seamFetchOptions: FetchTransportOptions = {
155
+ url: "https://api.resend.com/emails",
156
+ headers: { Authorization: `Bearer ${apiKey}` },
157
+ body: (m) => ({ from: m.from, to: m.to, subject: m.subject, html: m.html }),
158
+ };
159
+
160
+ export { seamMessage, seamTransport, seamOptions, seamFetchOptions };
161
+
162
+ /* --- Queueing, attachments, class mails, named mailers, faking, events --- */
163
+
164
+ import {
165
+ mailer as namedMailer,
166
+ send as sendMail,
167
+ sendLater as queueMail,
168
+ fakeMail,
169
+ restoreMail,
170
+ BaseMail,
171
+ listen,
172
+ logger,
173
+ type PendingMail as PendingMailType,
174
+ type Attachment,
175
+ type RecordedMail,
176
+ } from "@shaferllc/keel/core";
177
+
178
+ declare const user2: { email: string; name: string };
179
+ declare const body: string;
180
+ declare const pdfBytes: Uint8Array;
181
+ declare const logoBytes: Uint8Array;
182
+ declare const postmark: Transport;
183
+ declare const resend: Transport;
184
+
185
+ export async function queueing() {
186
+ await mail().to(user2.email).subject("Welcome").html(body).sendLater();
187
+ }
188
+
189
+ export async function attachments() {
190
+ await mail()
191
+ .to("ada@example.com")
192
+ .subject("Your invoice")
193
+ .html('<p>Attached. <img src="cid:logo"></p>')
194
+ .attach("invoice.pdf", pdfBytes)
195
+ .attach("data.csv", "a,b,c", "text/csv")
196
+ .embed("logo", logoBytes, "logo.png")
197
+ .send();
198
+ }
199
+
200
+ export class WelcomeEmail extends BaseMail {
201
+ constructor(private user: { email: string; name: string }) {
202
+ super();
203
+ }
204
+
205
+ build(message: PendingMailType): void {
206
+ message
207
+ .to(this.user.email)
208
+ .subject(`Welcome, ${this.user.name}`)
209
+ .html(`<h1>Hi ${this.user.name}</h1>`);
210
+ }
211
+ }
212
+
213
+ export async function classBasedMails() {
214
+ await sendMail(new WelcomeEmail(user2));
215
+ await queueMail(new WelcomeEmail(user2));
216
+ await sendMail(new WelcomeEmail(user2), "marketing");
217
+ }
218
+
219
+ export async function namedMailers() {
220
+ setMailer(postmark, { from: "hi@app.com" });
221
+ setMailer(resend, { from: "news@app.com" }, "marketing");
222
+
223
+ await mail().to(user2.email).subject("Receipt").text(body).send();
224
+ await mail("marketing").to(user2.email).subject("This month").html(body).send();
225
+
226
+ return namedMailer("marketing");
227
+ }
228
+
229
+ export async function faking() {
230
+ const faked = fakeMail();
231
+
232
+ faked.assertSent();
233
+ faked.assertSent((m) => m.subject === "Welcome");
234
+ faked.assertSentCount(1);
235
+ faked.assertQueued((m) => m.to.includes("ada@example.com"));
236
+ faked.assertNotSent((m) => m.subject === "Password reset");
237
+ faked.assertNotQueued();
238
+ faked.assertQueuedCount(0);
239
+ faked.assertNothingSent();
240
+
241
+ const sent: Message[] = faked.sent();
242
+ const queued: Message[] = faked.queued();
243
+ const all: RecordedMail[] = faked.mails;
244
+
245
+ restoreMail();
246
+ restoreMail("marketing");
247
+
248
+ return { sent, queued, all };
249
+ }
250
+
251
+ export function mailEvents() {
252
+ listen("mail.sent", (message) => {
253
+ logger().info("mail sent", { subject: (message as Message).subject });
254
+ });
255
+ }
256
+
257
+ export function attachmentType(a: Attachment): string | undefined {
258
+ return a.contentType;
259
+ }
260
+
261
+ export function composed(): Message {
262
+ return mail().to("a@b.com").subject("x").text("y").toMessage();
263
+ }
@@ -0,0 +1,119 @@
1
+ // Type-check harness for docs/middleware.md. Every type-checkable snippet in the
2
+ // guide is exercised here against the real exports, so a renamed method or wrong
3
+ // argument type fails `npm run typecheck:docs`. Compile-only — never executed.
4
+ import {
5
+ HttpKernel,
6
+ Application,
7
+ Router,
8
+ UnauthorizedException,
9
+ type MiddlewareRef,
10
+ type RouteHandler,
11
+ } from "@shaferllc/keel/core";
12
+ import type { MiddlewareHandler } from "hono";
13
+
14
+ // Externals the snippets reference.
15
+ declare const router: Router;
16
+ declare const handler: RouteHandler;
17
+ declare function lookupUser(auth?: string): Promise<{ id: number; name: string }>;
18
+
19
+ // Example middleware — the shape everything else registers.
20
+ export const requestLogger: MiddlewareHandler = async (c, next) => {
21
+ const start = performance.now();
22
+ await next();
23
+ const ms = (performance.now() - start).toFixed(1);
24
+ console.log(` ${c.req.method} ${c.req.path} → ${c.res.status} (${ms}ms)`);
25
+ };
26
+
27
+ const cors: MiddlewareHandler = async (_c, next) => {
28
+ await next();
29
+ };
30
+ const requestId: MiddlewareHandler = async (_c, next) => {
31
+ await next();
32
+ };
33
+
34
+ // The HTTP kernel — global middleware + custom error handler.
35
+ export class Kernel extends HttpKernel {
36
+ constructor(app: Application) {
37
+ super(app);
38
+ this.use(requestLogger).use(cors).use(requestId);
39
+ this.onError((err, c) => c.json({ error: String(err) }, 500));
40
+ }
41
+ }
42
+
43
+ export function buildKernel(app: Application) {
44
+ const kernel = new Kernel(app);
45
+ return kernel.build();
46
+ }
47
+
48
+ // Short-circuiting.
49
+ export const requireApiKey: MiddlewareHandler = async (c, next) => {
50
+ if (c.req.header("x-api-key") !== process.env.API_KEY) {
51
+ return c.json({ error: "Unauthorized" }, 401);
52
+ }
53
+ await next();
54
+ };
55
+
56
+ class Auth {
57
+ check(): boolean {
58
+ return true;
59
+ }
60
+ }
61
+ export const requireAuth: MiddlewareHandler = async (c, next) => {
62
+ if (!c.get("app").make(Auth).check()) throw new UnauthorizedException();
63
+ await next();
64
+ };
65
+
66
+ // Sharing data with handlers.
67
+ export const withUser: MiddlewareHandler = async (c, next) => {
68
+ c.set("user", await lookupUser(c.req.header("authorization")));
69
+ await next();
70
+ };
71
+
72
+ export const noCache: MiddlewareHandler = async (c, next) => {
73
+ await next();
74
+ c.header("Cache-Control", "no-store");
75
+ };
76
+
77
+ // Parameterized middleware via a factory.
78
+ const role = (name: string): MiddlewareHandler => async (_c, next) => {
79
+ void name;
80
+ await next();
81
+ };
82
+
83
+ // Named middleware + applying refs to routes, groups, resources.
84
+ class DashboardController {}
85
+ class PostController {}
86
+ class AdminController {}
87
+ class ReportController {}
88
+ const authMiddleware = requireAuth;
89
+ const adminMiddleware = requireAuth;
90
+ const auditLog = requestLogger;
91
+
92
+ export function named() {
93
+ router.named({ auth: authMiddleware, admin: adminMiddleware });
94
+ router.named({ admin: role("admin"), editor: role("editor") });
95
+
96
+ router.get("/dashboard", [DashboardController, "index"]).use("auth");
97
+ router.group(() => {
98
+ /* … */
99
+ }).use(["auth", "admin"]);
100
+ router.resource("posts", PostController).use(["store", "update"], "auth");
101
+ router.resource("admin", AdminController).use("*", "admin");
102
+
103
+ router.get("/reports", [ReportController, "index"]).use(["auth", auditLog]);
104
+ router.get("/admin", handler).use(role("admin"));
105
+ router.get("/reports2", handler).middleware(["auth", auditLog]);
106
+
107
+ const mw = router.resolveMiddleware("auth");
108
+ return mw;
109
+ }
110
+
111
+ export function groupOrdering() {
112
+ router.group(() => {
113
+ router.get("/posts/:id/edit", handler).use("owns-post");
114
+ }).use("auth");
115
+ }
116
+
117
+ // Type seams.
118
+ const guards: MiddlewareRef[] = ["auth", auditLog];
119
+ export { guards };
@@ -0,0 +1,126 @@
1
+ // Type-check harness for docs/migrations.md. Every type-checkable snippet in the
2
+ // guide is exercised here against the real exports, so a renamed method or wrong
3
+ // argument type fails `npm run typecheck:docs`. Compile-only — never executed.
4
+ import {
5
+ Migrator,
6
+ SchemaBuilder,
7
+ TableBuilder,
8
+ Column,
9
+ type Migration,
10
+ type Connection,
11
+ type Dialect,
12
+ } from "@shaferllc/keel/core";
13
+
14
+ // A mock connection. `as Connection` is needed because `select` is declared
15
+ // generic (`select<T = Row>(…): Promise<T[]>`) — see docs/database.md.
16
+ const connection = {
17
+ select: async () => [],
18
+ write: async () => ({ rowsAffected: 1, insertId: 1 }),
19
+ } as Connection;
20
+
21
+ export const migrations: Migration[] = [
22
+ {
23
+ name: "01_create_users",
24
+ up: (schema) =>
25
+ schema.createTable("users", (t) => {
26
+ t.id();
27
+ t.string("email").unique();
28
+ t.string("name");
29
+ t.boolean("active").default(true);
30
+ t.timestamps();
31
+ }),
32
+ down: (schema) => schema.dropTable("users"),
33
+ },
34
+ ];
35
+
36
+ export async function columnTypes() {
37
+ await new SchemaBuilder(connection, "sqlite").createTable("posts", (t) => {
38
+ t.id();
39
+ t.integer("user_id");
40
+ t.string("slug", 120).unique();
41
+ t.text("body").nullable();
42
+ t.json("meta").nullable();
43
+ t.boolean("published").default(false);
44
+ t.timestamps();
45
+ });
46
+ }
47
+
48
+ export async function defaults(schema: SchemaBuilder) {
49
+ await schema.createTable("accounts", (t) => {
50
+ t.string("role").default("member");
51
+ t.boolean("active").default(true);
52
+ t.integer("retries").default(0);
53
+ });
54
+ }
55
+
56
+ export async function rawExamples(schema: SchemaBuilder) {
57
+ await schema.raw("CREATE INDEX idx_posts_user ON posts (user_id)");
58
+ await schema.raw("UPDATE users SET active = ? WHERE active IS NULL", [true]);
59
+ }
60
+
61
+ export async function runAndRollBack() {
62
+ const migrator = new Migrator(connection, "postgres");
63
+
64
+ const applied = await migrator.up(migrations);
65
+ const rolled = await migrator.down(migrations);
66
+ const names = await migrator.ran();
67
+
68
+ const pending = migrations.filter((m) => !names.includes(m.name));
69
+ return { applied, rolled, names, pending };
70
+ }
71
+
72
+ export async function migratorDefaults() {
73
+ const migrator = new Migrator(connection); // dialect defaults to "sqlite"
74
+ return migrator.ran();
75
+ }
76
+
77
+ // SchemaBuilder reference
78
+ export async function schemaBuilderRef(schema: SchemaBuilder) {
79
+ await schema.createTable("users", (t) => {
80
+ t.id();
81
+ t.string("email").unique();
82
+ t.timestamps();
83
+ });
84
+ await schema.dropTable("users");
85
+ await schema.raw("CREATE INDEX idx ON users (email)");
86
+ await schema.raw("UPDATE users SET active = ? WHERE id = ?", [true, 1]);
87
+ }
88
+
89
+ // TableBuilder reference
90
+ export function tableBuilderRef() {
91
+ const t = new TableBuilder();
92
+ t.id();
93
+ t.id("uuid");
94
+ t.string("email");
95
+ t.string("slug", 120);
96
+ t.text("body");
97
+ t.integer("user_id");
98
+ t.bigInteger("view_count");
99
+ t.boolean("active").default(true);
100
+ t.timestamp("published_at").nullable();
101
+ t.json("meta").nullable();
102
+ t.timestamps();
103
+ const cols: Column[] = t.columns;
104
+ const sql: string = t.toCreateSql("users", "postgres");
105
+ return { cols, sql };
106
+ }
107
+
108
+ // Column reference
109
+ export function columnRef(t: TableBuilder) {
110
+ t.text("bio").nullable();
111
+ t.string("email").unique();
112
+ t.boolean("active").default(true);
113
+ t.string("role").default("member");
114
+ const fragment: string = new Column("email", "string").unique().toSql("sqlite");
115
+ return fragment;
116
+ }
117
+
118
+ // Migration type seam
119
+ export const indexMigration: Migration = {
120
+ name: "03_add_index",
121
+ up: (s) => s.raw("CREATE INDEX idx_users_email ON users (email)"),
122
+ down: (s) => s.raw("DROP INDEX idx_users_email"),
123
+ };
124
+
125
+ const d: Dialect = "mysql";
126
+ export { d };