@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,467 @@
1
+ # The Service Container
2
+
3
+ The container is the backbone of Keel. Every service — config, the router,
4
+ controllers, and anything you write — is registered in it and resolved out of
5
+ it. It is the single registry every service resolves out of.
6
+
7
+ ## Why a container?
8
+
9
+ Instead of `import`ing concrete classes everywhere and `new`ing them by hand,
10
+ you _bind_ how a service is built once, then _resolve_ it wherever you need it.
11
+ That gives you a single place to swap implementations (real vs. fake in tests),
12
+ share singletons, and inject dependencies.
13
+
14
+ ## Binding
15
+
16
+ Bindings live in a service provider's `register()` method (see
17
+ [Providers](./providers.md)). A binding is keyed by a **token** — a string,
18
+ symbol, or class — and a **factory** that receives the container.
19
+
20
+ ```ts
21
+ import { ServiceProvider } from "@shaferllc/keel/core";
22
+
23
+ export class AppServiceProvider extends ServiceProvider {
24
+ register(): void {
25
+ // Transient: a fresh value every time it is resolved.
26
+ this.app.bind("clock", () => new Date());
27
+
28
+ // Singleton: built once, then cached.
29
+ this.app.singleton(Mailer, (app) => new Mailer(app.make("config")));
30
+
31
+ // Instance: register an already-constructed value.
32
+ this.app.instance("version", "0.1.0");
33
+ }
34
+ }
35
+ ```
36
+
37
+ ## The short way: global helpers
38
+
39
+ You don't need `this.app` at all. The same operations exist as global helpers
40
+ that resolve against the active application — bind and resolve from anywhere:
41
+
42
+ ```ts
43
+ import { bind, singleton, instance, make, bound } from "@shaferllc/keel/core";
44
+
45
+ bind("clock", () => new Date()); // transient
46
+ singleton(Mailer, (app) => new Mailer(app)); // shared
47
+ instance("version", "0.6.0"); // pre-built value
48
+
49
+ const mailer = make(Mailer);
50
+ const version = make<string>("version");
51
+ if (bound("clock")) { /* … */ }
52
+ ```
53
+
54
+ Both styles work everywhere; the helpers are just less to type.
55
+
56
+ ## Aliases
57
+
58
+ Point a second token at an existing binding with `alias(name, target)`. The
59
+ target owns the binding (and its singleton, if any); the alias just resolves
60
+ through to it:
61
+
62
+ ```ts
63
+ import { alias, make } from "@shaferllc/keel/core";
64
+
65
+ alias("router", Router); // make("router") === make(Router)
66
+ ```
67
+
68
+ ## Swapping bindings in tests
69
+
70
+ `swap(token, factory)` temporarily replaces a binding with a fake, and
71
+ `restore(token)` puts the original back — the container remembers the real one
72
+ (binding *and* any resolved instance) so your test doesn't have to. `restore()`
73
+ with no argument undoes every swap, which makes a tidy `afterEach`:
74
+
75
+ ```ts
76
+ import { swap, restore } from "@shaferllc/keel/core";
77
+
78
+ swap(Mailer, () => fakeMailer); // resolved once, shared for the test
79
+ // … exercise code that make()s Mailer …
80
+ restore(Mailer); // or restore() to undo all swaps
81
+ ```
82
+
83
+ This is Keel's answer to mocking a service without reaching into module
84
+ internals: the code under test resolves `Mailer` the same way it always does,
85
+ and gets the fake. (For request-scoped fakes, prefer plain
86
+ [dependency injection](#dependency-injection-in-controllers) — pass the collaborator
87
+ in — so there's no global to restore.)
88
+
89
+ ## Resolving
90
+
91
+ Use `make()` (or `this.app.make()`) to pull something out:
92
+
93
+ ```ts
94
+ const mailer = make(Mailer);
95
+ const version = make<string>("version");
96
+ ```
97
+
98
+ If a token is bound, its factory runs (once, for singletons). If you pass an
99
+ **unbound class**, the container auto-constructs it, passing itself to the
100
+ constructor:
101
+
102
+ ```ts
103
+ class ReportService {
104
+ constructor(private app: Container) {}
105
+ }
106
+
107
+ const report = app.make(ReportService); // works with no explicit binding
108
+ ```
109
+
110
+ Auto-resolution is transient: an unbound class is rebuilt every `make()` — it is
111
+ never cached, because nothing marked it shared. Bind it with `singleton` if you
112
+ want one instance.
113
+
114
+ ### When nothing is bound
115
+
116
+ `make()` only auto-constructs **class** tokens. A string or symbol that was never
117
+ bound has nothing to build, so it throws:
118
+
119
+ ```ts
120
+ make("nope"); // Error: Nothing bound in the container for [nope].
121
+ ```
122
+
123
+ Guard with `bound()` when a token might be missing:
124
+
125
+ ```ts
126
+ const clock = bound("clock") ? make<Date>("clock") : new Date();
127
+ ```
128
+
129
+ ## Dependency injection in controllers
130
+
131
+ Controllers are resolved through the container, so their constructor receives
132
+ it. Pull whatever you need:
133
+
134
+ ```ts
135
+ import type { Ctx } from "@shaferllc/keel/core";
136
+ import { Application, type Container } from "@shaferllc/keel/core";
137
+
138
+ export class InvoiceController {
139
+ constructor(private app: Container) {}
140
+
141
+ index(c: Ctx) {
142
+ const config = this.app.make(Application).config();
143
+ return c.json({ currency: config.get("app.currency", "USD") });
144
+ }
145
+ }
146
+ ```
147
+
148
+ ## The API
149
+
150
+ | Method | Purpose |
151
+ |--------|---------|
152
+ | `bind(token, factory)` | Transient binding — fresh value each resolve |
153
+ | `singleton(token, factory)` | Shared binding — resolved once, then cached |
154
+ | `instance(token, value)` | Register a pre-built value as a shared instance |
155
+ | `make(token)` / `get(token)` | Resolve a token |
156
+ | `bound(token)` | Whether a token is bound or cached |
157
+ | `build(ctor)` | Instantiate a class, passing it the container |
158
+
159
+ ## Tokens
160
+
161
+ - **Strings/symbols** — good for values and interfaces: `"config"`, `"clock"`.
162
+ - **Classes** — good for services; the class doubles as its own token and can be
163
+ auto-resolved when unbound.
164
+
165
+ ## Under the hood
166
+
167
+ The whole container is about 90 lines in
168
+ [`src/core/container.ts`](../src/core/container.ts). Two maps — one for bindings,
169
+ one for cached instances — and a `make()` that checks the cache, runs the
170
+ factory, and caches shared results. Read it; there's no magic.
171
+
172
+ ## Related
173
+
174
+ Bindings usually live in a [service provider](./providers.md)'s `register()`
175
+ method; the global helpers resolve against the active [`Application`](./application.md),
176
+ which registers itself on construction.
177
+
178
+ ---
179
+
180
+ ## API reference
181
+
182
+ ### Container
183
+
184
+ The registry itself. You rarely construct it — the `Application` is a `Container`,
185
+ and you reach it as `this.app` inside providers/controllers or via the global
186
+ helpers below. Registration methods (`bind`, `singleton`) return `this` and so
187
+ chain; resolution methods return the value.
188
+
189
+ #### `bind(token, factory)`
190
+
191
+ `bind<T>(token: Token<T>, factory: Factory<T>): this`
192
+
193
+ Registers a transient binding — the factory runs on every resolve, yielding a
194
+ fresh value each time.
195
+
196
+ ```ts
197
+ app.bind("clock", () => new Date());
198
+ app.bind(Mailer, (c) => new Mailer(c.make("config")));
199
+ ```
200
+
201
+ **Notes:** returns `this`, so calls chain. Re-binding the same token overwrites
202
+ the prior binding. The factory receives the container, so it can resolve its own
203
+ dependencies.
204
+
205
+ #### `singleton(token, factory)`
206
+
207
+ `singleton<T>(token: Token<T>, factory: Factory<T>): this`
208
+
209
+ Registers a shared binding — the factory runs at most once; the result is cached
210
+ and returned on every later resolve.
211
+
212
+ ```ts
213
+ app.singleton(Mailer, (c) => new Mailer(c.make("config")));
214
+ ```
215
+
216
+ **Notes:** returns `this`. The factory is lazy — it doesn't run until the first
217
+ `make()`. The cached value lives in the instance map, so a later `bound()` is
218
+ `true` even before first resolve (the binding is registered immediately).
219
+
220
+ #### `instance(token, value)`
221
+
222
+ `instance<T>(token: Token<T>, value: T): T`
223
+
224
+ Registers an already-constructed value as a shared instance, skipping any factory.
225
+
226
+ ```ts
227
+ const version = app.instance("version", "0.30.0"); // returns "0.30.0"
228
+ ```
229
+
230
+ **Notes:** returns the value you passed (not `this`), so it reads well inline.
231
+ Overrides any cached instance for the token. Because it writes the instance map,
232
+ `make()` returns it directly without ever consulting a binding.
233
+
234
+ #### `make(token)`
235
+
236
+ `make<T>(token: Token<T>): T`
237
+
238
+ Resolves a token: returns a cached instance if present, else runs its binding
239
+ (caching the result for singletons), else auto-constructs an unbound class.
240
+
241
+ ```ts
242
+ const mailer = app.make(Mailer);
243
+ const version = app.make<string>("version");
244
+ ```
245
+
246
+ **Notes:** resolution order is instance cache → binding → class auto-build. An
247
+ unbound **class** token is built via `build()` and **not** cached. An unbound
248
+ string/symbol token throws `Nothing bound in the container for [token].`
249
+
250
+ #### `get(token)`
251
+
252
+ `get<T>(token: Token<T>): T`
253
+
254
+ Alias for `make()` — identical behavior, sugar for an `app(token)`-style read.
255
+
256
+ ```ts
257
+ const mailer = app.get(Mailer);
258
+ ```
259
+
260
+ **Notes:** delegates straight to `make()`; use whichever name reads better.
261
+
262
+ #### `build(ctor)`
263
+
264
+ `build<T>(ctor: Constructor<T>): T`
265
+
266
+ Instantiates a class directly, passing the container to its constructor. Bypasses
267
+ bindings and the instance cache entirely.
268
+
269
+ ```ts
270
+ const report = app.build(ReportService); // new ReportService(app)
271
+ ```
272
+
273
+ **Notes:** always constructs a new instance (never cached), and ignores any
274
+ binding registered for the class. `make()` calls this under the hood when it
275
+ auto-resolves an unbound class token.
276
+
277
+ #### `bound(token)`
278
+
279
+ `bound(token: Token): boolean`
280
+
281
+ Reports whether the token has a binding **or** a cached instance.
282
+
283
+ ```ts
284
+ if (app.bound("clock")) app.make("clock");
285
+ ```
286
+
287
+ **Notes:** `true` for singletons even before first resolve (the binding exists).
288
+
289
+ #### `alias(alias, target)`
290
+
291
+ `alias<T>(alias: Token<T>, target: Token<T>): this`
292
+
293
+ Registers a token that resolves through to another token.
294
+
295
+ ```ts
296
+ app.singleton(Router, () => new Router(app));
297
+ app.alias("router", Router);
298
+ app.make("router") === app.make(Router); // true — same singleton
299
+ ```
300
+
301
+ **Notes:** returns `this` (chainable). The alias is a thin transient wrapper that
302
+ calls `make(target)`, so the target keeps ownership of its own sharing — aliasing
303
+ a singleton still yields the one shared instance.
304
+
305
+ #### `swap(token, factory)`
306
+
307
+ `swap<T>(token: Token<T>, factory: Factory<T>): this`
308
+
309
+ Temporarily replaces a binding with a fake, for tests. The replacement is shared
310
+ (resolved once), and the original binding **and** any resolved instance are
311
+ remembered for `restore()`.
312
+
313
+ ```ts
314
+ app.swap(Mailer, () => fakeMailer);
315
+ app.make(Mailer); // fakeMailer
316
+ ```
317
+
318
+ **Notes:** returns `this`. Idempotent per token — the first `swap` saves the
319
+ original, later swaps just change the fake. Clears the cached instance so the
320
+ next `make()` goes through the fake.
321
+
322
+ #### `restore(token?)`
323
+
324
+ `restore(token?: Token): this`
325
+
326
+ Undoes a `swap()`, restoring the original binding (and instance). Called with no
327
+ token, it restores **every** swap — handy in an `afterEach`.
328
+
329
+ ```ts
330
+ app.restore(Mailer); // one token
331
+ app.restore(); // all swaps
332
+ ```
333
+
334
+ **Notes:** returns `this`. A no-op for a token that wasn't swapped. If the token
335
+ had no binding before the swap, `restore` removes it, leaving `bound()` false
336
+ again.
337
+ Does not consider auto-resolvable classes — an unbound class is `bound() === false`
338
+ yet still `make()`-able.
339
+
340
+ ### Global helpers
341
+
342
+ Free functions in [`src/core/helpers.ts`](../src/core/helpers.ts) that proxy to
343
+ the active application's container, so you can bind and resolve from anywhere
344
+ without threading `this.app` through. Each calls `app()` internally, which throws
345
+ if no `Application` has been bootstrapped.
346
+
347
+ #### `bind(token, factory)`
348
+
349
+ `bind<T>(token: Token<T>, factory: Factory<T>): void`
350
+
351
+ Transient binding on the active application.
352
+
353
+ ```ts
354
+ import { bind } from "@shaferllc/keel/core";
355
+ bind("clock", () => new Date());
356
+ ```
357
+
358
+ **Notes:** returns `void` — unlike `Container.bind`, it does **not** return the
359
+ container, so these helpers don't chain.
360
+
361
+ #### `singleton(token, factory)`
362
+
363
+ `singleton<T>(token: Token<T>, factory: Factory<T>): void`
364
+
365
+ Shared binding on the active application.
366
+
367
+ ```ts
368
+ import { singleton } from "@shaferllc/keel/core";
369
+ singleton(Mailer, (app) => new Mailer(app));
370
+ ```
371
+
372
+ **Notes:** returns `void`. Same lazy, resolve-once semantics as `Container.singleton`.
373
+
374
+ #### `instance(token, value)`
375
+
376
+ `instance<T>(token: Token<T>, value: T): T`
377
+
378
+ Registers a pre-built value on the active application; returns the value.
379
+
380
+ ```ts
381
+ import { instance } from "@shaferllc/keel/core";
382
+ const version = instance("version", "0.30.0");
383
+ ```
384
+
385
+ **Notes:** the one container helper that returns its value (mirrors
386
+ `Container.instance`), so it composes inline.
387
+
388
+ #### `make(token)`
389
+
390
+ `make<T>(token: Token<T>): T`
391
+
392
+ Resolves a token out of the active application's container.
393
+
394
+ ```ts
395
+ import { make } from "@shaferllc/keel/core";
396
+ const mailer = make(Mailer);
397
+ const version = make<string>("version");
398
+ ```
399
+
400
+ **Notes:** same resolution rules and throw-on-missing behavior as `Container.make`.
401
+
402
+ #### `bound(token)`
403
+
404
+ `bound(token: Token): boolean`
405
+
406
+ Whether the token is bound or cached on the active application.
407
+
408
+ ```ts
409
+ import { bound } from "@shaferllc/keel/core";
410
+ if (bound("clock")) { /* … */ }
411
+ ```
412
+
413
+ **Notes:** proxies `Container.bound`.
414
+
415
+ #### `alias(alias, target)` · `swap(token, factory)` · `restore(token?)`
416
+
417
+ Global-helper forms of the container methods, resolving against the active
418
+ application.
419
+
420
+ ```ts
421
+ import { alias, swap, restore } from "@shaferllc/keel/core";
422
+
423
+ alias("router", Router);
424
+ swap(Mailer, () => fakeMailer);
425
+ restore(); // undo every swap
426
+ ```
427
+
428
+ **Notes:** `alias`/`swap` proxy `Container.alias`/`Container.swap`; `restore`
429
+ proxies `Container.restore` (no token restores all). `swap`/`restore` are for
430
+ tests — see [Swapping bindings in tests](#swapping-bindings-in-tests).
431
+
432
+ ### Interfaces & types
433
+
434
+ #### `Token`
435
+
436
+ `type Token<T = unknown> = string | symbol | Constructor<T>`
437
+
438
+ What every binding is keyed by. Use a string/symbol for values and interfaces, or
439
+ a class — which doubles as its own token and can be auto-resolved when unbound.
440
+
441
+ ```ts
442
+ const nameKey: Token<string> = "app.name";
443
+ const svcKey: Token<Mailer> = Mailer; // the class is the token
444
+ ```
445
+
446
+ #### `Constructor`
447
+
448
+ `type Constructor<T = unknown> = new (...args: any[]) => T`
449
+
450
+ Any newable class. A class token is a `Constructor`; `build()` and auto-resolution
451
+ call `new ctor(container)` on it, so the constructor's first parameter receives the
452
+ container.
453
+
454
+ ```ts
455
+ const ctor: Constructor<ReportService> = ReportService;
456
+ ```
457
+
458
+ #### `Factory`
459
+
460
+ `type Factory<T> = (app: Container) => T`
461
+
462
+ The builder function you hand to `bind`/`singleton`. It receives the container, so
463
+ it can resolve dependencies while constructing the value.
464
+
465
+ ```ts
466
+ const mailerFactory: Factory<Mailer> = (app) => new Mailer(app.make("config"));
467
+ ```