@shaferllc/keel 0.59.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 (166) 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 +14 -2
  6. package/dist/core/auth.d.ts +47 -0
  7. package/dist/core/auth.js +77 -0
  8. package/dist/core/authorization.d.ts +9 -1
  9. package/dist/core/authorization.js +22 -2
  10. package/dist/core/cache.d.ts +82 -5
  11. package/dist/core/cache.js +181 -23
  12. package/dist/core/cli/stubs.d.ts +12 -0
  13. package/dist/core/cli/stubs.js +120 -0
  14. package/dist/core/container.d.ts +20 -0
  15. package/dist/core/container.js +52 -0
  16. package/dist/core/cors.d.ts +29 -0
  17. package/dist/core/cors.js +72 -0
  18. package/dist/core/crypto.d.ts +40 -4
  19. package/dist/core/crypto.js +66 -6
  20. package/dist/core/csrf.d.ts +25 -0
  21. package/dist/core/csrf.js +78 -0
  22. package/dist/core/database.d.ts +49 -4
  23. package/dist/core/database.js +89 -21
  24. package/dist/core/events.d.ts +129 -5
  25. package/dist/core/events.js +165 -7
  26. package/dist/core/health.d.ts +141 -0
  27. package/dist/core/health.js +226 -0
  28. package/dist/core/helpers.d.ts +15 -3
  29. package/dist/core/helpers.js +23 -3
  30. package/dist/core/index.d.ts +33 -18
  31. package/dist/core/index.js +16 -8
  32. package/dist/core/lock.d.ts +139 -0
  33. package/dist/core/lock.js +215 -0
  34. package/dist/core/logger.d.ts +82 -4
  35. package/dist/core/logger.js +141 -23
  36. package/dist/core/mail.d.ts +128 -7
  37. package/dist/core/mail.js +264 -16
  38. package/dist/core/model.d.ts +2 -0
  39. package/dist/core/model.js +16 -14
  40. package/dist/core/provider.d.ts +7 -0
  41. package/dist/core/provider.js +7 -0
  42. package/dist/core/queue.d.ts +134 -9
  43. package/dist/core/queue.js +304 -14
  44. package/dist/core/rate-limit.js +3 -0
  45. package/dist/core/relations.js +13 -13
  46. package/dist/core/request.d.ts +26 -0
  47. package/dist/core/request.js +77 -0
  48. package/dist/core/shield.d.ts +39 -0
  49. package/dist/core/shield.js +60 -0
  50. package/dist/core/social.d.ts +173 -0
  51. package/dist/core/social.js +337 -0
  52. package/dist/core/storage.d.ts +159 -6
  53. package/dist/core/storage.js +287 -7
  54. package/dist/core/tokens.d.ts +74 -0
  55. package/dist/core/tokens.js +155 -0
  56. package/dist/db/d1.d.ts +32 -0
  57. package/dist/db/d1.js +26 -0
  58. package/dist/db/libsql.d.ts +29 -0
  59. package/dist/db/libsql.js +32 -0
  60. package/dist/db/pg.d.ts +29 -0
  61. package/dist/db/pg.js +33 -0
  62. package/dist/mcp/server.d.ts +19 -0
  63. package/dist/mcp/server.js +355 -0
  64. package/docs/ai-manifest.json +2472 -0
  65. package/docs/ai.md +128 -0
  66. package/docs/architecture.md +331 -0
  67. package/docs/authentication.md +453 -0
  68. package/docs/authorization.md +167 -0
  69. package/docs/broadcasting.md +137 -0
  70. package/docs/broker.md +500 -0
  71. package/docs/cache.md +558 -0
  72. package/docs/configuration.md +311 -0
  73. package/docs/console.md +356 -0
  74. package/docs/container.md +467 -0
  75. package/docs/controllers.md +265 -0
  76. package/docs/cors.md +51 -0
  77. package/docs/database.md +530 -0
  78. package/docs/debugging.md +129 -0
  79. package/docs/decorators.md +127 -0
  80. package/docs/errors.md +395 -0
  81. package/docs/events.md +496 -0
  82. package/docs/examples/architecture-app.ts +27 -0
  83. package/docs/examples/authentication.ts +61 -0
  84. package/docs/examples/authorization.ts +79 -0
  85. package/docs/examples/broadcasting.ts +60 -0
  86. package/docs/examples/broker-cache-validate.ts +34 -0
  87. package/docs/examples/broker-fault-tolerance.ts +29 -0
  88. package/docs/examples/broker-middleware.ts +27 -0
  89. package/docs/examples/broker.ts +203 -0
  90. package/docs/examples/cache.ts +222 -0
  91. package/docs/examples/configuration.ts +81 -0
  92. package/docs/examples/container.ts +134 -0
  93. package/docs/examples/controllers.ts +86 -0
  94. package/docs/examples/database.ts +118 -0
  95. package/docs/examples/debugging.ts +41 -0
  96. package/docs/examples/decorators.ts +40 -0
  97. package/docs/examples/errors.ts +121 -0
  98. package/docs/examples/events.ts +204 -0
  99. package/docs/examples/factories.ts +84 -0
  100. package/docs/examples/hashing.ts +71 -0
  101. package/docs/examples/health.ts +94 -0
  102. package/docs/examples/helpers.ts +171 -0
  103. package/docs/examples/hooks.ts +54 -0
  104. package/docs/examples/inertia.ts +81 -0
  105. package/docs/examples/locks.ts +120 -0
  106. package/docs/examples/logger.ts +92 -0
  107. package/docs/examples/mail.ts +160 -0
  108. package/docs/examples/middleware.ts +119 -0
  109. package/docs/examples/migrations.ts +126 -0
  110. package/docs/examples/models.ts +239 -0
  111. package/docs/examples/notification.ts +124 -0
  112. package/docs/examples/providers.ts +123 -0
  113. package/docs/examples/queues.ts +254 -0
  114. package/docs/examples/rate-limiting.ts +42 -0
  115. package/docs/examples/redis.ts +99 -0
  116. package/docs/examples/request-response.ts +197 -0
  117. package/docs/examples/routing.ts +186 -0
  118. package/docs/examples/scheduling.ts +62 -0
  119. package/docs/examples/sessions.ts +102 -0
  120. package/docs/examples/static-files.ts +63 -0
  121. package/docs/examples/storage.ts +132 -0
  122. package/docs/examples/templates.ts +58 -0
  123. package/docs/examples/testing.ts +66 -0
  124. package/docs/examples/transformer.ts +141 -0
  125. package/docs/examples/transformers.ts +49 -0
  126. package/docs/examples/url-builder.ts +86 -0
  127. package/docs/examples/validation.ts +102 -0
  128. package/docs/examples/views.tsx +62 -0
  129. package/docs/examples/vite.ts +106 -0
  130. package/docs/factories.md +166 -0
  131. package/docs/getting-started.md +290 -0
  132. package/docs/hashing.md +259 -0
  133. package/docs/health.md +225 -0
  134. package/docs/helpers.md +347 -0
  135. package/docs/hono.md +186 -0
  136. package/docs/hooks.md +118 -0
  137. package/docs/inertia.md +241 -0
  138. package/docs/locks.md +323 -0
  139. package/docs/logger.md +290 -0
  140. package/docs/mail.md +678 -0
  141. package/docs/middleware.md +425 -0
  142. package/docs/migrations.md +476 -0
  143. package/docs/models.md +810 -0
  144. package/docs/notifications.md +474 -0
  145. package/docs/providers.md +363 -0
  146. package/docs/queues.md +679 -0
  147. package/docs/rate-limiting.md +155 -0
  148. package/docs/redis.md +178 -0
  149. package/docs/request-response.md +953 -0
  150. package/docs/routing.md +804 -0
  151. package/docs/scheduling.md +110 -0
  152. package/docs/security.md +85 -0
  153. package/docs/sessions.md +354 -0
  154. package/docs/social-auth.md +174 -0
  155. package/docs/static-files.md +211 -0
  156. package/docs/storage.md +450 -0
  157. package/docs/templates.md +315 -0
  158. package/docs/testing.md +125 -0
  159. package/docs/transformers.md +381 -0
  160. package/docs/url-builder.md +295 -0
  161. package/docs/validation.md +288 -0
  162. package/docs/views.md +267 -0
  163. package/docs/vite.md +434 -0
  164. package/llms-full.txt +17694 -0
  165. package/llms.txt +116 -0
  166. package/package.json +38 -7
@@ -0,0 +1,363 @@
1
+ # Service Providers
2
+
3
+ Service providers are the central place to configure your application. Nearly
4
+ everything Keel boots — config, routing, your own services — is wired up in a
5
+ provider.
6
+
7
+ ## The lifecycle
8
+
9
+ A provider has four hooks, run across the application's lifecycle. Only
10
+ `register()` and `boot()` are needed day-to-day; `ready()` and `shutdown()` are
11
+ there for work that must happen once the whole app is live, or as it stops:
12
+
13
+ ```ts
14
+ import { ServiceProvider } from "@shaferllc/keel/core";
15
+
16
+ export class AppServiceProvider extends ServiceProvider {
17
+ register(): void {
18
+ // Phase 1. Bind things into the container.
19
+ // Do NOT resolve other services here — nothing is guaranteed
20
+ // to be registered yet.
21
+ }
22
+
23
+ boot(): void {
24
+ // Phase 2. Runs after EVERY provider has registered.
25
+ // Safe to resolve services and wire them together.
26
+ }
27
+
28
+ ready(): void {
29
+ // Phase 3. Runs after every provider has booted and the app is fully up.
30
+ // For work that needs a live app — warm a cache, attach to the server.
31
+ }
32
+
33
+ shutdown(): void {
34
+ // Phase 4. Runs on graceful termination, in reverse registration order.
35
+ // Close connections, flush queues, cancel timers.
36
+ }
37
+ }
38
+ ```
39
+
40
+ The `Application` runs **all** `register()` methods first, then **all** `boot()`
41
+ methods, then **all** `ready()` methods — that ordering is what lets providers
42
+ depend on each other without worrying about load order. On `app.terminate()`
43
+ (wired to SIGINT/SIGTERM by `keel serve`), every `shutdown()` runs in **reverse**
44
+ registration order (LIFO), so a provider tears down before the ones it depended
45
+ on. All four hooks may be `async` — the application awaits them. `ready()` and
46
+ `shutdown()` are optional and map onto the app's `onReady()` / `onShutdown()`
47
+ hooks, so a provider's `shutdown()` runs alongside any it registered by hand.
48
+
49
+ ## The `app` reference
50
+
51
+ Every provider is handed the `Application` at construction and holds it as
52
+ `this.app` (a `protected` field). The `Application` **is** the service container,
53
+ so `this.app` gives you `bind`, `singleton`, `instance`, `make`, and `bound`
54
+ directly, plus the framework accessors `config()`, `router()`, and `view()`:
55
+
56
+ ```ts
57
+ import { ServiceProvider } from "@shaferllc/keel/core";
58
+ import { SearchIndex } from "../Services/SearchIndex.js";
59
+
60
+ export class SearchServiceProvider extends ServiceProvider {
61
+ register(): void {
62
+ this.app.singleton("search", () => new SearchIndex());
63
+ }
64
+
65
+ boot(): void {
66
+ const debug = this.app.config().get("app.debug", false);
67
+ this.app.router().get("/health", () => "ok");
68
+ }
69
+ }
70
+ ```
71
+
72
+ You never construct a provider yourself — the `Application` does it for you when
73
+ you register the class. It calls `new Provider(app)`, so `this.app` is always the
74
+ live application instance.
75
+
76
+ ## Registering a provider
77
+
78
+ Add your provider class to `bootstrap/providers.ts`:
79
+
80
+ ```ts
81
+ import type { ProviderClass } from "@shaferllc/keel/core";
82
+ import { AppServiceProvider } from "../app/Providers/AppServiceProvider.js";
83
+ import { BillingServiceProvider } from "../app/Providers/BillingServiceProvider.js";
84
+
85
+ export const providers: ProviderClass[] = [
86
+ AppServiceProvider,
87
+ BillingServiceProvider,
88
+ ];
89
+ ```
90
+
91
+ Providers boot in array order. Under the hood, `app.boot(providers)` calls
92
+ `app.register(Provider)` for each class — which does `new Provider(app)` and
93
+ stashes the instance — then runs the two phases across the whole set.
94
+
95
+ ## Providers are Keel's plugin system
96
+
97
+ A service provider is Keel's answer to a **plugin**: a self-contained slice of
98
+ functionality you register into the app. To make one **reusable**, register it
99
+ with **options** — they arrive as `this.options`, typed via the generic:
100
+
101
+ ```ts
102
+ class RateLimitProvider extends ServiceProvider<{ max: number }> {
103
+ boot() {
104
+ this.app.make(HttpKernel).use(rateLimiter({ max: this.options.max }));
105
+ }
106
+ }
107
+
108
+ app.register(RateLimitProvider, { max: 100 }); // parameterized, like a plugin
109
+ ```
110
+
111
+ The same provider class can be registered more than once with different options.
112
+ Without options, `this.options` is an empty object.
113
+
114
+ > Unlike Fastify plugins, Keel providers are **not encapsulated** — bindings,
115
+ > decorators, and routes are registered into the one global container. That's a
116
+ > deliberate simplification: there's a single, predictable scope, and no
117
+ > plugin-boundary rules to reason about. For per-request behavior in the HTTP
118
+ > pipeline (auth, logging, etc.), reach for [middleware](./middleware.md), which
119
+ > *is* scoped to the routes you attach it to.
120
+
121
+ ## Generating a provider
122
+
123
+ ```bash
124
+ npm run keel make:provider Billing
125
+ ```
126
+
127
+ Writes `app/Providers/BillingServiceProvider.ts`. Remember to add it to
128
+ `bootstrap/providers.ts`.
129
+
130
+ ## A realistic example
131
+
132
+ ```ts
133
+ import { ServiceProvider, Config } from "@shaferllc/keel/core";
134
+ import { StripeClient } from "../Services/StripeClient.js";
135
+
136
+ export class BillingServiceProvider extends ServiceProvider {
137
+ register(): void {
138
+ this.app.singleton(StripeClient, (app) => {
139
+ const key = app.make(Config).get<string>("services.stripe.key");
140
+ return new StripeClient(key);
141
+ });
142
+ }
143
+
144
+ boot(): void {
145
+ // e.g. register webhooks, warm a cache, etc.
146
+ }
147
+ }
148
+ ```
149
+
150
+ Now any controller or service can `this.app.make(StripeClient)` and get the same
151
+ configured instance.
152
+
153
+ ## Async providers
154
+
155
+ Both phases may return a promise, and the application `await`s each one before
156
+ moving on. Reach for this when a binding needs to open a connection or fetch a
157
+ remote manifest:
158
+
159
+ ```ts
160
+ import { ServiceProvider } from "@shaferllc/keel/core";
161
+ import { SearchClient } from "../Services/SearchClient.js";
162
+
163
+ export class SearchServiceProvider extends ServiceProvider {
164
+ async register(): Promise<void> {
165
+ const client = await SearchClient.connect(process.env.SEARCH_URL!);
166
+ this.app.instance(SearchClient, client);
167
+ }
168
+
169
+ async boot(): Promise<void> {
170
+ await this.app.make(SearchClient).warm();
171
+ }
172
+ }
173
+ ```
174
+
175
+ Because every `register()` is awaited before any `boot()` runs, an async
176
+ `register()` in one provider still completes before another provider's `boot()`
177
+ tries to resolve what it bound.
178
+
179
+ ## Error behavior
180
+
181
+ `register()` and `boot()` run inside `app.boot()`, which awaits each call in
182
+ sequence. If any provider throws (or rejects), `app.boot()` rejects and the
183
+ remaining providers never run — so a bad binding fails the whole boot loudly
184
+ rather than leaving a half-wired container. Booting is also idempotent: once
185
+ `app.boot()` has completed, calling it again returns immediately without
186
+ re-running any provider.
187
+
188
+ ## Rules of thumb
189
+
190
+ - **`register()` binds. `boot()` uses.** Resolving a service in `register()` is
191
+ the most common mistake — the thing you need may not be bound yet.
192
+ - **Keep providers focused.** One provider per concern (billing, auth, search)
193
+ reads better than one giant `AppServiceProvider`.
194
+ - **Order matters only for `boot()` side effects**, since all registration
195
+ happens before any booting.
196
+
197
+ ## Related
198
+
199
+ Providers wire services into the [container](./container.md); the
200
+ [Application](./architecture.md) kernel constructs and boots them. See
201
+ [configuration](./configuration.md) for what `this.app.config()` reads.
202
+
203
+ ---
204
+
205
+ ## API reference
206
+
207
+ ### `ServiceProvider`
208
+
209
+ The abstract base class every provider extends. You never instantiate it
210
+ directly — you subclass it and register the subclass (via `bootstrap/providers.ts`
211
+ or `app.register()`), and the `Application` constructs it for you. Override
212
+ `register()` and/or `boot()`; both are no-ops by default.
213
+
214
+ ```ts
215
+ import { ServiceProvider } from "@shaferllc/keel/core";
216
+
217
+ export class AppServiceProvider extends ServiceProvider {
218
+ register(): void {
219
+ this.app.bind("clock", () => new Date().toISOString());
220
+ }
221
+ }
222
+ ```
223
+
224
+ **Notes:** `abstract`, so it can't be `new`ed on its own. A subclass needn't
225
+ override both methods — leave one off to inherit the empty default.
226
+
227
+ #### `constructor(app, options?)`
228
+
229
+ `constructor(app: Application, options?: O)` (on `ServiceProvider<O>`)
230
+
231
+ Receives the live `Application` (stored as `protected this.app`) and the options
232
+ passed to `register` (stored as `protected this.options`, `{}` if none). The
233
+ `Application` invokes this for you; you don't call it.
234
+
235
+ ```ts
236
+ class RateLimitProvider extends ServiceProvider<{ max: number }> {
237
+ boot() {
238
+ this.app.make(HttpKernel).use(rateLimiter({ max: this.options.max }));
239
+ }
240
+ }
241
+ app.register(RateLimitProvider, { max: 100 });
242
+ ```
243
+
244
+ **Notes:** `app` and `options` are `protected` — reachable from subclass methods,
245
+ not from outside. Type the options via the class generic `ServiceProvider<O>`.
246
+
247
+ #### `app.register(Provider, options?)`
248
+
249
+ `register(Provider: ProviderClass, options?: unknown): this`
250
+
251
+ Registers a provider, optionally with options handed to its constructor. Chainable.
252
+ `app.boot([Providers])` registers each without options.
253
+
254
+ #### `register()`
255
+
256
+ `register(): void | Promise<void>`
257
+
258
+ Phase-one hook: bind services into the container. Called for every provider
259
+ before any `boot()` runs. Default implementation is empty.
260
+
261
+ ```ts
262
+ register(): void {
263
+ this.app.singleton(StripeClient, (app) =>
264
+ new StripeClient(app.make(Config).get<string>("services.stripe.key")),
265
+ );
266
+ }
267
+ ```
268
+
269
+ **Notes:** do **not** resolve other services here — another provider may not have
270
+ bound them yet. May be `async`; the application awaits it. Throwing rejects
271
+ `app.boot()`.
272
+
273
+ #### `boot()`
274
+
275
+ `boot(): void | Promise<void>`
276
+
277
+ Phase-two hook: runs after **every** provider has registered, so it's safe to
278
+ resolve services and wire them together. Default implementation is empty.
279
+
280
+ ```ts
281
+ async boot(): Promise<void> {
282
+ await this.app.make(SearchClient).warm();
283
+ }
284
+ ```
285
+
286
+ **Notes:** providers boot in registration (array) order — the only place order
287
+ matters, since all `register()` calls finish first. May be `async`; the
288
+ application awaits it. Throwing rejects `app.boot()`.
289
+
290
+ #### `ready()`
291
+
292
+ `ready(): void | Promise<void>`
293
+
294
+ Phase-three hook: runs after **every** provider has booted and the app is fully
295
+ up (after the app's own `onReady` hooks). For work that needs a live app —
296
+ warming a cache, attaching to the running server. Default implementation is
297
+ empty.
298
+
299
+ ```ts
300
+ async ready(): Promise<void> {
301
+ await this.app.make(Cache).warm(["home", "pricing"]);
302
+ }
303
+ ```
304
+
305
+ **Notes:** runs in registration order, once, at the end of `app.boot()`. Optional
306
+ — omit it and nothing runs. May be `async`; the application awaits it.
307
+
308
+ #### `shutdown()`
309
+
310
+ `shutdown(): void | Promise<void>`
311
+
312
+ Cleanup hook: runs on `app.terminate()` (which `keel serve` wires to SIGINT and
313
+ SIGTERM) in **reverse** registration order. Close database/Redis connections,
314
+ flush logs, cancel timers. Default implementation is empty.
315
+
316
+ ```ts
317
+ async shutdown(): Promise<void> {
318
+ await this.app.make(Redis).quit();
319
+ }
320
+ ```
321
+
322
+ **Notes:** LIFO — the last provider registered shuts down first, so a provider
323
+ tears down before the ones it depends on. It joins the app's `onShutdown` hooks,
324
+ so a hook a provider registered by hand and its `shutdown()` both run. A throw
325
+ doesn't stop the others; the first error is re-thrown after all have run.
326
+
327
+ #### `app` (protected property)
328
+
329
+ `protected app: Application`
330
+
331
+ The application/container this provider configures. Use it in `register()` to
332
+ bind and in `boot()` to resolve.
333
+
334
+ ```ts
335
+ boot(): void {
336
+ const level = this.app.config().get("logger.level", "info");
337
+ }
338
+ ```
339
+
340
+ **Notes:** it's the same `Application` instance across every provider, exposing
341
+ the container methods (`bind`, `singleton`, `instance`, `make`, `bound`) plus
342
+ `config()`, `router()`, and `view()`. Being `protected`, it's only visible to
343
+ subclass code.
344
+
345
+ ### Interfaces & types
346
+
347
+ #### `ProviderClass`
348
+
349
+ `type ProviderClass = new (app: Application) => ServiceProvider`
350
+
351
+ The constructor type of a provider — a class (not an instance) that takes an
352
+ `Application` and yields a `ServiceProvider`. Use it to type the array you export
353
+ from `bootstrap/providers.ts` and anywhere you pass provider classes around.
354
+
355
+ ```ts
356
+ import type { ProviderClass } from "@shaferllc/keel/core";
357
+ import { AppServiceProvider } from "../app/Providers/AppServiceProvider.js";
358
+
359
+ export const providers: ProviderClass[] = [AppServiceProvider];
360
+ ```
361
+
362
+ **Notes:** it references the class itself, so entries are the class name with no
363
+ `new` and no parentheses. `app.register()` and `app.boot()` both accept these.