@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
@@ -8,6 +8,7 @@ import { contextStorage } from "hono/context-storage";
8
8
  import { Config } from "../config.js";
9
9
  import { HttpException, NotFoundException, ValidationException, STATUS_TEXT, } from "../exceptions.js";
10
10
  import { Router } from "./router.js";
11
+ import { instrument, runRequest, newRequestId, currentRequestId } from "../instrumentation.js";
11
12
  /** Per-request stash of subdomain params, keyed by the raw Request. */
12
13
  const SUBDOMAINS = new WeakMap();
13
14
  /** Compile a host pattern like ":tenant.example.com" into a matcher. */
@@ -96,6 +97,26 @@ export class HttpKernel {
96
97
  c.set("subdomains", subs);
97
98
  await next();
98
99
  });
100
+ // Open a request scope: a request id everything downstream (queries, logs,
101
+ // jobs) can attribute itself to, plus timing. Emitting `request.handled` and
102
+ // `exception` from here — inside the scope — is what lets a watcher tie a
103
+ // request to everything that happened within it. A no-op stream when nobody
104
+ // is listening, so this stays cheap for apps that don't instrument.
105
+ hono.use("*", async (c, next) => {
106
+ const id = newRequestId();
107
+ const start = Date.now();
108
+ try {
109
+ await runRequest(id, () => next());
110
+ }
111
+ catch (err) {
112
+ // Errors thrown *outside* Hono's per-request handling (rare) land here;
113
+ // the common case (a handler throwing) is caught by Hono and reaches
114
+ // `handle()` instead, which is where `exception` is emitted.
115
+ this.reportRequest(c, id, start, err instanceof HttpException ? err.status : 500);
116
+ throw err;
117
+ }
118
+ this.reportRequest(c, id, start, c.res.status);
119
+ });
99
120
  for (const mw of this.middleware) {
100
121
  hono.use("*", mw);
101
122
  }
@@ -128,7 +149,34 @@ export class HttpKernel {
128
149
  hono.onError((err, c) => this.handle(err, c));
129
150
  return hono;
130
151
  }
152
+ /** Emit `request.handled` with method, path, status, timing, and headers. */
153
+ reportRequest(c, id, start, status) {
154
+ const url = new URL(c.req.url);
155
+ const headers = {};
156
+ c.req.raw.headers.forEach((v, k) => (headers[k] = v));
157
+ const ip = c.req.header("x-forwarded-for")?.split(",")[0]?.trim();
158
+ instrument("request.handled", {
159
+ id,
160
+ method: c.req.method,
161
+ path: url.pathname,
162
+ status,
163
+ durationMs: Date.now() - start,
164
+ headers,
165
+ ...(ip ? { ip } : {}),
166
+ });
167
+ }
131
168
  async handle(err, c) {
169
+ // The single choke point every error (thrown handler or unmatched route)
170
+ // passes through — and it runs inside the request scope, so `currentRequestId`
171
+ // ties the exception to its request.
172
+ const requestId = currentRequestId();
173
+ instrument("exception", {
174
+ error: err,
175
+ method: c.req.method,
176
+ path: new URL(c.req.url).pathname,
177
+ status: err instanceof HttpException ? err.status : 500,
178
+ ...(requestId ? { requestId } : {}),
179
+ });
132
180
  // Reportable exceptions: give them a chance to log/report themselves.
133
181
  const maybe = err;
134
182
  if (typeof maybe?.report === "function") {
@@ -2,8 +2,8 @@
2
2
  * Router facade. Collects route definitions declaratively; the HTTP kernel
3
3
  * compiles them onto the underlying Hono instance at boot.
4
4
  *
5
- * Fluent, AdonisJS-inspired API: named routes, per-route and group middleware,
6
- * prefixes, param constraints, resource routes, and URL generation.
5
+ * A fluent API: named routes, per-route and group middleware, prefixes, param
6
+ * constraints, resource routes, and URL generation.
7
7
  */
8
8
  import type { Context as HonoContext, MiddlewareHandler } from "hono";
9
9
  import type { Container, Constructor } from "../container.js";
@@ -66,7 +66,7 @@ export declare class Route {
66
66
  as(name: string): this;
67
67
  /** Attach middleware that runs only for this route (after group middleware). */
68
68
  middleware(mw: MiddlewareRef | MiddlewareRef[]): this;
69
- /** Alias for middleware(), matching AdonisJS. */
69
+ /** Alias for middleware(). */
70
70
  use(mw: MiddlewareRef | MiddlewareRef[]): this;
71
71
  /** Constrain a route parameter with a regex, source string, or matcher. */
72
72
  where(param: string, matcher: Matcher): this;
@@ -81,7 +81,7 @@ export declare class RouteGroup {
81
81
  constructor(routes: RouteDefinition[]);
82
82
  prefix(prefix: string): this;
83
83
  middleware(mw: MiddlewareRef | MiddlewareRef[]): this;
84
- /** Alias for middleware(), matching AdonisJS. */
84
+ /** Alias for middleware(). */
85
85
  use(mw: MiddlewareRef | MiddlewareRef[]): this;
86
86
  /** Attach metadata to every route in the group (a route's own config wins). */
87
87
  config(data: Record<string, unknown>): this;
@@ -115,7 +115,7 @@ declare class RouteMatcher {
115
115
  constructor(router: Router, path: string);
116
116
  /** Redirect to a path or URL. */
117
117
  redirect(to: string, status?: number): Route;
118
- /** Alias for redirect(), matching AdonisJS. */
118
+ /** Alias for redirect(). */
119
119
  redirectToPath(to: string, status?: number): Route;
120
120
  /** Redirect to a named route, optionally with params and a query string. */
121
121
  redirectToRoute(name: string, params?: Record<string, string | number>, options?: {
@@ -2,8 +2,8 @@
2
2
  * Router facade. Collects route definitions declaratively; the HTTP kernel
3
3
  * compiles them onto the underlying Hono instance at boot.
4
4
  *
5
- * Fluent, AdonisJS-inspired API: named routes, per-route and group middleware,
6
- * prefixes, param constraints, resource routes, and URL generation.
5
+ * A fluent API: named routes, per-route and group middleware, prefixes, param
6
+ * constraints, resource routes, and URL generation.
7
7
  */
8
8
  import { view, config } from "../helpers.js";
9
9
  import { redirect as makeRedirect, request } from "../request.js";
@@ -56,7 +56,7 @@ export class Route {
56
56
  this.def.middleware.push(...(Array.isArray(mw) ? mw : [mw]));
57
57
  return this;
58
58
  }
59
- /** Alias for middleware(), matching AdonisJS. */
59
+ /** Alias for middleware(). */
60
60
  use(mw) {
61
61
  return this.middleware(mw);
62
62
  }
@@ -94,7 +94,7 @@ export class RouteGroup {
94
94
  r.middleware.unshift(...list); // group runs first
95
95
  return this;
96
96
  }
97
- /** Alias for middleware(), matching AdonisJS. */
97
+ /** Alias for middleware(). */
98
98
  use(mw) {
99
99
  return this.middleware(mw);
100
100
  }
@@ -200,7 +200,7 @@ class RouteMatcher {
200
200
  redirect(to, status = 302) {
201
201
  return this.router.get(this.path, () => makeRedirect(to, status));
202
202
  }
203
- /** Alias for redirect(), matching AdonisJS. */
203
+ /** Alias for redirect(). */
204
204
  redirectToPath(to, status = 302) {
205
205
  return this.redirect(to, status);
206
206
  }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Internationalization — translations with ICU message formatting, and the
3
+ * `Intl` formatters that go with them.
4
+ *
5
+ * setTranslations({
6
+ * en: { "cart.items": "{count, plural, =0 {Your cart is empty} one {# item} other {# items}}" },
7
+ * fr: { "cart.items": "{count, plural, =0 {Panier vide} one {# article} other {# articles}}" },
8
+ * });
9
+ *
10
+ * t("cart.items", { count: 3 }); // "3 items" (the request's locale)
11
+ * i18n("fr").t("cart.items", { count: 3 }); // "3 articles"
12
+ *
13
+ * There is **no dependency** here, and there doesn't need to be: `Intl` ships with
14
+ * every modern runtime — Node and Workers both carry the full ICU data — so
15
+ * plurals, currencies, dates, and relative times are the platform's job. What Keel
16
+ * adds is the message parser on top, which is the part `Intl` doesn't do.
17
+ *
18
+ * The supported ICU subset is the part people actually use: interpolation,
19
+ * `plural`, `selectordinal`, `select`, `number`, `date`, and `time` — nested
20
+ * arbitrarily deep. See the i18n guide for the grammar.
21
+ */
22
+ import type { MiddlewareHandler } from "hono";
23
+ /** A locale's messages — nested objects or flat dot-separated keys, or both. */
24
+ export type Translations = Record<string, unknown>;
25
+ /** Messages keyed by locale: `{ en: {...}, fr: {...} }`. */
26
+ export type TranslationsByLocale = Record<string, Translations>;
27
+ /** Where translations come from. Return them; Keel flattens and indexes them. */
28
+ export interface TranslationLoader {
29
+ load(): Promise<TranslationsByLocale> | TranslationsByLocale;
30
+ }
31
+ /**
32
+ * Format an ICU message. The grammar we support:
33
+ *
34
+ * {name} interpolation
35
+ * {n, number} {n, number, percent} {n, number, ::currency/USD}
36
+ * {d, date, medium} {d, time, short}
37
+ * {c, plural, =0 {none} one {# item} other {# items}}
38
+ * {c, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}
39
+ * {g, select, male {He} female {She} other {They}}
40
+ *
41
+ * `#` inside a plural branch becomes the formatted number. Branches are
42
+ * themselves messages, so they nest. `'{'` and `'}'` are literal braces.
43
+ */
44
+ export declare function formatMessage(message: string, data?: Record<string, unknown>, locale?: string): string;
45
+ /**
46
+ * Pick the best locale for an `Accept-Language` header.
47
+ *
48
+ * negotiateLocale("fr-CA,fr;q=0.9,en;q=0.8", ["en", "fr"], "en") // "fr"
49
+ *
50
+ * Exact matches win, then the language part (`fr-CA` → `fr`), in the client's
51
+ * order of preference. Falls back to `defaultLocale` when nothing matches.
52
+ */
53
+ export declare function negotiateLocale(header: string | null | undefined, supported: string[], defaultLocale: string): string;
54
+ /** One locale, bound to its messages. Get one with `i18n(locale)`. */
55
+ export declare class I18n {
56
+ readonly locale: string;
57
+ private messages;
58
+ private manager;
59
+ constructor(locale: string, messages: Record<string, string>, manager: I18nManager);
60
+ /** Whether a key resolves in this locale (or its fallbacks). */
61
+ has(key: string): boolean;
62
+ /**
63
+ * Translate a key, formatting its ICU message with `data`.
64
+ *
65
+ * t("cart.items", { count: 3 }); // "3 items"
66
+ *
67
+ * A missing key doesn't throw — it goes through the configured `missing`
68
+ * handler (which returns the key itself by default, so the page still renders
69
+ * and the gap is obvious) and fires an `i18n.missing` event.
70
+ */
71
+ t(key: string, data?: Record<string, unknown>): string;
72
+ formatNumber(value: number, options?: Intl.NumberFormatOptions): string;
73
+ formatCurrency(value: number, currency: string, options?: Intl.NumberFormatOptions): string;
74
+ formatDate(value: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
75
+ formatTime(value: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
76
+ /**
77
+ * "3 days ago", "in 2 hours". With no unit, the best one is picked from the
78
+ * distance between `value` and now.
79
+ */
80
+ formatRelativeTime(value: Date | number | string, unit?: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;
81
+ /** "a, b, and c" — or "a, b, or c" with `{ type: "disjunction" }`. */
82
+ formatList(items: string[], options?: Intl.ListFormatOptions): string;
83
+ /** The plural category for a count: "one", "other", … */
84
+ formatPlural(count: number, options?: Intl.PluralRulesOptions): Intl.LDMLPluralRule;
85
+ /** A language/region/currency code as its display name: "fr" → "French". */
86
+ formatDisplayName(code: string, type?: Intl.DisplayNamesOptions["type"]): string;
87
+ }
88
+ export interface I18nOptions {
89
+ /** Used when nothing else matches. Default: `"en"`. */
90
+ defaultLocale?: string;
91
+ /**
92
+ * Which locales this app serves. Derived from the loaded translations when
93
+ * omitted.
94
+ */
95
+ supportedLocales?: string[];
96
+ /** Per-locale fallbacks: `{ "es-MX": "es" }`. A language prefix is tried anyway. */
97
+ fallbackLocales?: Record<string, string>;
98
+ /**
99
+ * What to render for a key with no translation. Default: the key itself — so
100
+ * the page still renders and the gap is visible rather than blank.
101
+ */
102
+ missing?: (key: string, locale: string) => string;
103
+ }
104
+ export declare class I18nManager {
105
+ private translations;
106
+ private options;
107
+ constructor(options?: I18nOptions);
108
+ get defaultLocale(): string;
109
+ /** Add translations, merging into whatever is already loaded. */
110
+ add(data: TranslationsByLocale): this;
111
+ /** Load from one or more loaders. */
112
+ load(...loaders: TranslationLoader[]): Promise<this>;
113
+ /** The locales this app serves. */
114
+ supported(): string[];
115
+ /**
116
+ * The chain to try for a locale: itself, its configured fallback, its language
117
+ * prefix (`es-MX` → `es`), then the default. That's what makes a regional
118
+ * locale usable with only the base language translated.
119
+ */
120
+ private chain;
121
+ /** An `I18n` for a locale, with its fallback chain already flattened in. */
122
+ locale(code?: string): I18n;
123
+ /** What to render for a missing key — and a chance to notice it. */
124
+ missing(key: string, locale: string): string;
125
+ }
126
+ /** Replace the i18n manager (options and all). */
127
+ export declare function setI18n(next: I18nManager): I18nManager;
128
+ /** The active i18n manager. */
129
+ export declare function getI18n(): I18nManager;
130
+ /** Register translations on the active manager. */
131
+ export declare function setTranslations(data: TranslationsByLocale): I18nManager;
132
+ /**
133
+ * An `I18n` for a locale — or, with no argument, the **current request's** locale
134
+ * (set by `detectLocale()`), falling back to the default outside a request.
135
+ */
136
+ export declare function i18n(locale?: string): I18n;
137
+ /** Translate a key in the current request's locale. */
138
+ export declare function t(key: string, data?: Record<string, unknown>): string;
139
+ /** Translations from a plain object — the simplest loader. */
140
+ export declare function objectLoader(data: TranslationsByLocale): TranslationLoader;
141
+ export interface DetectLocaleOptions {
142
+ /** Read `?lang=fr` (or another param). Highest precedence when set. */
143
+ query?: string;
144
+ /** Read a cookie. Checked after the query param, before the header. */
145
+ cookie?: string;
146
+ /** Skip the `Accept-Language` header. Default: false. */
147
+ header?: boolean;
148
+ /** Your own resolver — wins over everything if it returns a locale. */
149
+ resolve?: (c: Parameters<MiddlewareHandler>[0]) => string | undefined;
150
+ }
151
+ /**
152
+ * Work out the request's locale and stash it, so `t()` and `i18n()` pick it up
153
+ * anywhere in the request without threading it through.
154
+ *
155
+ * this.use(detectLocale()); // Accept-Language
156
+ * this.use(detectLocale({ query: "lang" })); // ?lang=fr wins
157
+ *
158
+ * Precedence: `resolve` → query → cookie → `Accept-Language` → the default. Only
159
+ * **supported** locales are honored, so `?lang=xx` can't put the app into a locale
160
+ * you have no translations for.
161
+ */
162
+ export declare function detectLocale(options?: DetectLocaleOptions): MiddlewareHandler;