@shaferllc/keel 0.66.0 → 0.68.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 (132) hide show
  1. package/AGENTS.md +167 -0
  2. package/README.md +30 -1
  3. package/bin/keel-mcp.mjs +9 -0
  4. package/dist/core/application.d.ts +5 -5
  5. package/dist/core/application.js +2 -2
  6. package/dist/core/cache.d.ts +82 -5
  7. package/dist/core/cache.js +181 -23
  8. package/dist/core/cli/stubs.d.ts +12 -0
  9. package/dist/core/cli/stubs.js +120 -0
  10. package/dist/core/events.d.ts +129 -5
  11. package/dist/core/events.js +165 -7
  12. package/dist/core/health.d.ts +141 -0
  13. package/dist/core/health.js +226 -0
  14. package/dist/core/helpers.d.ts +9 -3
  15. package/dist/core/helpers.js +11 -3
  16. package/dist/core/index.d.ts +15 -10
  17. package/dist/core/index.js +7 -4
  18. package/dist/core/lock.d.ts +139 -0
  19. package/dist/core/lock.js +215 -0
  20. package/dist/core/logger.d.ts +82 -4
  21. package/dist/core/logger.js +141 -23
  22. package/dist/core/mail.d.ts +128 -7
  23. package/dist/core/mail.js +264 -16
  24. package/dist/core/queue.d.ts +134 -9
  25. package/dist/core/queue.js +304 -14
  26. package/dist/core/storage.d.ts +159 -6
  27. package/dist/core/storage.js +287 -7
  28. package/dist/mcp/server.d.ts +19 -0
  29. package/dist/mcp/server.js +355 -0
  30. package/docs/ai-manifest.json +2472 -0
  31. package/docs/ai.md +128 -0
  32. package/docs/architecture.md +331 -0
  33. package/docs/authentication.md +453 -0
  34. package/docs/authorization.md +167 -0
  35. package/docs/broadcasting.md +137 -0
  36. package/docs/broker.md +500 -0
  37. package/docs/cache.md +558 -0
  38. package/docs/configuration.md +311 -0
  39. package/docs/console.md +356 -0
  40. package/docs/container.md +467 -0
  41. package/docs/controllers.md +265 -0
  42. package/docs/cors.md +51 -0
  43. package/docs/database.md +530 -0
  44. package/docs/debugging.md +129 -0
  45. package/docs/decorators.md +127 -0
  46. package/docs/errors.md +395 -0
  47. package/docs/events.md +496 -0
  48. package/docs/examples/architecture-app.ts +27 -0
  49. package/docs/examples/authentication.ts +61 -0
  50. package/docs/examples/authorization.ts +79 -0
  51. package/docs/examples/broadcasting.ts +60 -0
  52. package/docs/examples/broker-cache-validate.ts +34 -0
  53. package/docs/examples/broker-fault-tolerance.ts +29 -0
  54. package/docs/examples/broker-middleware.ts +27 -0
  55. package/docs/examples/broker.ts +203 -0
  56. package/docs/examples/cache.ts +222 -0
  57. package/docs/examples/configuration.ts +81 -0
  58. package/docs/examples/container.ts +134 -0
  59. package/docs/examples/controllers.ts +86 -0
  60. package/docs/examples/database.ts +118 -0
  61. package/docs/examples/debugging.ts +41 -0
  62. package/docs/examples/decorators.ts +40 -0
  63. package/docs/examples/errors.ts +121 -0
  64. package/docs/examples/events.ts +204 -0
  65. package/docs/examples/factories.ts +84 -0
  66. package/docs/examples/hashing.ts +71 -0
  67. package/docs/examples/health.ts +94 -0
  68. package/docs/examples/helpers.ts +171 -0
  69. package/docs/examples/hooks.ts +54 -0
  70. package/docs/examples/inertia.ts +81 -0
  71. package/docs/examples/locks.ts +120 -0
  72. package/docs/examples/logger.ts +92 -0
  73. package/docs/examples/mail.ts +160 -0
  74. package/docs/examples/middleware.ts +119 -0
  75. package/docs/examples/migrations.ts +126 -0
  76. package/docs/examples/models.ts +239 -0
  77. package/docs/examples/notification.ts +124 -0
  78. package/docs/examples/providers.ts +123 -0
  79. package/docs/examples/queues.ts +254 -0
  80. package/docs/examples/rate-limiting.ts +42 -0
  81. package/docs/examples/redis.ts +99 -0
  82. package/docs/examples/request-response.ts +197 -0
  83. package/docs/examples/routing.ts +186 -0
  84. package/docs/examples/scheduling.ts +62 -0
  85. package/docs/examples/sessions.ts +102 -0
  86. package/docs/examples/static-files.ts +63 -0
  87. package/docs/examples/storage.ts +132 -0
  88. package/docs/examples/templates.ts +58 -0
  89. package/docs/examples/testing.ts +66 -0
  90. package/docs/examples/transformer.ts +141 -0
  91. package/docs/examples/transformers.ts +49 -0
  92. package/docs/examples/url-builder.ts +86 -0
  93. package/docs/examples/validation.ts +102 -0
  94. package/docs/examples/views.tsx +62 -0
  95. package/docs/examples/vite.ts +106 -0
  96. package/docs/factories.md +166 -0
  97. package/docs/getting-started.md +290 -0
  98. package/docs/hashing.md +259 -0
  99. package/docs/health.md +225 -0
  100. package/docs/helpers.md +347 -0
  101. package/docs/hono.md +186 -0
  102. package/docs/hooks.md +118 -0
  103. package/docs/inertia.md +241 -0
  104. package/docs/locks.md +323 -0
  105. package/docs/logger.md +290 -0
  106. package/docs/mail.md +678 -0
  107. package/docs/middleware.md +425 -0
  108. package/docs/migrations.md +476 -0
  109. package/docs/models.md +810 -0
  110. package/docs/notifications.md +474 -0
  111. package/docs/providers.md +363 -0
  112. package/docs/queues.md +679 -0
  113. package/docs/rate-limiting.md +155 -0
  114. package/docs/redis.md +178 -0
  115. package/docs/request-response.md +953 -0
  116. package/docs/routing.md +804 -0
  117. package/docs/scheduling.md +110 -0
  118. package/docs/security.md +85 -0
  119. package/docs/sessions.md +354 -0
  120. package/docs/social-auth.md +174 -0
  121. package/docs/static-files.md +211 -0
  122. package/docs/storage.md +450 -0
  123. package/docs/templates.md +315 -0
  124. package/docs/testing.md +125 -0
  125. package/docs/transformers.md +381 -0
  126. package/docs/url-builder.md +295 -0
  127. package/docs/validation.md +288 -0
  128. package/docs/views.md +267 -0
  129. package/docs/vite.md +434 -0
  130. package/llms-full.txt +17694 -0
  131. package/llms.txt +116 -0
  132. package/package.json +26 -7
@@ -0,0 +1,254 @@
1
+ // Type-check harness for docs/queues.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
+ // The Cloudflare "custom driver" snippet uses Cloudflare's own Queue/env types,
5
+ // which aren't Keel exports, so it stays illustrative in the doc and is omitted
6
+ // here.
7
+ import {
8
+ Job,
9
+ Queue,
10
+ SyncDriver,
11
+ MemoryDriver,
12
+ dispatch,
13
+ work,
14
+ setQueue,
15
+ getQueue,
16
+ mail,
17
+ type Dispatchable,
18
+ type JobOptions,
19
+ type QueueDriver,
20
+ type Drainable,
21
+ type QueuedJob,
22
+ } from "@shaferllc/keel/core";
23
+
24
+ declare const user: { email: string };
25
+ declare function rebuildSearchIndex(): Promise<void>;
26
+ declare function doWork(): Promise<void>;
27
+ declare function registerUser(): Promise<void>;
28
+
29
+ export class SendWelcome extends Job {
30
+ constructor(private email: string) {
31
+ super();
32
+ }
33
+ async handle() {
34
+ await mail().to(this.email).subject("Welcome").text("Glad you're here").send();
35
+ }
36
+ }
37
+
38
+ export async function dispatching() {
39
+ await dispatch(new SendWelcome(user.email));
40
+ await dispatch(new SendWelcome(user.email), { delay: 60, queue: "emails" });
41
+ await dispatch(() => rebuildSearchIndex());
42
+ }
43
+
44
+ export function drivers() {
45
+ setQueue(new SyncDriver());
46
+ setQueue(new MemoryDriver());
47
+ }
48
+
49
+ export async function runningQueued() {
50
+ setQueue(new MemoryDriver());
51
+ await dispatch(new SendWelcome("a@x.com"));
52
+ await dispatch(new SendWelcome("b@x.com"));
53
+ const ran: number = await work();
54
+ return ran;
55
+ }
56
+
57
+ export async function inTests() {
58
+ const driver = new MemoryDriver();
59
+ setQueue(driver);
60
+ await registerUser();
61
+ const size: number = driver.size;
62
+ const first: boolean = driver.jobs[0]!.job instanceof SendWelcome;
63
+ await work();
64
+ return { size, first };
65
+ }
66
+
67
+ // --- API reference: top-level functions ---
68
+
69
+ export async function topLevel() {
70
+ await dispatch(new SendWelcome("a@x.com"));
71
+ await dispatch(() => rebuildSearchIndex(), { delay: 60, queue: "emails" });
72
+
73
+ const ran: number = await work();
74
+
75
+ setQueue(new MemoryDriver());
76
+ const q: Queue = setQueue(new SyncDriver());
77
+
78
+ const driver: QueueDriver = getQueue().driver;
79
+ return { ran, q, driver };
80
+ }
81
+
82
+ // --- API reference: Job ---
83
+
84
+ export class RebuildIndex extends Job {
85
+ async handle() {
86
+ await rebuildSearchIndex();
87
+ }
88
+ }
89
+
90
+ // --- API reference: Queue ---
91
+
92
+ export async function queueClass() {
93
+ const q = new Queue(new MemoryDriver());
94
+ await q.dispatch(new SendWelcome("a@x.com"));
95
+ await q.dispatch(new SendWelcome("a@x.com"), { queue: "emails" });
96
+ const ran: number = await q.work();
97
+ const driver: QueueDriver = q.driver;
98
+ return { ran, driver };
99
+ }
100
+
101
+ // --- API reference: drivers ---
102
+
103
+ export async function syncDriver() {
104
+ const d = new SyncDriver();
105
+ await d.push(() => doWork(), {});
106
+ }
107
+
108
+ export async function memoryDriver() {
109
+ const driver = new MemoryDriver();
110
+ setQueue(driver);
111
+ await dispatch(new SendWelcome("a@x.com"));
112
+ const size: number = driver.size;
113
+ const queued: Dispatchable = driver.jobs[0]!.job;
114
+ await driver.push(() => doWork(), { queue: "default" });
115
+ const ran: number = await driver.work();
116
+ return { size, queued, ran };
117
+ }
118
+
119
+ // --- API reference: interfaces & types ---
120
+
121
+ export const a: Dispatchable = new SendWelcome("a@x.com");
122
+ export const b: Dispatchable = () => rebuildSearchIndex();
123
+
124
+ export const opts: JobOptions = { delay: 60, queue: "emails" };
125
+
126
+ export const logDriver: QueueDriver = {
127
+ async push(job, options) {
128
+ void job;
129
+ console.log("queued", options.queue ?? "default");
130
+ },
131
+ };
132
+
133
+ export function registerLogDriver() {
134
+ setQueue(logDriver);
135
+ }
136
+
137
+ export class ArrayDriver implements QueueDriver, Drainable {
138
+ private q: Dispatchable[] = [];
139
+ get size() {
140
+ return this.q.length;
141
+ }
142
+ async push(job: Dispatchable) {
143
+ this.q.push(job);
144
+ }
145
+ async work() {
146
+ let n = 0;
147
+ for (const job of this.q.splice(0)) {
148
+ await (job instanceof Job ? job.handle() : job());
149
+ n++;
150
+ }
151
+ return n;
152
+ }
153
+ }
154
+
155
+ export function queuedJobEntry(driver: MemoryDriver) {
156
+ const entry: QueuedJob = driver.jobs[0]!;
157
+ const lane: string | undefined = entry.options.queue;
158
+ return lane;
159
+ }
160
+
161
+ /* --- Retries, backoff, failure, priority, context, faking --- */
162
+
163
+ import {
164
+ fakeQueue,
165
+ restoreQueue,
166
+ exponentialBackoff,
167
+ linearBackoff,
168
+ fixedBackoff,
169
+ noBackoff,
170
+ type Backoff,
171
+ type JobContext,
172
+ type FailedJob,
173
+ } from "@shaferllc/keel/core";
174
+
175
+ declare const stripe: { charge(amount: number): Promise<void> };
176
+ declare function notifyBilling(orderId: number, error: unknown): Promise<void>;
177
+ declare function log(message: string, context: Record<string, unknown>): void;
178
+
179
+ export class ChargeCard extends Job {
180
+ static override maxRetries = 5;
181
+ static override backoff = exponentialBackoff(1_000);
182
+ static override queue = "billing";
183
+ static override priority = -5;
184
+
185
+ constructor(
186
+ private amount: number,
187
+ private orderId: number,
188
+ ) {
189
+ super();
190
+ }
191
+
192
+ async handle(): Promise<void> {
193
+ await stripe.charge(this.amount);
194
+ }
195
+
196
+ override async failed(error: unknown): Promise<void> {
197
+ await notifyBilling(this.orderId, error);
198
+ }
199
+ }
200
+
201
+ export class ImportFile extends Job {
202
+ static override maxRetries = 3;
203
+
204
+ async handle(): Promise<void> {
205
+ const { jobId, attempt, queue }: JobContext = this.context!;
206
+ if (attempt > 1) log("retrying import", { jobId, attempt, queue });
207
+ }
208
+ }
209
+
210
+ export async function overridingPolicy() {
211
+ await dispatch(new ChargeCard(100, 1), { maxRetries: 1, backoff: noBackoff });
212
+ }
213
+
214
+ export function backoffStrategies(): Backoff[] {
215
+ return [
216
+ exponentialBackoff(1_000),
217
+ exponentialBackoff(1_000, 30_000),
218
+ linearBackoff(5_000),
219
+ fixedBackoff(2_000),
220
+ noBackoff,
221
+ ];
222
+ }
223
+
224
+ export async function priorities() {
225
+ await dispatch(new ChargeCard(100, 1), { priority: -10 });
226
+ await dispatch(() => {}, { priority: 10 });
227
+ }
228
+
229
+ export async function inspectFailures(): Promise<FailedJob[]> {
230
+ await work();
231
+ return getQueue().failed;
232
+ }
233
+
234
+ export async function faking() {
235
+ const queue = fakeQueue();
236
+
237
+ await dispatch(new ChargeCard(100, 7));
238
+
239
+ queue.assertPushed(ChargeCard);
240
+ queue.assertPushed(ChargeCard, (job) => job instanceof ChargeCard);
241
+ queue.assertPushedCount(1, ChargeCard);
242
+ queue.assertNotPushed(ImportFile);
243
+
244
+ const [entry] = queue.pushedJobs(ChargeCard);
245
+ void entry?.options.delay;
246
+
247
+ restoreQueue();
248
+ }
249
+
250
+ export function nothingPushed() {
251
+ const queue = fakeQueue();
252
+ queue.assertNothingPushed();
253
+ restoreQueue();
254
+ }
@@ -0,0 +1,42 @@
1
+ // Type-check harness for docs/rate-limiting.md. Every type-checkable snippet in
2
+ // the guide is exercised here against the real exports, so a renamed option or
3
+ // wrong argument type fails `npm run typecheck:docs`. Compile-only — never run.
4
+ import { rateLimiter, type RateLimiterOptions } from "@shaferllc/keel/core";
5
+ import type { Context, MiddlewareHandler } from "hono";
6
+
7
+ // Externals referenced by the narrative but not part of this module.
8
+ declare const app: { use(mw: MiddlewareHandler): void };
9
+
10
+ export function globalAndPerRoute() {
11
+ app.use(rateLimiter({ max: 60, window: 60 }));
12
+ app.use(rateLimiter({ max: 5, window: 60 }));
13
+ }
14
+
15
+ export function options() {
16
+ rateLimiter({
17
+ max: 60,
18
+ window: 60,
19
+ key: (c: Context) => c.req.header("x-api-key") ?? "anon",
20
+ message: "Slow down!",
21
+ });
22
+ }
23
+
24
+ export function defaults(): MiddlewareHandler {
25
+ // no arguments: 60 req / 60s, keyed by IP
26
+ return rateLimiter();
27
+ }
28
+
29
+ export function reused() {
30
+ const limit: MiddlewareHandler = rateLimiter({ max: 100, window: 60 });
31
+ return limit;
32
+ }
33
+
34
+ // Interface / type seam
35
+ const perUser: RateLimiterOptions = {
36
+ max: 30,
37
+ window: 60,
38
+ key: (c) => c.req.header("authorization") ?? "anon",
39
+ message: "Easy there — try again shortly.",
40
+ };
41
+ const mw: MiddlewareHandler = rateLimiter(perUser);
42
+ export { perUser, mw };
@@ -0,0 +1,99 @@
1
+ // Type-check harness for docs/redis.md. Compile-only — never executed.
2
+ import {
3
+ redis,
4
+ setRedis,
5
+ Redis,
6
+ MemoryRedis,
7
+ redisStore,
8
+ Cache,
9
+ type RedisConnection,
10
+ } from "@shaferllc/keel/core";
11
+
12
+ declare function computeStats(): Promise<unknown>;
13
+ declare function renderHome(): string;
14
+ declare function env(key: string): string;
15
+
16
+ export async function using() {
17
+ setRedis(new MemoryRedis());
18
+ await redis().set("views", "1");
19
+ await redis().incr("views");
20
+ await redis().get("views");
21
+ await redis().set("token", "abc", { ex: 60 });
22
+ await redis().del("token");
23
+ }
24
+
25
+ export async function commands() {
26
+ const r = redis();
27
+ const s = await r.get("k");
28
+ await r.set("k", "v", { ex: 60 });
29
+ const removed = await r.del("a", "b");
30
+ const present = await r.exists("a");
31
+ const has = await r.has("k");
32
+ await r.incr("k");
33
+ await r.decr("k");
34
+ await r.incrBy("k", 5);
35
+ await r.expire("k", 60);
36
+ const ttl = await r.ttl("k");
37
+ const keys = await r.keys("user:*");
38
+ await r.flushAll();
39
+ return { s, removed, present, has, ttl, keys };
40
+ }
41
+
42
+ export async function jsonAndRemember() {
43
+ await redis().setJson("user:1", { id: 1, name: "Ada" });
44
+ const user = await redis().getJson<{ id: number; name: string }>("user:1");
45
+ const stats = await redis().remember("stats", 300, () => computeStats());
46
+ return { user, stats };
47
+ }
48
+
49
+ export async function asCacheStore() {
50
+ const cache = new Cache(redisStore(redis()));
51
+ return cache.remember("home", 60, () => renderHome());
52
+ }
53
+
54
+ export function upstashDriver() {
55
+ const upstash = (url: string, token: string): RedisConnection => {
56
+ const call = (...args: (string | number)[]) =>
57
+ fetch(url, {
58
+ method: "POST",
59
+ headers: { Authorization: `Bearer ${token}` },
60
+ body: JSON.stringify(args),
61
+ }).then((r) => r.json() as Promise<{ result: unknown }>);
62
+ return {
63
+ async get(key) {
64
+ return ((await call("GET", key)).result as string) ?? null;
65
+ },
66
+ async set(key, value, o) {
67
+ await (o?.ex ? call("SET", key, value, "EX", o.ex) : call("SET", key, value));
68
+ },
69
+ async del(...keys) {
70
+ return (await call("DEL", ...keys)).result as number;
71
+ },
72
+ async exists(...keys) {
73
+ return (await call("EXISTS", ...keys)).result as number;
74
+ },
75
+ async incrBy(key, n) {
76
+ return (await call("INCRBY", key, n)).result as number;
77
+ },
78
+ async expire(key, s) {
79
+ return (await call("EXPIRE", key, s)).result === 1;
80
+ },
81
+ async ttl(key) {
82
+ return (await call("TTL", key)).result as number;
83
+ },
84
+ async keys(pattern) {
85
+ return (await call("KEYS", pattern)).result as string[];
86
+ },
87
+ async flushAll() {
88
+ await call("FLUSHALL");
89
+ },
90
+ };
91
+ };
92
+ setRedis(upstash(env("UPSTASH_URL"), env("UPSTASH_TOKEN")));
93
+ }
94
+
95
+ // Own client instance
96
+ export function ownClient() {
97
+ const r = new Redis(new MemoryRedis());
98
+ return r.connection;
99
+ }
@@ -0,0 +1,197 @@
1
+ // Type-check harness for docs/request-response.md. Every type-checkable snippet
2
+ // in the reference is exercised here against the real exports, so a renamed
3
+ // accessor or wrong argument type fails `npm run typecheck:docs`. Compile-only —
4
+ // never executed. These accessors read an ambient request context, so the calls
5
+ // only need to type-check, not run.
6
+ import {
7
+ ctx,
8
+ json,
9
+ text,
10
+ html,
11
+ redirect,
12
+ request,
13
+ response,
14
+ param,
15
+ query,
16
+ header,
17
+ body,
18
+ } from "@shaferllc/keel/core";
19
+
20
+ declare const created: unknown;
21
+ declare const rows: unknown;
22
+ declare const csv: string;
23
+ declare const token: string;
24
+ declare const data: unknown;
25
+ declare const user: { isAdmin: boolean } | undefined;
26
+ declare function next(): Promise<void>;
27
+ declare function log(path: string): void;
28
+ declare function highlightNav(): void;
29
+ declare function store(bytes: ArrayBuffer): Promise<void>;
30
+
31
+ export async function readingInput() {
32
+ request.param("id");
33
+ request.query("q");
34
+ request.header("authorization");
35
+
36
+ await request.all();
37
+ await request.input("email");
38
+ const page = await request.input("page", 1); // number
39
+ await request.only(["email", "name"]);
40
+ await request.except(["password"]);
41
+
42
+ const body1 = await request.json<{ email: string }>();
43
+
44
+ // Raw body accessors for other content types.
45
+ const asText: string = await request.text();
46
+ const asBytes: ArrayBuffer = await request.arrayBuffer();
47
+ const asBlob: Blob = await request.blob();
48
+
49
+ return { page, body1, asText, asBytes, asBlob };
50
+ }
51
+
52
+ export function requestMeta() {
53
+ const method: string = request.method;
54
+ const path: string = request.path;
55
+ const url: string = request.url;
56
+ const status: number = request.status;
57
+ const raw: Request = request.raw;
58
+ const hasBody: boolean = request.hasBody();
59
+ const headers: Record<string, string> = request.headers();
60
+ const ip: string | undefined = request.ip();
61
+ const ips: string[] = request.ips();
62
+ return { method, path, url, status, raw, hasBody, headers, ip, ips };
63
+ }
64
+
65
+ export function requestUrl() {
66
+ const protocol: string = request.protocol;
67
+ const secure: boolean = request.secure;
68
+ const host: string = request.host;
69
+ const hostname: string = request.hostname;
70
+ const origin: string = request.origin;
71
+ const fullUrl: string = request.fullUrl;
72
+ const querystring: string = request.querystring;
73
+ return { protocol, secure, host, hostname, origin, fullUrl, querystring };
74
+ }
75
+
76
+ export async function middleware() {
77
+ await next();
78
+ if (request.status >= 500) log(request.path);
79
+ }
80
+
81
+ export function routeInfo() {
82
+ request.route;
83
+ const named: boolean = request.routeIs("users.show");
84
+ const tenant: string | undefined = request.subdomain("tenant");
85
+ if (request.routeIs("users.show")) highlightNav();
86
+ return { named, tenant };
87
+ }
88
+
89
+ export function cookiesRead() {
90
+ request.cookie("session");
91
+ request.cookie();
92
+ }
93
+
94
+ export async function fileUploads() {
95
+ const avatar = await request.file("avatar"); // File | undefined
96
+ const docs = await request.files("docs"); // File[]
97
+ const all = await request.allFiles(); // { field: File | File[] }
98
+
99
+ if (avatar) {
100
+ avatar.name;
101
+ avatar.size;
102
+ avatar.type;
103
+ const bytes = await avatar.arrayBuffer();
104
+ await store(bytes);
105
+ }
106
+ return { docs, all };
107
+ }
108
+
109
+ export function negotiation() {
110
+ const best: string | null = request.accepts([
111
+ "application/json",
112
+ "text/html",
113
+ ]);
114
+ const types: string[] = request.types();
115
+ const lang: string | null = request.language(["en", "fr"]);
116
+ const langs: string[] = request.languages();
117
+ const enc: string | null = request.encoding(["br", "gzip"]);
118
+ const encs: string[] = request.encodings();
119
+ const charset: string | null = request.charset(["utf-8"]);
120
+ const charsets: string[] = request.charsets();
121
+ return { best, types, lang, langs, enc, encs, charset, charsets };
122
+ }
123
+
124
+ export function negotiateSwitch(): Response {
125
+ switch (request.accepts(["application/json", "text/html"])) {
126
+ case "application/json":
127
+ return json(created);
128
+ case "text/html":
129
+ return html("<p>ok</p>");
130
+ default:
131
+ return response.abort("Not acceptable", 406);
132
+ }
133
+ }
134
+
135
+ export function responseBuilders() {
136
+ ctx().req.raw;
137
+
138
+ json({ ok: true });
139
+ json({ error: "nope" }, 422);
140
+ text("pong");
141
+ text("rate limited", 429);
142
+ html("<h1>Hi</h1>");
143
+ redirect("/login");
144
+ redirect("/", 301);
145
+ }
146
+
147
+ export function writingOutput() {
148
+ response.json({ ok: true });
149
+ response.text("hello");
150
+ response.html("<h1>Hi</h1>");
151
+ response.redirect("/login");
152
+ response.send(data);
153
+
154
+ response.status(201).json(created);
155
+ response.header("x-total", "42").json(rows);
156
+ response.headers({ "x-total": "42", "cache-control": "no-store" });
157
+ const ct: string | null = response.getHeader("content-type");
158
+ const has: boolean = response.hasHeader("cache-control");
159
+ void ct;
160
+ void has;
161
+ response.type("text/csv").append("vary", "accept");
162
+ response.removeHeader("x-powered-by");
163
+ response.cookie("flash", "saved").redirect("/");
164
+
165
+ response.cookie("session", token, { httpOnly: true, maxAge: 3600 });
166
+ response.clearCookie("session");
167
+
168
+ response.status(202).json({ queued: true });
169
+ response.type("text/csv").send(csv);
170
+ response.attachment("report.csv").type("text/csv").send(csv);
171
+ response.attachment().text("x");
172
+ response.back();
173
+ response.back("/dashboard");
174
+ redirect("back");
175
+ response.append("vary", "accept").append("vary", "accept-language");
176
+ response.send({ ok: true });
177
+ response.send("pong");
178
+ }
179
+
180
+ export function guards() {
181
+ response.abort("Not found", 404);
182
+ }
183
+
184
+ export function conditionalGuards() {
185
+ response.abortIf(!user, "Not found", 404);
186
+ response.abortUnless(user?.isAdmin, "Forbidden", 403);
187
+ }
188
+
189
+ export async function standaloneShortcuts() {
190
+ const id: string = param("id");
191
+ const allParams: Record<string, string> = param();
192
+ const q: string | undefined = query("q");
193
+ const allQuery: Record<string, string> = query();
194
+ const auth: string | undefined = header("authorization");
195
+ const payload = await body<{ email: string }>();
196
+ return { id, allParams, q, allQuery, auth, payload };
197
+ }