@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,134 @@
1
+ // Type-check harness for docs/console.md. Compile-only — never executed.
2
+ import {
3
+ defineCommand,
4
+ arg,
5
+ flag,
6
+ ConsoleKernel,
7
+ createUi,
8
+ createPrompt,
9
+ startRepl,
10
+ stripAnsi,
11
+ type Application,
12
+ type Ui,
13
+ type Prompt,
14
+ type CommandDefinition,
15
+ type ReplHelper,
16
+ } from "@shaferllc/keel/core";
17
+
18
+ export const greet = defineCommand({
19
+ name: "greet",
20
+ description: "Greet someone",
21
+
22
+ args: { name: arg.string({ description: "who to greet" }) },
23
+ flags: { loud: flag.boolean({ alias: "l", description: "SHOUT IT" }) },
24
+
25
+ async run({ args, flags, ui }) {
26
+ // The inference is the point: these annotations must hold.
27
+ const name: string = args.name;
28
+ const loud: boolean = flags.loud;
29
+
30
+ const message = `Hello, ${name}!`;
31
+ ui.success(loud ? message.toUpperCase() : message);
32
+ },
33
+ });
34
+
35
+ export const everySpec = defineCommand({
36
+ name: "specs",
37
+ args: {
38
+ required: arg.string(),
39
+ optional: arg.string({ required: false }),
40
+ defaulted: arg.string({ default: "x" }),
41
+ count: arg.number(),
42
+ rest: arg.spread({ required: false }),
43
+ },
44
+ flags: {
45
+ on: flag.boolean(),
46
+ name: flag.string({ alias: "n" }),
47
+ needed: flag.string({ required: true }),
48
+ times: flag.number({ default: 1 }),
49
+ tags: flag.array(),
50
+ },
51
+ allowUnknownFlags: true,
52
+
53
+ run({ args, flags, rest }) {
54
+ const a: string = args.required;
55
+ const b: string | undefined = args.optional;
56
+ const c: string = args.defaulted;
57
+ const d: number = args.count;
58
+ const e: string[] = args.rest;
59
+
60
+ const f: boolean = flags.on;
61
+ const g: string | undefined = flags.name;
62
+ const h: string = flags.needed;
63
+ const i: number = flags.times;
64
+ const j: string[] = flags.tags;
65
+
66
+ void [a, b, c, d, e, f, g, h, i, j, rest];
67
+ return 0; // the exit code
68
+ },
69
+ });
70
+
71
+ export function terminalUi(ui: Ui) {
72
+ ui.info("Checking…");
73
+ ui.success("Migrated 3 tables");
74
+ ui.warning("Nothing to do");
75
+ ui.error("Failed");
76
+ ui.debug("verbose detail");
77
+
78
+ ui.action("create", "app/Models/User.ts");
79
+ ui.action("skip", "app/Models/Post.ts", "skipped");
80
+
81
+ ui.table(["Name", "Rows"]).row(["users", "42"]).row(["orgs", "7"]).render();
82
+
83
+ ui.sticker(["http://localhost:3000"], "Server running");
84
+ ui.instructions(["cd my-app", "npm install", "keel serve"], "Next steps");
85
+
86
+ return ui.colors("green", "done");
87
+ }
88
+
89
+ export async function tasks(ui: Ui) {
90
+ return ui
91
+ .tasks()
92
+ .add("Install dependencies", async (task) => {
93
+ task.update("resolving…");
94
+ return "42 packages";
95
+ })
96
+ .add("Run migrations", async () => "3 tables")
97
+ .run();
98
+ }
99
+
100
+ export async function prompts(prompt: Prompt) {
101
+ const name = await prompt.ask("Project name?", { default: "my-app" });
102
+ const secret = await prompt.secure("API key?");
103
+ const ok = await prompt.confirm("Delete everything?");
104
+ const driver = await prompt.choice("Database?", ["sqlite", "postgres"]);
105
+ const features = await prompt.multiple("Features?", ["auth", "queue", "mail"]);
106
+
107
+ return { name, secret, ok, driver, features };
108
+ }
109
+
110
+ export async function testingACommand(setup: CommandDefinition) {
111
+ const ui = createUi({ raw: true });
112
+ const prompt = createPrompt({ trap: true });
113
+ const kernel = new ConsoleKernel({ ui, prompt }).register(setup);
114
+
115
+ prompt.trap("Project name?").replyWith("keel-app");
116
+ prompt.trap("Database?").chooseOption(1);
117
+ prompt.trap("Write the config?").accept();
118
+
119
+ prompt
120
+ .trap("Email?")
121
+ .assertFails("", "Email is required")
122
+ .assertPasses("ada@example.com")
123
+ .replyWith("ada@example.com");
124
+
125
+ const code = await kernel.run(["setup"]);
126
+
127
+ prompt.assertAllTrapsUsed();
128
+
129
+ return { code, logs: ui.logs, errors: ui.errors, plain: stripAnsi(ui.logs.join("\n")) };
130
+ }
131
+
132
+ export async function repl(app: Application, helpers: ReplHelper[]) {
133
+ await startRepl(app, { helpers, prompt: "keel > " });
134
+ }
@@ -0,0 +1,134 @@
1
+ // Type-check harness for docs/container.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
+ Container,
6
+ Application,
7
+ ServiceProvider,
8
+ bind,
9
+ singleton,
10
+ instance,
11
+ make,
12
+ bound,
13
+ alias,
14
+ swap,
15
+ restore,
16
+ type Ctx,
17
+ type Token,
18
+ type Constructor,
19
+ type Factory,
20
+ } from "@shaferllc/keel/core";
21
+
22
+ // Stand-ins for app-defined services referenced in the guide.
23
+ class Mailer {
24
+ constructor(_dep?: unknown) {}
25
+ }
26
+ class ReportService {
27
+ constructor(private app: Container) {}
28
+ }
29
+ class Router {
30
+ constructor(_app?: unknown) {}
31
+ }
32
+ declare const fakeMailer: Mailer;
33
+
34
+ declare const app: Container;
35
+
36
+ export class AppServiceProvider extends ServiceProvider {
37
+ register(): void {
38
+ this.app.bind("clock", () => new Date());
39
+ this.app.singleton(Mailer, (app) => new Mailer(app.make("config")));
40
+ this.app.instance("version", "0.1.0");
41
+ }
42
+ }
43
+
44
+ export function globalHelpers() {
45
+ bind("clock", () => new Date());
46
+ singleton(Mailer, (app) => new Mailer(app));
47
+ instance("version", "0.6.0");
48
+
49
+ const mailer = make(Mailer);
50
+ const version = make<string>("version");
51
+ if (bound("clock")) {
52
+ /* … */
53
+ }
54
+ return { mailer, version };
55
+ }
56
+
57
+ export function resolving() {
58
+ const mailer = make(Mailer);
59
+ const version = make<string>("version");
60
+
61
+ const report = app.make(ReportService); // no explicit binding
62
+
63
+ make("nope"); // throws at runtime; type-checks fine
64
+ const clock = bound("clock") ? make<Date>("clock") : new Date();
65
+ return { mailer, version, report, clock };
66
+ }
67
+
68
+ export class InvoiceController {
69
+ constructor(private app: Container) {}
70
+
71
+ index(c: Ctx) {
72
+ const config = this.app.make(Application).config();
73
+ return c.json({ currency: config.get("app.currency", "USD") });
74
+ }
75
+ }
76
+
77
+ export function containerReference() {
78
+ app.bind("clock", () => new Date());
79
+ app.bind(Mailer, (c) => new Mailer(c.make("config")));
80
+ app.singleton(Mailer, (c) => new Mailer(c.make("config")));
81
+ const version = app.instance("version", "0.30.0");
82
+
83
+ const mailer = app.make(Mailer);
84
+ const ver = app.make<string>("version");
85
+ const same = app.get(Mailer);
86
+ const report = app.build(ReportService);
87
+ const isBound = app.bound("clock");
88
+
89
+ return { version, mailer, ver, same, report, isBound };
90
+ }
91
+
92
+ export function helperReference() {
93
+ bind("clock", () => new Date());
94
+ singleton(Mailer, (app) => new Mailer(app));
95
+ const version = instance("version", "0.30.0");
96
+ const mailer = make(Mailer);
97
+ const ver = make<string>("version");
98
+ const isBound = bound("clock");
99
+ return { version, mailer, ver, isBound };
100
+ }
101
+
102
+ export function aliasesAndSwapping() {
103
+ // Guide: Aliases
104
+ alias("router", Router);
105
+ const viaAlias = make(Router);
106
+
107
+ // Guide: Swapping bindings in tests (global-helper forms)
108
+ swap(Mailer, () => fakeMailer);
109
+ make(Mailer);
110
+ restore(Mailer);
111
+ restore();
112
+
113
+ return { viaAlias };
114
+ }
115
+
116
+ export function aliasesAndSwappingContainer() {
117
+ app.singleton(Router, () => new Router(app));
118
+ app.alias("router", Router);
119
+ const same = app.make("router") === app.make(Router);
120
+
121
+ app.swap(Mailer, () => fakeMailer);
122
+ app.make(Mailer);
123
+ app.restore(Mailer);
124
+ app.restore();
125
+ return { same };
126
+ }
127
+
128
+ // Type seams.
129
+ const nameKey: Token<string> = "app.name";
130
+ const svcKey: Token<Mailer> = Mailer;
131
+ const ctor: Constructor<ReportService> = ReportService;
132
+ const mailerFactory: Factory<Mailer> = (app) => new Mailer(app.make("config"));
133
+
134
+ export { nameKey, svcKey, ctor, mailerFactory };
@@ -0,0 +1,86 @@
1
+ // Type-check harness for docs/controllers.md. Every type-checkable snippet in
2
+ // the guide is exercised here against the real exports, so a renamed method or a
3
+ // wrong handler shape fails `npm run typecheck:docs`. Compile-only — never run.
4
+ import {
5
+ json,
6
+ param,
7
+ Container,
8
+ type Ctx,
9
+ type RouteHandler,
10
+ } from "@shaferllc/keel/core";
11
+
12
+ // A stand-in service the DI example resolves.
13
+ class Mailer {
14
+ send() {}
15
+ }
16
+
17
+ // --- Basic controller: methods are actions, take Ctx or nothing ---
18
+ export class PostController {
19
+ index() {
20
+ return json({ posts: [] });
21
+ }
22
+
23
+ // Take the context explicitly…
24
+ show(c: Ctx) {
25
+ return c.json({ id: c.req.param("id") });
26
+ }
27
+
28
+ // …or lean on the ambient helpers and drop the argument.
29
+ edit() {
30
+ return json({ id: param("id") });
31
+ }
32
+ }
33
+
34
+ // --- Dependency injection: constructor receives the container ---
35
+ export class UserController {
36
+ constructor(private app: Container) {}
37
+
38
+ store() {
39
+ const mailer = this.app.make(Mailer);
40
+ return json({ sent: typeof mailer.send });
41
+ }
42
+
43
+ show(c: Ctx) {
44
+ const id = c.req.param("id");
45
+ const q = c.req.query("expand");
46
+ return c.json({ id, expand: q });
47
+ }
48
+ }
49
+
50
+ // --- Single-action controller: define handle() ---
51
+ export class PublishPost {
52
+ handle() {
53
+ return json({ published: true });
54
+ }
55
+ }
56
+
57
+ // --- Resource controller: the seven conventional actions ---
58
+ export class ResourcePostController {
59
+ index(c: Ctx) { return c.json({ action: "index" }); }
60
+ create(c: Ctx) { return c.json({ action: "create" }); }
61
+ store(c: Ctx) { return c.json({ action: "store" }); }
62
+ show(c: Ctx) { return c.json({ action: "show" }); }
63
+ edit(c: Ctx) { return c.json({ action: "edit" }); }
64
+ update(c: Ctx) { return c.json({ action: "update" }); }
65
+ destroy(c: Ctx) { return c.json({ action: "destroy" }); }
66
+ }
67
+
68
+ // --- Default export, for the lazy-loading snippet ---
69
+ export default class ReportController {
70
+ index(c: Ctx) {
71
+ return c.json({ action: "index" });
72
+ }
73
+ }
74
+
75
+ // --- RouteHandler: every arm of the union type-checks ---
76
+ export function handlerForms() {
77
+ const closure: RouteHandler = () => json({ ok: true });
78
+ const staticResp: RouteHandler = json({ status: "ok" });
79
+ const action: RouteHandler = [UserController, "show"];
80
+ const single: RouteHandler = [PublishPost];
81
+ const lazy: RouteHandler = [
82
+ () => Promise.resolve({ default: ReportController }),
83
+ "index",
84
+ ];
85
+ return { closure, staticResp, action, single, lazy };
86
+ }
@@ -0,0 +1,208 @@
1
+ // Type-check harness for docs/database.md. Every snippet in the reference is
2
+ // exercised here against the real exports, so a renamed method or wrong argument
3
+ // type fails `npm run typecheck:docs`. Compile-only — never executed.
4
+ import {
5
+ db,
6
+ setConnection,
7
+ type Connection,
8
+ type WriteResult,
9
+ type Row,
10
+ type Dialect,
11
+ type Operator,
12
+ } from "@shaferllc/keel/core";
13
+
14
+ declare const email: string;
15
+ declare const name: string;
16
+ declare const now: number;
17
+
18
+ export async function connect() {
19
+ const d1 = {} as {
20
+ prepare(sql: string): {
21
+ bind(...b: unknown[]): {
22
+ all(): Promise<{ results: Row[]; meta: { changes: number; last_row_id: number } }>;
23
+ run(): Promise<{ meta: { changes: number; last_row_id: number } }>;
24
+ };
25
+ };
26
+ };
27
+
28
+ const connection: Connection = {
29
+ select: (sql, bindings) => d1.prepare(sql).bind(...bindings).all().then((r) => r.results),
30
+ write: async (sql, bindings) => {
31
+ const r = await d1.prepare(sql).bind(...bindings).run();
32
+ return { rowsAffected: r.meta.changes, insertId: r.meta.last_row_id };
33
+ },
34
+ };
35
+ setConnection(connection, "sqlite");
36
+ setConnection(connection, "postgres");
37
+ }
38
+
39
+ export async function querying() {
40
+ await db("users").where("active", true).orderBy("name").get();
41
+ await db("users").where("id", 1).first();
42
+ await db("users").where("age", ">", 18).count();
43
+ await db("posts").whereIn("id", [1, 2, 3]).get();
44
+ await db("posts").whereNull("deleted_at").limit(20).offset(40).get();
45
+ await db("orders").select("id", "total").where("status", "paid").orWhere("status", "shipped").get();
46
+ }
47
+
48
+ export async function writing() {
49
+ const id = await db("users").insertGetId({ email, name });
50
+ await db("users").where("id", id).update({ name: "Grace" });
51
+ await db("users").where("id", id).delete();
52
+ }
53
+
54
+ export async function typedRows() {
55
+ type User = {
56
+ id: number;
57
+ email: string;
58
+ };
59
+ const user = await db<User>("users").where("id", 1).first();
60
+ const all = await db<User>("users").get();
61
+ return { user, all };
62
+ }
63
+
64
+ export async function reference() {
65
+ db("users");
66
+ db<{ id: number }>("users");
67
+
68
+ db("users").select("id", "email").get();
69
+ db("users").where("active", true);
70
+ db("users").where("age", ">", 18);
71
+ db("users").where("email", "like", "%@example.com");
72
+ db("orders").where("status", "paid").orWhere("status", "shipped").get();
73
+ db("posts").whereIn("id", [1, 2, 3]).get();
74
+ db("posts").whereNull("deleted_at").get();
75
+ db("users").whereNotNull("verified_at").get();
76
+ db("users").orderBy("last_name").orderBy("created_at", "desc").get();
77
+ db("posts").limit(20).offset(40).get();
78
+
79
+ const rows = await db("users").where("active", true).get();
80
+ const first = await db("users").where("email", email).first();
81
+ const active = await db("users").where("active", true).count();
82
+ const taken = await db("users").where("email", email).exists();
83
+
84
+ const result: WriteResult = await db("users").insert({ email, name });
85
+ const newId = await db("users").insertGetId({ email, name });
86
+ const upd: WriteResult = await db("users").where("id", 1).update({ name: "Grace" });
87
+ await db("sessions").where("expires_at", "<", now).delete();
88
+
89
+ return { rows, first, active, taken, result, newId, upd };
90
+ }
91
+
92
+ export async function ormExtras() {
93
+ await db("posts").whereBetween("views", [10, 100]).get();
94
+ await db("posts").whereNotIn("id", [4, 5]).get();
95
+ await db("posts").whereLike("title", "%keel%").get();
96
+ await db("posts").latest().get();
97
+ await db("posts").oldest("published_at").get();
98
+
99
+ const total: number = await db("orders").where("paid", true).sum("total");
100
+ const a: number = await db("orders").avg("total");
101
+ const mn: number = await db("orders").min("total");
102
+ const mx: number = await db("orders").max("total");
103
+ const email = await db("users").where("id", 1).value<string>("email");
104
+ const titles = await db("posts").pluck<string>("title");
105
+ const page = await db("posts").latest().paginate(2, 15);
106
+ const rows = page.data;
107
+ return { total, a, mn, mx, email, titles, page: page.currentPage, last: page.lastPage, rows };
108
+ }
109
+
110
+ // Interface / type seams
111
+ const mock: Connection = {
112
+ select: async () => [{ id: 1 }],
113
+ write: async (): Promise<WriteResult> => ({ rowsAffected: 1, insertId: 1 }),
114
+ };
115
+ const dialect: Dialect = "postgres";
116
+ const op: Operator = "like";
117
+ const row: Row = { id: 1 };
118
+ export { mock, dialect, op, row };
119
+
120
+ /* --- Transactions --- */
121
+
122
+ import {
123
+ transaction,
124
+ inTransaction,
125
+ type TransactionHandle,
126
+ type TransactionConnection,
127
+ } from "@shaferllc/keel/core";
128
+
129
+ declare const order: Record<string, unknown>;
130
+ declare const item: Record<string, unknown>;
131
+ declare const entry: Record<string, unknown>;
132
+ declare const id: number;
133
+
134
+ export async function committing() {
135
+ await transaction(async () => {
136
+ await db("orders").insert(order);
137
+ await db("stock").where("id", id).update({ count: 0 });
138
+ });
139
+ }
140
+
141
+ export async function explicitHandle() {
142
+ return transaction(async (tx: TransactionHandle) => {
143
+ await tx.table("orders").insert(order);
144
+ await tx.write("UPDATE stock SET count = count - 1 WHERE id = ?", [id]);
145
+ void tx.depth;
146
+ void tx.dialect;
147
+ return "done";
148
+ });
149
+ }
150
+
151
+ export async function abandoning() {
152
+ await transaction(async (tx) => {
153
+ await tx.table("orders").insert(order);
154
+ await tx.rollback();
155
+ });
156
+ }
157
+
158
+ export async function nesting() {
159
+ await transaction(async () => {
160
+ await db("orders").insert(order);
161
+
162
+ try {
163
+ await transaction(async () => {
164
+ await db("items").insert(item);
165
+ throw new Error("out of stock");
166
+ });
167
+ } catch {
168
+ // only the inner work was rolled back
169
+ }
170
+
171
+ await db("audit").insert(entry);
172
+ });
173
+ }
174
+
175
+ export function checking(): boolean {
176
+ return inTransaction();
177
+ }
178
+
179
+ export async function onANamedConnection() {
180
+ await transaction(async () => {
181
+ await db("events", "reporting").insert(entry);
182
+ }, "reporting");
183
+ }
184
+
185
+ /** A driver that pools must implement begin() — see the guide. */
186
+ export function pooledDriver(): Connection {
187
+ const conn: Connection = {
188
+ async select() {
189
+ return [];
190
+ },
191
+ async write() {
192
+ return { rowsAffected: 0 };
193
+ },
194
+ async begin(): Promise<TransactionConnection> {
195
+ return {
196
+ async select() {
197
+ return [];
198
+ },
199
+ async write() {
200
+ return { rowsAffected: 0 };
201
+ },
202
+ async commit() {},
203
+ async rollback() {},
204
+ };
205
+ },
206
+ };
207
+ return conn;
208
+ }
@@ -0,0 +1,41 @@
1
+ // Type-check harness for docs/debugging.md. Every type-checkable snippet in the
2
+ // reference is exercised here against the real exports, so a renamed function or
3
+ // wrong argument type fails `npm run typecheck:docs`. Compile-only — never run.
4
+ import { dump, dd } from "@shaferllc/keel/core";
5
+
6
+ declare const user: { id: number; name: string };
7
+ declare const order: { total: number };
8
+ declare const request: {
9
+ all(): Promise<Record<string, unknown>>;
10
+ headers(): Record<string, string>;
11
+ };
12
+ declare function computeTotal(): number;
13
+ declare function save(): Promise<{ id: number }>;
14
+
15
+ export function dumpBasics() {
16
+ dump(user, order); // logs both, returns `user`
17
+
18
+ const total = dump(computeTotal()); // logs the total AND uses it
19
+ return total;
20
+ }
21
+
22
+ export async function dumpInline() {
23
+ return dump(await save()); // inspect the saved value, still return it
24
+ }
25
+
26
+ export function dumpReference() {
27
+ const first = dump(user, order, request); // returns `user`
28
+ first.name; // typed as the first argument's type
29
+ return first;
30
+ }
31
+
32
+ export async function ddBasics() {
33
+ dd(await request.all(), request.headers());
34
+ // unreachable — dd() throws
35
+ }
36
+
37
+ export async function ddReference() {
38
+ // `dd` returns `never`, so TS treats everything after it as unreachable.
39
+ const value = await request.all();
40
+ dd(value);
41
+ }
@@ -0,0 +1,40 @@
1
+ // Type-check harness for docs/decorators.md. Compile-only — never executed.
2
+ import {
3
+ decorateRequest,
4
+ decorated,
5
+ setRequestValue,
6
+ hasRequestDecorator,
7
+ clearRequestDecorators,
8
+ type RequestResolver,
9
+ } from "@shaferllc/keel/core";
10
+
11
+ type User = { id: number; name: string };
12
+ declare function findUser(auth: string | undefined): Promise<User | null>;
13
+
14
+ export function registering() {
15
+ decorateRequest("locale", (c) => c.req.header("accept-language") ?? "en");
16
+ decorateRequest("user", async (c) => findUser(c.req.header("authorization")));
17
+ decorateRequest("tenant", (c) => c.req.header("x-tenant") ?? "public");
18
+ }
19
+
20
+ export async function accessing() {
21
+ const locale = await decorated<string>("locale");
22
+ const user = await decorated<User | null>("user");
23
+ const tenant = await decorated<string>("tenant");
24
+ return { locale, user, tenant };
25
+ }
26
+
27
+ export async function setting(theUser: User) {
28
+ setRequestValue("user", theUser);
29
+ return decorated<User>("user");
30
+ }
31
+
32
+ export function introspection() {
33
+ const has = hasRequestDecorator("user");
34
+ clearRequestDecorators();
35
+ return has;
36
+ }
37
+
38
+ // The resolver type
39
+ const resolver: RequestResolver<string> = (c) => c.req.path;
40
+ export { resolver };