@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,239 @@
1
+ // Type-check harness for docs/models.md. Every type-checkable snippet in the
2
+ // guide and its API reference is exercised here against the real exports, so a
3
+ // renamed method or wrong argument type fails `npm run typecheck:docs`.
4
+ // Compile-only — never executed.
5
+ import {
6
+ Model,
7
+ Relation,
8
+ HasOne,
9
+ HasMany,
10
+ BelongsTo,
11
+ BelongsToMany,
12
+ setConnection,
13
+ json,
14
+ type Connection,
15
+ type Row,
16
+ type CastType,
17
+ type Casts,
18
+ } from "@shaferllc/keel/core";
19
+
20
+ declare const email: string;
21
+ declare const roleId: number;
22
+
23
+ /* ------------------------------- define models ------------------------------ */
24
+
25
+ export class User extends Model {
26
+ static table = "users";
27
+ static primaryKey = "id"; // default
28
+
29
+ declare id: number;
30
+ declare email: string;
31
+ declare name: string;
32
+
33
+ posts() {
34
+ return this.hasMany(Post);
35
+ }
36
+ profile() {
37
+ return this.hasOne(Profile);
38
+ }
39
+ roles() {
40
+ return this.belongsToMany(Role);
41
+ }
42
+ }
43
+
44
+ export class Post extends Model {
45
+ static table = "posts";
46
+ static fillable = ["title", "body"];
47
+ static casts = {
48
+ published: "boolean", // 1/0 <-> true/false
49
+ views: "int", // "10" -> 10
50
+ meta: "json", // '{"a":1}' <-> { a: 1 } (also "array")
51
+ posted_at: "date", // ISO string <-> Date
52
+ } as const;
53
+
54
+ declare id: number;
55
+ declare title: string;
56
+ declare published: boolean;
57
+ declare meta: Record<string, unknown>;
58
+
59
+ author() {
60
+ return this.belongsTo(User);
61
+ }
62
+ }
63
+
64
+ export class Profile extends Model {
65
+ static table = "profiles";
66
+ declare id: number;
67
+ }
68
+
69
+ export class Role extends Model {
70
+ static table = "roles";
71
+ declare id: number;
72
+ }
73
+
74
+ /* -------------------------------- connection -------------------------------- */
75
+
76
+ export function connect() {
77
+ // The mock uses `as Connection` because `select` is declared generic — a
78
+ // concrete implementation returns `Row[]`, which the compiler can't match to
79
+ // an arbitrary `T` without the cast.
80
+ const connection = {
81
+ select: async () => [] as Row[],
82
+ write: async () => ({ rowsAffected: 1, insertId: 1 }),
83
+ } as Connection;
84
+ setConnection(connection, "sqlite");
85
+ }
86
+
87
+ /* --------------------------------- reading ---------------------------------- */
88
+
89
+ export async function reading() {
90
+ const all = await User.all();
91
+ const one = await User.find(1);
92
+ const orFail = await User.findOrFail(1);
93
+ const firstOne = await User.first();
94
+ const active = await User.where("active", true);
95
+ const rich = await User.query().where("age", ">", 18).orderBy("name").limit(10).get();
96
+ return { all, one, orFail, firstOne, active, rich };
97
+ }
98
+
99
+ /* --------------------------------- writing ---------------------------------- */
100
+
101
+ export async function writing() {
102
+ const user = await User.create({ email: "a@b.com", name: "Ada" });
103
+
104
+ user.name = "Grace";
105
+ await user.save();
106
+
107
+ const draft = new User({ email: "new@x.com" });
108
+ await draft.save();
109
+ draft.id;
110
+
111
+ await user.delete();
112
+ }
113
+
114
+ /* -------------------------------- attribute casts --------------------------- */
115
+
116
+ export async function casts() {
117
+ const post = await Post.find(1);
118
+ post?.published; // real boolean
119
+ post?.meta; // real object
120
+ if (post) {
121
+ post.published = false;
122
+ await post.save();
123
+ }
124
+ }
125
+
126
+ /* ------------------------------- mass assignment ---------------------------- */
127
+
128
+ export async function massAssignment(request: { all(): Row }) {
129
+ class Article extends Model {
130
+ static table = "articles";
131
+ static guarded = ["is_admin"];
132
+ declare title: string;
133
+ }
134
+
135
+ await Post.create({ title: "Hi", is_admin: true }); // is_admin dropped
136
+ const post = new Post();
137
+ post.fill(request.all()); // safe from over-posting
138
+ post.forceFill({ is_admin: true }); // explicit bypass
139
+
140
+ const article = new Article();
141
+ article.fill(request.all());
142
+ }
143
+
144
+ /* --------------------------------- serializing ------------------------------ */
145
+
146
+ export async function serializing(user: User) {
147
+ user.toJSON();
148
+ json(user);
149
+ user.fill({ name: "X" });
150
+ }
151
+
152
+ /* -------------------------------- relationships ----------------------------- */
153
+
154
+ export async function relationships(user: User, post: Post) {
155
+ const posts = await user.posts(); // Post[]
156
+ const author = await post.author(); // User | null
157
+
158
+ const recent = await user.posts().query().orderBy("created_at", "desc").limit(5).get();
159
+ return { posts, author, recent };
160
+ }
161
+
162
+ /* -------------------------------- eager loading ----------------------------- */
163
+
164
+ export async function eagerLoading() {
165
+ const users = await User.all();
166
+ await User.load(users, "posts", "roles");
167
+
168
+ const loaded = users[0]?.getRelation<Post[]>("posts");
169
+ users[0]?.toJSON();
170
+ return { loaded };
171
+ }
172
+
173
+ /* --------------------------------- many-to-many ----------------------------- */
174
+
175
+ export async function manyToMany(user: User) {
176
+ await user.roles().attach(roleId);
177
+ await user.roles().attach(roleId, { assigned_at: "now" });
178
+ await user.roles().detach(roleId);
179
+ await user.roles().detach();
180
+ await user.roles().sync([1, 2, 3]);
181
+ }
182
+
183
+ /* -------------------------------- override keys ----------------------------- */
184
+
185
+ export function overrideKeys(user: User, post: Post) {
186
+ const a: HasMany<Post> = user.hasMany(Post, "authored_by", "id");
187
+ const b: BelongsTo<User> = post.belongsTo(User, "owner_id", "id");
188
+ const c: BelongsToMany<Role> = user.belongsToMany(Role, "user_roles", "user_id", "role_id");
189
+ const d: HasOne<Profile> = user.hasOne(Profile, "user_id", "id");
190
+ return { a, b, c, d };
191
+ }
192
+
193
+ /* --------------------------- relation base / subclasses --------------------- */
194
+
195
+ export async function relationSurface(user: User) {
196
+ const rel: HasMany<Post> = user.posts();
197
+ const base: Relation<Post, Post[]> = rel;
198
+ base.query();
199
+ const viaGet = await base.get();
200
+ const viaAwait = await base; // PromiseLike — then() resolves to the result
201
+ rel.query();
202
+
203
+ const one: HasOne<Profile> = user.hasOne(Profile);
204
+ const oneResult = await one.get(); // Profile | null
205
+ return { viaGet, viaAwait, oneResult };
206
+ }
207
+
208
+ /* --------------------------- static configuration --------------------------- */
209
+
210
+ export function configuration() {
211
+ User.table;
212
+ User.primaryKey;
213
+ Post.fillable;
214
+ Post.guarded;
215
+ Post.casts;
216
+ Post.timestamps;
217
+ Post.createdAtColumn;
218
+ Post.updatedAtColumn;
219
+ Post.query();
220
+ }
221
+
222
+ export async function ormMaturity() {
223
+ const page = await User.paginate(2, 15); // Paginated<User>
224
+ const users: User[] = page.data;
225
+ const meta = { total: page.total, currentPage: page.currentPage, lastPage: page.lastPage };
226
+
227
+ const tag = await User.firstOrCreate({ email: "a@b.com" }, { name: "Ada" });
228
+ const sub = await User.updateOrCreate({ email: "a@b.com" }, { name: "Grace" });
229
+
230
+ await tag.update({ name: "Ada L." });
231
+ await sub.refresh();
232
+ return { users, meta, tag, sub };
233
+ }
234
+
235
+ /* ---------------------------- interface / type seams ------------------------ */
236
+
237
+ const castType: CastType = "boolean";
238
+ const castMap: Casts = { published: "boolean", views: "int", meta: "json" };
239
+ export { castType, castMap };
@@ -0,0 +1,124 @@
1
+ // Type-check harness for docs/notifications.md. Every type-checkable snippet in
2
+ // the guide is exercised here against the real exports, so a renamed method or
3
+ // wrong argument type fails `npm run typecheck:docs`. Compile-only — never run.
4
+ import {
5
+ Notification,
6
+ Notifier,
7
+ MailChannel,
8
+ DatabaseChannel,
9
+ ArrayChannel,
10
+ routeFor,
11
+ notify,
12
+ setNotifier,
13
+ getNotifier,
14
+ type Notifiable,
15
+ type MailContent,
16
+ type Channel,
17
+ } from "@shaferllc/keel/core";
18
+
19
+ declare const user: Notifiable;
20
+ declare const alice: Notifiable;
21
+ declare const bob: Notifiable;
22
+
23
+ export class InvoicePaid extends Notification {
24
+ constructor(private amount: number) {
25
+ super();
26
+ }
27
+ via(_notifiable: Notifiable) {
28
+ return ["mail", "database"];
29
+ }
30
+ toMail(): MailContent {
31
+ return { subject: "Payment received", text: `Thanks for $${this.amount}.` };
32
+ }
33
+ toArray() {
34
+ return { amount: this.amount };
35
+ }
36
+ }
37
+
38
+ export class QueuedInvoicePaid extends Notification {
39
+ shouldQueue = true;
40
+ via() {
41
+ return ["array"];
42
+ }
43
+ toArray() {
44
+ return {};
45
+ }
46
+ }
47
+
48
+ export async function sending() {
49
+ await notify(user, new InvoicePaid(4200));
50
+ await notify([alice, bob], new InvoicePaid(4200));
51
+ }
52
+
53
+ export function routing() {
54
+ const mail = routeFor(user, "mail");
55
+ const dbId = routeFor(user, "database");
56
+ return { mail, dbId };
57
+ }
58
+
59
+ export function registerChannels() {
60
+ setNotifier(new Notifier().channel("database", new DatabaseChannel()));
61
+ getNotifier().channel("slack", slack);
62
+ }
63
+
64
+ // A custom channel — the illustrative Slack snippet, compiling.
65
+ export const slack: Channel = {
66
+ async send(notifiable, notification) {
67
+ const payload = notification.toArray?.(notifiable) ?? {};
68
+ void payload;
69
+ // POST payload to a Slack webhook via fetch…
70
+ },
71
+ };
72
+
73
+ export async function customChannel() {
74
+ setNotifier(new Notifier().channel("slack", slack));
75
+ }
76
+
77
+ export async function inTests() {
78
+ const array = new ArrayChannel();
79
+ setNotifier(new Notifier().channel("array", array));
80
+
81
+ await notify(user, new QueuedInvoicePaid());
82
+
83
+ const first = array.sent[0];
84
+ return { count: array.sent.length, notification: first?.notification };
85
+ }
86
+
87
+ // API-reference entries
88
+ export async function reference() {
89
+ await notify(user, new InvoicePaid(4200));
90
+ setNotifier(new Notifier().channel("database", new DatabaseChannel()));
91
+ getNotifier().channel("slack", slack);
92
+
93
+ routeFor(user, "mail");
94
+ routeFor(user, "database");
95
+
96
+ const notifier = new Notifier()
97
+ .channel("database", new DatabaseChannel())
98
+ .channel("array", new ArrayChannel());
99
+ await notifier.send(user, new InvoicePaid(4200));
100
+ await new Notifier().send(user, new InvoicePaid(4200));
101
+
102
+ new DatabaseChannel();
103
+ new DatabaseChannel("alerts");
104
+
105
+ await new MailChannel().send(user, new InvoicePaid(4200));
106
+ await new DatabaseChannel().send(user, new InvoicePaid(4200));
107
+
108
+ const arrayChannel = new ArrayChannel();
109
+ await arrayChannel.send(user, new InvoicePaid(4200));
110
+ return arrayChannel.sent[0]?.notification;
111
+ }
112
+
113
+ // Interface / type seams
114
+ const content: MailContent = {
115
+ subject: "Welcome",
116
+ html: "<h1>Hi</h1>",
117
+ to: "override@app.com",
118
+ };
119
+
120
+ const channel: Channel = {
121
+ async send(_notifiable, _notification) {},
122
+ };
123
+
124
+ export { content, channel };
@@ -0,0 +1,123 @@
1
+ // Type-check harness for docs/providers.md. Every type-checkable snippet in the
2
+ // guide is exercised here against the real exports, so a renamed method or wrong
3
+ // signature fails `npm run typecheck:docs`. Compile-only — never executed.
4
+ import {
5
+ ServiceProvider,
6
+ Config,
7
+ Application,
8
+ type ProviderClass,
9
+ } from "@shaferllc/keel/core";
10
+
11
+ // Stand-in services the snippets bind — shape-only, never run.
12
+ class StripeClient {
13
+ constructor(_key: string) {}
14
+ }
15
+ class SearchIndex {}
16
+ class SearchClient {
17
+ static connect(_url: string): Promise<SearchClient> {
18
+ return Promise.resolve(new SearchClient());
19
+ }
20
+ warm(): Promise<void> {
21
+ return Promise.resolve();
22
+ }
23
+ }
24
+ class Cache {
25
+ warm(_keys: string[]): Promise<void> {
26
+ return Promise.resolve();
27
+ }
28
+ }
29
+ class Redis {
30
+ quit(): Promise<void> {
31
+ return Promise.resolve();
32
+ }
33
+ }
34
+
35
+ // --- The lifecycle (all four hooks) ----------------------------------------
36
+ export class AppServiceProvider extends ServiceProvider {
37
+ register(): void {
38
+ this.app.bind("clock", () => new Date().toISOString());
39
+ }
40
+
41
+ boot(): void {
42
+ // Phase 2 — safe to resolve.
43
+ }
44
+
45
+ async ready(): Promise<void> {
46
+ await this.app.make(Cache).warm(["home", "pricing"]);
47
+ }
48
+
49
+ async shutdown(): Promise<void> {
50
+ await this.app.make(Redis).quit();
51
+ }
52
+ }
53
+
54
+ // --- The `app` reference ---------------------------------------------------
55
+ export class SearchServiceProvider extends ServiceProvider {
56
+ register(): void {
57
+ this.app.singleton("search", () => new SearchIndex());
58
+ }
59
+
60
+ boot(): void {
61
+ const debug = this.app.config().get("app.debug", false);
62
+ this.app.router().get("/health", () => "ok");
63
+ void debug;
64
+ }
65
+ }
66
+
67
+ // --- A realistic example ---------------------------------------------------
68
+ export class BillingServiceProvider extends ServiceProvider {
69
+ register(): void {
70
+ this.app.singleton(StripeClient, (app) => {
71
+ const key = app.make(Config).get<string>("services.stripe.key");
72
+ return new StripeClient(key);
73
+ });
74
+ }
75
+
76
+ boot(): void {
77
+ // e.g. register webhooks, warm a cache, etc.
78
+ }
79
+ }
80
+
81
+ // --- Async providers -------------------------------------------------------
82
+ export class AsyncSearchProvider extends ServiceProvider {
83
+ async register(): Promise<void> {
84
+ const client = await SearchClient.connect("https://search.example");
85
+ this.app.instance(SearchClient, client);
86
+ }
87
+
88
+ async boot(): Promise<void> {
89
+ await this.app.make(SearchClient).warm();
90
+ }
91
+ }
92
+
93
+ // --- Registering a provider ------------------------------------------------
94
+ export const providers: ProviderClass[] = [
95
+ AppServiceProvider,
96
+ BillingServiceProvider,
97
+ ];
98
+
99
+ // --- Booting the providers -------------------------------------------------
100
+ export async function bootApp(): Promise<Application> {
101
+ const app = new Application();
102
+ app.register(AppServiceProvider);
103
+ return app.boot(providers);
104
+ }
105
+
106
+ // --- Parameterized providers (plugin-style options) ------------------------
107
+ export class RateLimitProvider extends ServiceProvider<{ max: number }> {
108
+ boot(): void {
109
+ const max: number = this.options.max; // typed via the generic
110
+ void max;
111
+ }
112
+ }
113
+
114
+ export async function registerWithOptions(): Promise<Application> {
115
+ const app = new Application();
116
+ app.register(RateLimitProvider, { max: 100 });
117
+ app.register(RateLimitProvider, { max: 20 }); // again, different options
118
+ return app.boot([], { discoverConfig: false, config: { app: {} } });
119
+ }
120
+
121
+ // --- ProviderClass as a value ----------------------------------------------
122
+ const provider: ProviderClass = SearchServiceProvider;
123
+ export { provider };