@zerotal/arch 1.7.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 (160) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +67 -0
  4. package/api-surface.md +283 -0
  5. package/docs/about.md +676 -0
  6. package/docs/admin/actions.md +183 -0
  7. package/docs/admin/auth.md +89 -0
  8. package/docs/admin/dashboard.md +155 -0
  9. package/docs/admin/extending-ui.md +229 -0
  10. package/docs/admin/extending.md +205 -0
  11. package/docs/admin/forms.md +155 -0
  12. package/docs/admin/index.md +144 -0
  13. package/docs/admin/operations.md +224 -0
  14. package/docs/admin/references.md +174 -0
  15. package/docs/admin/resources.md +132 -0
  16. package/docs/admin/structure.md +158 -0
  17. package/docs/admin/tables.md +185 -0
  18. package/docs/admin/testing.md +138 -0
  19. package/docs/ai.md +373 -0
  20. package/docs/application.md +435 -0
  21. package/docs/arch.md +263 -0
  22. package/docs/assets.md +755 -0
  23. package/docs/audit.md +367 -0
  24. package/docs/authentication.md +1000 -0
  25. package/docs/authorization.md +529 -0
  26. package/docs/broadcasting/channels.md +170 -0
  27. package/docs/broadcasting/client.md +108 -0
  28. package/docs/broadcasting/events.md +190 -0
  29. package/docs/broadcasting/index.md +163 -0
  30. package/docs/broadcasting/references.md +84 -0
  31. package/docs/broadcasting/testing.md +132 -0
  32. package/docs/cache.md +387 -0
  33. package/docs/carbon.md +830 -0
  34. package/docs/changelog.md +465 -0
  35. package/docs/client/auth.md +113 -0
  36. package/docs/client/errors.md +139 -0
  37. package/docs/client/files.md +118 -0
  38. package/docs/client/index.md +128 -0
  39. package/docs/client/references.md +58 -0
  40. package/docs/client/requests.md +131 -0
  41. package/docs/client/resilience.md +141 -0
  42. package/docs/client/testing.md +146 -0
  43. package/docs/commands.md +450 -0
  44. package/docs/components.md +2427 -0
  45. package/docs/config-system.md +258 -0
  46. package/docs/container.md +596 -0
  47. package/docs/context.md +579 -0
  48. package/docs/contributing.md +164 -0
  49. package/docs/controllers.md +354 -0
  50. package/docs/conventions.md +466 -0
  51. package/docs/cookies.md +101 -0
  52. package/docs/csrf.md +206 -0
  53. package/docs/database.md +476 -0
  54. package/docs/deployment.md +434 -0
  55. package/docs/devtools.md +1013 -0
  56. package/docs/email-verification.md +320 -0
  57. package/docs/encryption.md +380 -0
  58. package/docs/errors.md +317 -0
  59. package/docs/events.md +420 -0
  60. package/docs/flow/components.md +846 -0
  61. package/docs/flow/decorators.md +458 -0
  62. package/docs/flow/events.md +420 -0
  63. package/docs/flow/forms.md +551 -0
  64. package/docs/flow/index.md +568 -0
  65. package/docs/flow/layouts.md +793 -0
  66. package/docs/flow/lifecycle.md +336 -0
  67. package/docs/flow/pagination.md +402 -0
  68. package/docs/flow/performance.md +262 -0
  69. package/docs/flow/references.md +317 -0
  70. package/docs/flow/routing.md +550 -0
  71. package/docs/flow/testing.md +447 -0
  72. package/docs/getting-started.md +286 -0
  73. package/docs/health.md +269 -0
  74. package/docs/helpers.md +423 -0
  75. package/docs/i18n.md +414 -0
  76. package/docs/index.md +65 -0
  77. package/docs/inertia/build.md +95 -0
  78. package/docs/inertia/devtools.md +197 -0
  79. package/docs/inertia/index.md +267 -0
  80. package/docs/inertia/middleware.md +81 -0
  81. package/docs/inertia/props.md +507 -0
  82. package/docs/inertia/references.md +144 -0
  83. package/docs/inertia/rendering.md +189 -0
  84. package/docs/inertia/ssr.md +102 -0
  85. package/docs/inspirations.md +257 -0
  86. package/docs/lifecycle.md +331 -0
  87. package/docs/lock.md +454 -0
  88. package/docs/logger.md +517 -0
  89. package/docs/media.md +582 -0
  90. package/docs/middleware.md +509 -0
  91. package/docs/migrations.md +529 -0
  92. package/docs/monitor.md +377 -0
  93. package/docs/notifications.md +848 -0
  94. package/docs/orm/casts.md +365 -0
  95. package/docs/orm/factories.md +231 -0
  96. package/docs/orm/index.md +663 -0
  97. package/docs/orm/lifecycle.md +421 -0
  98. package/docs/orm/queries.md +692 -0
  99. package/docs/orm/relationships.md +440 -0
  100. package/docs/orm/serialization.md +251 -0
  101. package/docs/package-development.md +536 -0
  102. package/docs/pagination.md +340 -0
  103. package/docs/password-reset.md +335 -0
  104. package/docs/providers.md +447 -0
  105. package/docs/query-builder.md +465 -0
  106. package/docs/queue.md +549 -0
  107. package/docs/rate-limiting.md +253 -0
  108. package/docs/responses.md +336 -0
  109. package/docs/roles-and-2fa.md +418 -0
  110. package/docs/routing.md +1008 -0
  111. package/docs/scaffolding.md +335 -0
  112. package/docs/scheduler.md +533 -0
  113. package/docs/seeding.md +229 -0
  114. package/docs/session.md +451 -0
  115. package/docs/social.md +524 -0
  116. package/docs/storage.md +622 -0
  117. package/docs/structure.md +230 -0
  118. package/docs/support-policy.md +114 -0
  119. package/docs/telemetry.md +505 -0
  120. package/docs/tenancy.md +608 -0
  121. package/docs/testing/browser.md +153 -0
  122. package/docs/testing/console.md +130 -0
  123. package/docs/testing/database.md +238 -0
  124. package/docs/testing/flow-browser.md +216 -0
  125. package/docs/testing/http.md +441 -0
  126. package/docs/testing/index.md +240 -0
  127. package/docs/testing/mocking.md +410 -0
  128. package/docs/upgrade.md +192 -0
  129. package/docs/validator.md +549 -0
  130. package/docs/view.md +453 -0
  131. package/package.json +60 -0
  132. package/src/bin/mcp.ts +73 -0
  133. package/src/config.ts +63 -0
  134. package/src/errors.ts +27 -0
  135. package/src/index.ts +56 -0
  136. package/src/install/ArchInstallCommand.ts +189 -0
  137. package/src/install/detect.ts +98 -0
  138. package/src/install/guidelines.ts +290 -0
  139. package/src/install/markers.ts +77 -0
  140. package/src/install/mcpConfig.ts +81 -0
  141. package/src/mcp/index.ts +27 -0
  142. package/src/mcp/jsonrpc.ts +133 -0
  143. package/src/mcp/server.ts +307 -0
  144. package/src/mcp/stdio.ts +111 -0
  145. package/src/mcp/types.ts +146 -0
  146. package/src/probe/ArchProbeCommand.ts +48 -0
  147. package/src/probe/sentinel.ts +13 -0
  148. package/src/probe/topics.ts +291 -0
  149. package/src/provider/ArchProvider.ts +101 -0
  150. package/src/tools/_probe.ts +164 -0
  151. package/src/tools/apiSurface.ts +241 -0
  152. package/src/tools/appInfo.ts +88 -0
  153. package/src/tools/baselines.ts +210 -0
  154. package/src/tools/context.ts +37 -0
  155. package/src/tools/doctor.ts +93 -0
  156. package/src/tools/index.ts +50 -0
  157. package/src/tools/logs.ts +265 -0
  158. package/src/tools/routes.ts +133 -0
  159. package/src/tools/schema.ts +119 -0
  160. package/src/tools/searchDocs.ts +345 -0
package/docs/i18n.md ADDED
@@ -0,0 +1,414 @@
1
+ ---
2
+ title: Internationalization
3
+ description: Resolve each visitor's locale automatically, then translate keys with interpolation, pluralization, and fallback.
4
+ ---
5
+
6
+ # Internationalization
7
+
8
+ Request-scoped localization: resolve each visitor's locale automatically, then
9
+ translate keys with interpolation, pluralization, and fallback — without threading
10
+ the locale through every function call.
11
+
12
+ ## Getting Started
13
+
14
+ ```bash
15
+ # in your project root
16
+ bun add @zerotal/i18n
17
+ ```
18
+
19
+ ## Register the provider
20
+
21
+ Add `I18nProvider` to the providers array in `bootstrap/providers.ts`:
22
+
23
+ ```typescript
24
+ // bootstrap/providers.ts
25
+ import { I18nProvider } from "@zerotal/i18n";
26
+
27
+ const providers = [
28
+ // …your other providers
29
+ I18nProvider,
30
+ ];
31
+
32
+ export default providers;
33
+ ```
34
+
35
+ Registering the provider switches on the following:
36
+
37
+ - `onRegister` — binds the translation service as the lazy `i18n` singleton (a
38
+ `Translator` loaded from your catalogs).
39
+ - `onBooting` — configures and registers `LocaleMiddleware` via `app.useOnce()`,
40
+ so every request resolves a locale and gains `ctx.locale` + `ctx.t()`.
41
+
42
+ ## Configuration
43
+
44
+ Create `config/i18n.ts`. The `I18nConfig()` helper fills in sensible defaults, so
45
+ you only set what differs:
46
+
47
+ ```typescript
48
+ // config/i18n.ts
49
+ import { I18nConfig } from "@zerotal/i18n";
50
+ import { env } from "zerotal";
51
+
52
+ export default I18nConfig({
53
+ defaultLocale: env("APP_LOCALE", "en"),
54
+ fallbackLocale: env("APP_FALLBACK_LOCALE", "en"),
55
+ supportedLocales: ["en"],
56
+ resolvers: ["query", "cookie", "accept-header"], // tried in order
57
+ queryKey: "lang", // ?lang=fr
58
+ cookieKey: "locale", // locale=fr cookie
59
+ loadPath: "resources/lang", // <locale>.json files (optional)
60
+ });
61
+ ```
62
+
63
+ | Field | Required | Default | Description |
64
+ | ------------------ | -------- | ------------------------------ | ------------------------------------------------------------ |
65
+ | `defaultLocale` | no | `"en"` | Locale used when no resolver matches. |
66
+ | `fallbackLocale` | no | `defaultLocale` | Locale consulted when a key is missing in the active locale. |
67
+ | `supportedLocales` | no | `[defaultLocale]` | Locales resolvers may return; others fall back to default. |
68
+ | `resolvers` | no | `query, cookie, accept-header` | Request resolvers, tried in order (see below). |
69
+ | `queryKey` | no | `"lang"` | Query-string key for the `query` resolver. |
70
+ | `cookieKey` | no | `"locale"` | Cookie name for the `cookie` resolver. |
71
+ | `loadPath` | no | — | Directory of `<locale>.json` catalogs, loaded at boot. |
72
+ | `catalogs` | no | — | In-memory catalogs, merged over anything from `loadPath`. |
73
+
74
+ > **Tip** — `I18nConfig()` is optional. You can instead write a plain object with
75
+ > `satisfies I18nConfigShape` for the same type-checking — but then every field is
76
+ > required, since the helper is what supplies the defaults above.
77
+
78
+ Catalog files live under `loadPath`, one JSON file per locale:
79
+
80
+ ```
81
+ resources/lang/
82
+ ├── en.json
83
+ ├── fr.json
84
+ └── es.json
85
+ ```
86
+
87
+ ## Basic usage
88
+
89
+ `LocaleMiddleware` resolves the locale for every request and exposes `ctx.locale`
90
+ and `ctx.t()` on the request context:
91
+
92
+ ```typescript
93
+ // in a controller
94
+ async show(ctx: HttpContext) {
95
+ ctx.t("welcome.greeting", { name: "Alice" }); // active locale
96
+ ctx.t("welcome.greeting", { name: "Alice" }, "fr"); // explicit locale
97
+ return ctx.json({ locale: ctx.locale });
98
+ }
99
+ ```
100
+
101
+ Outside a controller — in a service, job, or view — use the `Lang` facade or the
102
+ global `t()` helper. Both read the active request locale from `I18nContext`, so no
103
+ locale needs to be passed around:
104
+
105
+ ```typescript
106
+ // anywhere in the request's async tree
107
+ import { Lang, t } from "@zerotal/i18n";
108
+
109
+ Lang.translate("auth.login.title");
110
+ t("dashboard.welcome");
111
+ ```
112
+
113
+ ## Keys, interpolation, pluralization
114
+
115
+ Catalogs may use nested objects or flat dotted keys — both resolve:
116
+
117
+ ```json
118
+ // resources/lang/en.json
119
+ {
120
+ "welcome": { "greeting": "Hello, {name}!" },
121
+ "validation.required": "The :field field is required.",
122
+ "apples": "no apples | one apple | {count} apples"
123
+ }
124
+ ```
125
+
126
+ - **Interpolation** — `{name}` and `:name` are both replaced from the
127
+ replacements object.
128
+ - **Pluralization** — pipe-separated segments are chosen by `count`: two segments
129
+ are `singular | plural` (`count === 1` → first); three or more are
130
+ `zero | one | many` (`0` → first, `1` → second, otherwise last).
131
+ - **Fallback** — a key missing in the active locale is looked up in
132
+ `fallbackLocale`; if still missing, the key itself is returned, so gaps are
133
+ visible and never throw.
134
+
135
+ ```typescript
136
+ t("apples", { count: 0 }); // "no apples"
137
+ t("apples", { count: 5 }); // "5 apples"
138
+ ```
139
+
140
+ ## Locale resolution
141
+
142
+ `resolveLocale(request, config)` tries each configured resolver in order and only
143
+ returns a value listed in `supportedLocales` (otherwise `defaultLocale`):
144
+
145
+ | Resolver | Source |
146
+ | --------------- | --------------------------------------------------------- |
147
+ | `query` | `?lang=fr` (key configurable via `queryKey`) |
148
+ | `cookie` | `locale=fr` cookie (name configurable via `cookieKey`) |
149
+ | `accept-header` | `Accept-Language`, by quality; `fr-FR` falls back to `fr` |
150
+
151
+ **Which resolvers should I list?** Order them most-explicit first:
152
+
153
+ - **`query`** — useful for a one-off preview link (`?lang=fr`) or language
154
+ switcher, but it doesn't persist. Put it first so it can override the others.
155
+ - **`cookie`** — the choice that _sticks_. List it when you let users pick a
156
+ language and persist it (see [Overriding the locale](#overriding-the-locale)).
157
+ - **`accept-header`** — the visitor's browser preference; a sensible default when
158
+ no explicit choice has been made. List it last as the fallback.
159
+
160
+ ## Overriding the locale
161
+
162
+ The locale is resolved once, when `LocaleMiddleware` runs, and is then fixed for
163
+ the rest of the request — so `ctx.locale` and `ctx.t()` always reflect what the
164
+ resolvers chose. There is no `setLocale()`. To change the language, do one of two
165
+ things.
166
+
167
+ **Persist a user's choice** by writing the `locale` cookie; the `cookie` resolver
168
+ picks it up on every subsequent request:
169
+
170
+ ```typescript
171
+ // in a controller — save the visitor's language choice
172
+ async setLocale(ctx: HttpContext) {
173
+ const { locale } = await ctx.body<{ locale: string }>();
174
+
175
+ const headers = new Headers({ Location: ctx.header("Referer") ?? "/" });
176
+ headers.append(
177
+ "Set-Cookie",
178
+ `locale=${locale}; Path=/; Max-Age=${365 * 86400}; SameSite=Lax`,
179
+ );
180
+ ctx.response = new Response(null, { status: 303, headers });
181
+ }
182
+ ```
183
+
184
+ **Override within the current request** — e.g. to honour a locale stored on the
185
+ user — by running code inside `I18nContext.run()`. The `Lang` facade and `t()`
186
+ helper use the supplied locale for the duration of the callback:
187
+
188
+ ```typescript
189
+ // in a controller
190
+ import { I18nContext, t } from "@zerotal/i18n";
191
+
192
+ async show(ctx: HttpContext) {
193
+ const user = ctx.user as { name?: string; locale?: string } | undefined;
194
+ const locale = user?.locale ?? ctx.locale;
195
+
196
+ const greeting = I18nContext.run(locale, () =>
197
+ t("welcome.greeting", { name: user?.name }),
198
+ );
199
+
200
+ return ctx.view(DashboardPage({ greeting }));
201
+ }
202
+ ```
203
+
204
+ > **Note** — `I18nContext.run()` only affects the `Lang` facade and `t()` inside
205
+ > its callback. `ctx.t()` and `ctx.locale` were bound by the middleware and stay on
206
+ > the request's resolved locale. For a persistent change, set the cookie above.
207
+
208
+ ## Catalog structure
209
+
210
+ Catalogs can be flat or deeply nested — both styles resolve with the same dot-path:
211
+
212
+ ```json
213
+ // resources/lang/en.json
214
+ {
215
+ "auth": {
216
+ "login": {
217
+ "title": "Sign in",
218
+ "submit": "Sign in to your account",
219
+ "forgot": "Forgot your password?"
220
+ },
221
+ "logout": "Sign out"
222
+ },
223
+ "validation": {
224
+ "required": "The :field field is required.",
225
+ "email": "The :field must be a valid email address.",
226
+ "min": "The :field must be at least :min characters."
227
+ },
228
+ "posts": {
229
+ "count": "no posts | one post | :count posts",
230
+ "created": "Post created on :date"
231
+ }
232
+ }
233
+ ```
234
+
235
+ ```typescript
236
+ ctx.t("auth.login.title"); // "Sign in"
237
+ ctx.t("auth.logout"); // "Sign out"
238
+ ctx.t("validation.required", { field: "name" }); // "The name field is required."
239
+ ctx.t("posts.count", { count: 0 }); // "no posts"
240
+ ctx.t("posts.count", { count: 1 }); // "one post"
241
+ ctx.t("posts.count", { count: 42 }); // "42 posts"
242
+ ```
243
+
244
+ ## Translating validation messages
245
+
246
+ A common pattern is to keep your localized form-error strings under a `validation`
247
+ namespace in each catalog, then translate them with `t()` where you build the
248
+ error response:
249
+
250
+ ```json
251
+ // resources/lang/fr.json
252
+ {
253
+ "validation": {
254
+ "required": "Le champ :field est obligatoire.",
255
+ "email": "Le champ :field doit être une adresse e-mail valide.",
256
+ "min": "Le champ :field doit contenir au moins :min caractères.",
257
+ "unique": "Cette valeur est déjà prise."
258
+ }
259
+ }
260
+ ```
261
+
262
+ ```typescript
263
+ // in a controller — translate a validation message yourself
264
+ t("validation.required", { field: "email" }); // active locale
265
+ ```
266
+
267
+ > **Note** — The `validation` namespace is just a convention for organizing keys;
268
+ > the i18n package translates any key you pass to `t()`. See
269
+ > [Validator](/docs/validator) for how the validator itself reports errors.
270
+
271
+ ## Multiple supported locales
272
+
273
+ List every locale your app ships in `supportedLocales`. The resolver only returns a
274
+ locale from this list — unsupported values fall back to `defaultLocale`:
275
+
276
+ ```typescript
277
+ // config/i18n.ts
278
+ export default I18nConfig({
279
+ defaultLocale: "en",
280
+ fallbackLocale: "en",
281
+ supportedLocales: ["en", "fr", "es", "zu", "af"],
282
+ resolvers: ["cookie", "accept-header"],
283
+ });
284
+ ```
285
+
286
+ Add a catalog file for each:
287
+
288
+ ```
289
+ resources/lang/
290
+ en.json
291
+ fr.json
292
+ es.json
293
+ zu.json ← isiZulu
294
+ af.json ← Afrikaans
295
+ ```
296
+
297
+ ## Using t in JSX views
298
+
299
+ ```tsx
300
+ // app/views/PostCard.tsx
301
+ import { t } from "@zerotal/i18n";
302
+
303
+ export function PostCard({ post }: { post: Post }) {
304
+ return (
305
+ <div>
306
+ <h2>{post.title}</h2>
307
+ <p>{t("posts.count", { count: post.commentCount })}</p>
308
+ <a href={`/posts/${post.slug}`}>{t("posts.readMore")}</a>
309
+ </div>
310
+ );
311
+ }
312
+ ```
313
+
314
+ `t()` reads the active locale from `I18nContext` (async local storage) — no props
315
+ threading needed.
316
+
317
+ ## Errors
318
+
319
+ `I18nError` (`E_I18N`) is the base; `CatalogLoadError` (`E_I18N_CATALOG_LOAD`) is
320
+ thrown when a catalog file exists but contains malformed JSON. Both extend
321
+ `ZerotalError`.
322
+
323
+ ## Testing
324
+
325
+ Set your suite up once as described in [Testing](/docs/testing). Translation
326
+ tests are cheap, and the two worth writing are the ones that catch a missing
327
+ string before a user does.
328
+
329
+ **Assert the resolved string, not the key.** A test that checks `t("cart.empty")
330
+ === "cart.empty"` passes when the catalogue is missing entirely:
331
+
332
+ ```typescript
333
+ // tests/i18n/catalogues.test.ts
334
+ import { test, expect } from "bun:test";
335
+ import { t } from "@zerotal/i18n";
336
+
337
+ test("renders the French cart message", () => {
338
+ expect(t("cart.empty", {}, "fr")).toBe("Votre panier est vide");
339
+ });
340
+
341
+ test("substitutes replacements", () => {
342
+ expect(t("cart.count", { n: 3 }, "fr")).toContain("3");
343
+ });
344
+ ```
345
+
346
+ **Test that every locale has every key.** This is the test that earns its place —
347
+ it fails the moment someone adds an English string and forgets the translation,
348
+ which is otherwise found in production by a French speaker:
349
+
350
+ ```typescript
351
+ // tests/i18n/parity.test.ts
352
+ import { test, expect } from "bun:test";
353
+ import { loadCatalogs } from "@zerotal/i18n";
354
+
355
+ test("every locale defines the same keys as English", async () => {
356
+ const catalogs = await loadCatalogs("./resources/lang");
357
+ const english = Object.keys(catalogs.en).sort();
358
+
359
+ for (const [locale, messages] of Object.entries(catalogs)) {
360
+ expect(Object.keys(messages).sort(), `locale: ${locale}`).toEqual(english);
361
+ }
362
+ });
363
+ ```
364
+
365
+ **Locale resolution is a separate concern** from translation, and fails
366
+ separately — a correct catalogue served under the wrong locale looks like a
367
+ missing translation:
368
+
369
+ ```typescript
370
+ // tests/http/locale.test.ts
371
+ const res = await app.get("/", { "Accept-Language": "fr-CA,fr;q=0.9" });
372
+
373
+ res.assertSee("Votre panier est vide");
374
+ ```
375
+
376
+ > **Note** — `resolveLocale` and `parseAcceptLanguage` are exported and pure, so
377
+ > a header you are unsure about can be checked directly rather than through a
378
+ > request.
379
+
380
+ ## References
381
+
382
+ **Request context** — added by `LocaleMiddleware`:
383
+
384
+ | Member | Signature | Description |
385
+ | ------------ | ---------------------------------------------------------------------- | ------------------------------------- |
386
+ | `ctx.locale` | `string` | The locale resolved for this request. |
387
+ | `ctx.t()` | `t(key: string, replacements?: Replacements, locale?: string): string` | Translate using the request locale. |
388
+
389
+ **`Lang` facade** — the `i18n` binding (a `Translator`):
390
+
391
+ | Method | Signature | Description |
392
+ | ----------------- | --------------------------------------------------------------------- | -------------------------------------------------------- |
393
+ | `Lang.translate` | `(key: string, replacements?: Replacements, locale?: string): string` | Translate a key; a missing key returns the key itself. |
394
+ | `Lang.has` | `(key: string, locale?: string): boolean` | Whether the key exists in the active or fallback locale. |
395
+ | `Lang.locales` | `string[]` | Loaded locales. |
396
+ | `Lang.addCatalog` | `(locale: string, messages: Messages): void` | Merge messages into a locale (tooling / tests). |
397
+
398
+ **Helpers & context** — importable from `@zerotal/i18n`:
399
+
400
+ | Export | Signature | Description |
401
+ | --------------------- | --------------------------------------------------------------------- | ------------------------------------------------ |
402
+ | `t` | `(key: string, replacements?: Replacements, locale?: string): string` | Global translate; reads the active locale. |
403
+ | `I18nContext.run` | `<T>(locale: string, cb: () => T): T` | Run `cb` with `locale` active. |
404
+ | `I18nContext.current` | `(): string \| undefined` | Active locale, or `undefined` outside a request. |
405
+ | `resolveLocale` | `(request: Request, config: I18nConfigShape): string` | Resolve a request's locale per config. |
406
+
407
+ See [Configuration](#configuration) for the `config/i18n.ts` fields.
408
+
409
+ ## Next steps
410
+
411
+ - [Validator](/docs/validator) — pair form validation with a translated `validation` namespace.
412
+ - [Middleware](/docs/middleware) — how `LocaleMiddleware` resolves the request locale.
413
+ - [Cookies](/docs/cookies) — persist a visitor's locale choice.
414
+ - [View](/docs/view) — use `t()` inside server-rendered JSX.
package/docs/index.md ADDED
@@ -0,0 +1,65 @@
1
+ ---
2
+ title: Documentation
3
+ description: Start here — installation, the ideas behind Zerotal, and a map of every guide in the documentation.
4
+ ---
5
+
6
+ # Zerotal Documentation
7
+
8
+ Zerotal is a Bun-native, full-stack TypeScript framework. You write plain classes,
9
+ drop them in convention-named folders, and the framework wires them together at
10
+ boot — no build step, no manual registration.
11
+
12
+ ## New here?
13
+
14
+ Three pages, in order, and you'll be productive:
15
+
16
+ 1. **[Getting Started](/docs/getting-started)** — scaffold a project, run it, add a
17
+ route, a model, and a [service provider](/docs/providers). Fifteen minutes.
18
+ 2. **[About Zerotal](/docs/about)** — the whole framework on one screen: the four
19
+ ideas it rests on, and a runnable example of every major feature.
20
+ 3. **[Conventions](/docs/conventions)** — what gets auto-discovered, and the naming
21
+ rules that make it happen. This is the page that stops the surprises.
22
+
23
+ ```bash
24
+ # in your project's parent directory
25
+ bun create zerotal my-app
26
+ cd my-app && bun dev
27
+ ```
28
+
29
+ ## Find your way around
30
+
31
+ | If you want to… | Read |
32
+ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
33
+ | Handle a request | [Routing](/docs/routing) · [Controllers](/docs/controllers) · [Middleware](/docs/middleware) · [Requests Context](/docs/context) |
34
+ | Talk to a database | [Database](/docs/database) · [ORM](/docs/orm) · [Migrations](/docs/migrations) · [Query Builder](/docs/query-builder) |
35
+ | Build a UI | [Views](/docs/view) · [Flow](/docs/flow) · [Inertia](/docs/inertia) · [Assets](/docs/assets) |
36
+ | Sign users in | [Authentication](/docs/authentication) · [Authorization](/docs/authorization) · [Roles & 2FA](/docs/roles-and-2fa) · [Social Login](/docs/social) |
37
+ | Do work in the background | [Queue](/docs/queue) · [Scheduler](/docs/scheduler) · [Broadcasting](/docs/broadcasting) |
38
+ | Understand how the framework boots | [The Application](/docs/application) · [Lifecycle](/docs/lifecycle) · [Container](/docs/container) · [Providers](/docs/providers) |
39
+ | Test what you built | [Testing](/docs/testing) · [HTTP Tests](/docs/testing/http) · [Database Tests](/docs/testing/database) |
40
+ | See what's running in production | [Logger](/docs/logger) · [Monitor](/docs/monitor) · [Telemetry](/docs/telemetry) · [Health](/docs/health) |
41
+ | Debug what it just did | [DevTools](/docs/devtools) · [Errors](/docs/errors) |
42
+ | Work with a coding agent | [Agent Surface](/docs/arch) |
43
+ | Ship it | [Deployment](/docs/deployment) · [Commands](/docs/commands) |
44
+
45
+ ## Choosing a frontend
46
+
47
+ Zerotal doesn't pick for you, and the choice is per project rather than per
48
+ framework:
49
+
50
+ - **[Views](/docs/view)** — server-rendered JSX. No client runtime at all. Right for
51
+ content sites, forms, and anything that doesn't need live updates.
52
+ - **[Flow](/docs/flow)** — reactive components rendered on the server, updated
53
+ over a WebSocket. You write TypeScript classes, not client state. Right when you
54
+ want interactivity without a separate frontend codebase.
55
+ - **[Inertia](/docs/inertia)** — a React or Vue SPA that talks to your controllers
56
+ directly, with no API layer to maintain. Right when you already know React or Vue
57
+ and want the full client-side experience.
58
+
59
+ ## Reference
60
+
61
+ - **[API Reference](/docs/api)** — generated from source, every exported symbol.
62
+ - **[Commands](/docs/commands)** — every `bun zt` command.
63
+ - **[Release Notes](/docs/changelog)** · **[Upgrade Guide](/docs/upgrade)**
64
+ - **[Package Development](/docs/package-development)** — build your own `@zerotal`-style package.
65
+ - **[Contribution Guide](/docs/contributing)** · **[Inspirations](/docs/inspirations)**
@@ -0,0 +1,95 @@
1
+ ---
2
+ title: Inertia CLI & Build
3
+ description: The page registry, the bundler pipeline, and building for production.
4
+ ---
5
+
6
+ # CLI & build
7
+
8
+ `@zerotal/inertia` ships two commands — one to scaffold page components, one to
9
+ bundle them — plus the page registry that ties component names to files.
10
+
11
+ ## Generating a page
12
+
13
+ Scaffold a new page component under your pages directory (`resources/js/pages/` by default):
14
+
15
+ ```bash
16
+ # in your project root
17
+ bun zt make:page Dashboard
18
+ bun zt make:page Users/Index # nested — creates resources/js/pages/Users/Index.tsx
19
+ ```
20
+
21
+ The generated stub wires up `usePage()` for typed access to shared props and a couple
22
+ of starter links. The framework (React `.tsx` or Vue `.vue`) is auto-detected from
23
+ the Inertia adapter you installed; force it with `--framework vue` or `--framework react`.
24
+ After writing the file, `make:page` regenerates the [page registry](#page-registry)
25
+ automatically.
26
+
27
+ ### Persistent layouts — --layout
28
+
29
+ Pass `--layout` to wrap the page in an Inertia persistent layout (the layout mounts
30
+ once and survives client-side navigations; only the page content re-renders):
31
+
32
+ ```bash
33
+ # in your project root
34
+ bun zt make:page Settings --layout MainLayout
35
+ ```
36
+
37
+ This emits a page that assigns a `.layout` function and imports the layout from a
38
+ `layouts/` directory relative to the page. Adjust the import path if your layouts live
39
+ elsewhere — the generator prints a reminder.
40
+
41
+ | Argument / flag | Description |
42
+ | --------------- | ------------------------------------------------------------------ |
43
+ | `name` | Page name, e.g. `Dashboard` or `Users/Index` (required). |
44
+ | `--layout` | Wrap the page in a persistent layout component. |
45
+ | `--framework` | Force `vue` or `react` (auto-detected from the installed adapter). |
46
+
47
+ ## Building assets
48
+
49
+ Bundle the frontend with `Bun.build` and regenerate the page registry:
50
+
51
+ ```bash
52
+ # in your project root
53
+ bun zt inertia:build # development build (external source maps)
54
+ bun zt inertia:build -p # production build (minified, no source maps)
55
+ ```
56
+
57
+ The build:
58
+
59
+ - entrypoint `resources/js/app.tsx` → output `public/assets/`,
60
+ - targets the browser with **code splitting on** (each page becomes its own chunk
61
+ from the registry's dynamic imports, so navigation only loads what it needs),
62
+ - auto-detects and applies CSS (and Vue) plugins present in your project,
63
+ - prints a table of output files and sizes.
64
+
65
+ | Flag | Effect |
66
+ | -------------------- | --------------------------------------------------- |
67
+ | `-p`, `--production` | Minify and drop source maps for a production build. |
68
+
69
+ Pair `inertia:build -p` with a hashed [asset version](/docs/inertia/middleware#asset-versioning)
70
+ in your deploy so clients reload onto the new bundle.
71
+
72
+ ## Page registry
73
+
74
+ The page registry is a generated module (`resources/js/pages.generated.ts`) mapping each
75
+ component name (e.g. `"Users/Index"`) to a dynamic `import()` of its file. It's what lets
76
+ the client resolve `inertia("Users/Index")` to the right chunk, and what enables per-page
77
+ code splitting.
78
+
79
+ You rarely touch it directly — both `make:page` and `inertia:build` regenerate it.
80
+ To regenerate programmatically:
81
+
82
+ ```ts
83
+ // in a build script
84
+ import { generatePageRegistry } from "@zerotal/inertia";
85
+
86
+ await generatePageRegistry(process.cwd());
87
+ ```
88
+
89
+ Run it whenever you add or remove page files outside the generators (e.g. in a custom
90
+ build script) so the registry stays in sync with your pages directory.
91
+
92
+ ## Next steps
93
+
94
+ - [Inertia overview](/docs/inertia) — the guide's front page and the rest of the sections.
95
+ - [Reference](/docs/inertia/references) — the full API surface in one table.