@zerotal/arch 1.7.3 → 1.7.5

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 (127) hide show
  1. package/CHANGELOG.md +34 -2
  2. package/README.md +4 -2
  3. package/api-surface.md +4 -4
  4. package/docs/about.md +16 -16
  5. package/docs/admin/actions.md +9 -9
  6. package/docs/admin/auth.md +2 -2
  7. package/docs/admin/dashboard.md +5 -5
  8. package/docs/admin/extending-ui.md +12 -12
  9. package/docs/admin/extending.md +16 -7
  10. package/docs/admin/forms.md +4 -4
  11. package/docs/admin/index.md +2 -2
  12. package/docs/admin/operations.md +9 -9
  13. package/docs/admin/resources.md +3 -3
  14. package/docs/admin/structure.md +6 -6
  15. package/docs/admin/tables.md +8 -8
  16. package/docs/admin/testing.md +4 -4
  17. package/docs/ai.md +118 -11
  18. package/docs/application.md +12 -12
  19. package/docs/arch.md +25 -3
  20. package/docs/assets.md +11 -11
  21. package/docs/audit.md +13 -13
  22. package/docs/authentication.md +39 -39
  23. package/docs/authorization.md +13 -13
  24. package/docs/broadcasting/channels.md +2 -2
  25. package/docs/broadcasting/client.md +2 -2
  26. package/docs/broadcasting/events.md +9 -10
  27. package/docs/broadcasting/index.md +1 -1
  28. package/docs/broadcasting/testing.md +5 -5
  29. package/docs/cache.md +15 -13
  30. package/docs/carbon.md +35 -35
  31. package/docs/changelog.md +165 -1
  32. package/docs/client/index.md +277 -70
  33. package/docs/commands.md +4 -4
  34. package/docs/components.md +116 -115
  35. package/docs/config-system.md +4 -4
  36. package/docs/container.md +27 -27
  37. package/docs/context.md +27 -27
  38. package/docs/contributing.md +46 -0
  39. package/docs/controllers.md +10 -10
  40. package/docs/conventions.md +23 -15
  41. package/docs/cookies.md +3 -3
  42. package/docs/csrf.md +6 -6
  43. package/docs/database.md +22 -17
  44. package/docs/deployment.md +2 -2
  45. package/docs/devtools.md +16 -16
  46. package/docs/email-verification.md +8 -8
  47. package/docs/encryption.md +7 -7
  48. package/docs/errors.md +6 -6
  49. package/docs/events.md +12 -11
  50. package/docs/flow/components.md +32 -32
  51. package/docs/flow/decorators.md +18 -18
  52. package/docs/flow/events.md +16 -16
  53. package/docs/flow/forms.md +17 -17
  54. package/docs/flow/icons.md +199 -0
  55. package/docs/flow/index.md +20 -20
  56. package/docs/flow/layouts.md +35 -35
  57. package/docs/flow/lifecycle.md +16 -16
  58. package/docs/flow/models.md +9 -9
  59. package/docs/flow/pagination.md +8 -8
  60. package/docs/flow/performance.md +7 -7
  61. package/docs/flow/references.md +3 -3
  62. package/docs/flow/routing.md +26 -26
  63. package/docs/flow/testing.md +20 -20
  64. package/docs/getting-started.md +18 -13
  65. package/docs/health.md +17 -8
  66. package/docs/helpers.md +17 -17
  67. package/docs/i18n.md +10 -10
  68. package/docs/inertia/devtools.md +4 -4
  69. package/docs/inertia/index.md +12 -7
  70. package/docs/inertia/props.md +22 -22
  71. package/docs/inertia/rendering.md +86 -7
  72. package/docs/inertia/ssr.md +2 -2
  73. package/docs/lifecycle.md +2 -2
  74. package/docs/lock.md +11 -11
  75. package/docs/logger.md +12 -12
  76. package/docs/media.md +22 -22
  77. package/docs/middleware.md +17 -17
  78. package/docs/migrations.md +13 -13
  79. package/docs/monitor.md +6 -6
  80. package/docs/notifications.md +24 -24
  81. package/docs/orm/casts.md +14 -14
  82. package/docs/orm/factories.md +8 -8
  83. package/docs/orm/index.md +22 -20
  84. package/docs/orm/lifecycle.md +13 -13
  85. package/docs/orm/queries.md +28 -28
  86. package/docs/orm/relationships.md +16 -16
  87. package/docs/orm/serialization.md +11 -11
  88. package/docs/package-development.md +10 -10
  89. package/docs/pagination.md +14 -14
  90. package/docs/password-reset.md +6 -6
  91. package/docs/providers.md +11 -11
  92. package/docs/query-builder.md +18 -18
  93. package/docs/queue.md +14 -12
  94. package/docs/rate-limiting.md +6 -6
  95. package/docs/responses.md +14 -14
  96. package/docs/roles-and-2fa.md +9 -9
  97. package/docs/routing.md +44 -44
  98. package/docs/scaffolding.md +1 -1
  99. package/docs/scheduler.md +10 -10
  100. package/docs/seeding.md +6 -6
  101. package/docs/session.md +16 -14
  102. package/docs/social.md +14 -14
  103. package/docs/storage.md +25 -13
  104. package/docs/structure.md +2 -2
  105. package/docs/support-policy.md +36 -8
  106. package/docs/telemetry.md +12 -12
  107. package/docs/tenancy.md +34 -40
  108. package/docs/testing/browser.md +6 -6
  109. package/docs/testing/console.md +3 -3
  110. package/docs/testing/database.md +10 -10
  111. package/docs/testing/flow-browser.md +6 -6
  112. package/docs/testing/http.md +12 -12
  113. package/docs/testing/index.md +3 -3
  114. package/docs/testing/mocking.md +8 -8
  115. package/docs/upgrade.md +1 -1
  116. package/docs/validator.md +19 -19
  117. package/docs/view.md +14 -14
  118. package/package.json +4 -4
  119. package/src/index.ts +25 -1
  120. package/src/tools/searchDocs.ts +7 -0
  121. package/docs/client/auth.md +0 -113
  122. package/docs/client/errors.md +0 -139
  123. package/docs/client/files.md +0 -118
  124. package/docs/client/references.md +0 -58
  125. package/docs/client/requests.md +0 -131
  126. package/docs/client/resilience.md +0 -141
  127. package/docs/client/testing.md +0 -146
@@ -1,35 +1,39 @@
1
1
  ---
2
- title: API Client
3
- description: Call your HTTP API with full TypeScript inference of params, body, query, and response shapes.
2
+ title: HTTP Client
3
+ description: Call another service from your app requests, auth, timeouts, retries, errors, and a circuit breaker.
4
4
  ---
5
5
 
6
- # API Client
6
+ # HTTP Client
7
7
 
8
- A type-safe HTTP client bound to a route map. The TypeScript compiler infers
9
- request body types, path parameters, query parameters, and response shapes directly
10
- from the map no casting required.
8
+ Your application calling somebody else's: charging a card, sending a message,
9
+ looking up an address. `@zerotal/client` is the outbound HTTP client — a thin,
10
+ expressive wrapper over `fetch` that adds what every real integration ends up
11
+ needing anyway and that is tedious to get right by hand.
11
12
 
12
- This is the **frontend/SPA** client for calling _your own_ typed API. For
13
- **server-to-server** outgoing HTTP from inside your app, reach for core's `Http`
14
- facade ([HTTP Client helpers](/docs/context)) instead — it's the fluent,
15
- fakeable, server-side HTTP client. Same protocol, different jobs.
13
+ ```ts fragment
14
+ import { Client } from "@zerotal/client";
15
+
16
+ const charge = await Client.post("https://api.stripe.com/v1/charges", {
17
+ amount: 2000,
18
+ currency: "usd",
19
+ source: token,
20
+ });
21
+ ```
22
+
23
+ Timeouts, retries with backoff, bearer tokens, a circuit breaker for the upstream
24
+ that has started failing, and errors you can branch on.
25
+
26
+ > **For requests coming _into_ your app**, see [Routing](/docs/routing). This page is
27
+ > only about requests going out.
16
28
 
17
- ## Getting Started
29
+ ## Install and register
18
30
 
19
31
  ```bash
20
- # in your project root
21
32
  bun add @zerotal/client
22
33
  ```
23
34
 
24
- You can use the client two ways: create instances yourself with `createApiClient()`
25
- (typical for the browser/frontend), or register `ClientProvider` to resolve a shared,
26
- config-driven client from the container (typical for server-side and console code).
27
-
28
- ## Register the provider
29
-
30
- Registering the provider is optional — it gives you one container-bound `client`
31
- built from `config/client.ts`. Add `ClientProvider` to the providers array in
32
- `bootstrap/providers.ts`:
35
+ Add `ClientProvider` to `bootstrap/providers.ts` to get one shared, configured
36
+ client from the container:
33
37
 
34
38
  ```ts
35
39
  // bootstrap/providers.ts
@@ -43,26 +47,68 @@ const providers = [
43
47
  export default providers;
44
48
  ```
45
49
 
46
- Registering the provider switches on the following:
50
+ It binds a client under the `client` key in the `web`, `console`, `worker`, `test`
51
+ and `repl` environments — so a queued job calling a gateway uses the same client a
52
+ controller does — and exposes it as the `Client` facade.
47
53
 
48
- - `onRegister` — binds an `ApiClient` as a lazy singleton under the `client` key, built from the `client` config namespace.
49
- - `onBooted` — pre-resolves that async singleton so the `Client` facade can be accessed synchronously after boot.
54
+ ## Making requests
50
55
 
51
- The provider is active in the `web`, `console`, `worker`, `test`, and `repl`
52
- environments. Resolve the shared client via the container or the `Client` facade:
56
+ Five methods, each taking a URL. Pass an absolute URL, or set a `baseUrl` in config
57
+ and pass a path:
53
58
 
54
59
  ```ts
55
- // in a controller or service
56
60
  import { Client } from "@zerotal/client";
57
61
 
58
- const users = await Client.get("/api/users");
62
+ await Client.get("https://api.example.com/v1/invoices");
63
+ await Client.post("https://api.example.com/v1/invoices", { customer: "cus_123" });
64
+ await Client.put("https://api.example.com/v1/invoices/in_9", { memo: "Paid" });
65
+ await Client.patch("https://api.example.com/v1/invoices/in_9", { memo: "Paid" });
66
+ await Client.delete("https://api.example.com/v1/invoices/in_9");
67
+ ```
68
+
69
+ The response body is decoded for you — JSON when the response is JSON, text
70
+ otherwise. A 2xx returns; anything else throws, which [Errors](#errors) covers.
71
+
72
+ ### Query parameters
73
+
74
+ ```ts fragment
75
+ await Client.get("https://api.example.com/v1/charges", undefined, {
76
+ query: { limit: 25, status: "succeeded", created: { gte: 1_700_000_000 } },
77
+ });
78
+ ```
79
+
80
+ Nested objects and arrays are serialized with bracket notation, so
81
+ `{ created: { gte: 1 } }` becomes `created[gte]=1`.
82
+
83
+ ### Request bodies
84
+
85
+ A plain object or array is JSON-encoded with the matching `Content-Type`. Anything
86
+ `fetch` understands natively — `FormData`, `URLSearchParams`, `Blob`, `ArrayBuffer`,
87
+ a raw string — passes straight through, so the runtime sets the header itself
88
+ (including multipart boundaries):
89
+
90
+ ```ts fragment
91
+ // Form-encoded, which several gateways still require
92
+ await Client.post(
93
+ "https://api.example.com/v1/charges",
94
+ new URLSearchParams({ amount: "2000", currency: "usd" }),
95
+ );
96
+ ```
97
+
98
+ ### Headers
99
+
100
+ Per request, merged over the client's defaults:
101
+
102
+ ```ts fragment
103
+ await Client.post("https://api.example.com/v1/messages", payload, {
104
+ headers: { "Idempotency-Key": crypto.randomUUID() },
105
+ });
59
106
  ```
60
107
 
61
- ## Configuration
108
+ ## Authentication
62
109
 
63
- Create `config/client.ts` with the `ClientConfig()` helper (or `satisfies
64
- ClientConfigShape`) so every field stays type-checked. The shape extends
65
- [`ApiClientConfig`](/docs/client/references) — every client option below is valid here:
110
+ Most APIs want a bearer token. Set it once and every request carries
111
+ `Authorization: Bearer …`:
66
112
 
67
113
  ```ts
68
114
  // config/client.ts
@@ -70,59 +116,220 @@ import { ClientConfig } from "@zerotal/client";
70
116
  import { env } from "zerotal";
71
117
 
72
118
  export default ClientConfig({
73
- baseUrl: env("API_BASE_URL", "https://api.example.com"),
119
+ baseUrl: "https://api.example.com",
120
+ token: env("PAYMENTS_API_KEY", ""),
74
121
  headers: { Accept: "application/json" },
75
122
  });
76
123
  ```
77
124
 
78
- | Field | Required | Default | Description |
79
- | ----------------- | -------- | ------- | -------------------------------------------------------------- |
80
- | `baseUrl` | no | `""` | Base URL prepended to every request path. |
81
- | `headers` | no | `{}` | Default headers sent with every request. |
82
- | `token` | no | — | Bearer token (string or resolver) attached as `Authorization`. |
83
- | `withCredentials` | no | `false` | Send cookies (`credentials: 'include'`); also turns CSRF on. |
84
- | `csrf` | no | — | CSRF cookie/header names, or a boolean to force on/off. |
85
- | `timeout` | no | — | Default per-request timeout in ms (`0`/omitted = none). |
86
- | `retry` | no | — | Default retry policy (number of attempts or `RetryOptions`). |
125
+ A `token` may also be a function, including an async one, which is how credentials
126
+ that expire are handled it is resolved per request:
127
+
128
+ ```ts fragment
129
+ export default ClientConfig({
130
+ token: async () => await currentAccessToken(),
131
+ });
132
+ ```
133
+
134
+ Change it at runtime with `setToken(token)`, or pass `null` to clear it. Its type is
135
+ `TokenSource`.
136
+
137
+ For an API that wants something other than a bearer token, set the header directly:
138
+
139
+ ```ts fragment
140
+ export default ClientConfig({
141
+ headers: { "X-Api-Key": env("PARTNER_API_KEY", "") },
142
+ });
143
+ ```
144
+
145
+ ### Refreshing on a 401
146
+
147
+ `onUnauthorized` receives the error and a `retry` function, so a token can be
148
+ refreshed and the original request replayed once:
149
+
150
+ ```ts fragment
151
+ export default ClientConfig({
152
+ onUnauthorized: async (error, retry) => retry({ Authorization: `Bearer ${await refresh()}` }),
153
+ });
154
+ ```
155
+
156
+ ## Timeouts
157
+
158
+ There is no timeout by default, because the right one depends on the upstream. Set a
159
+ default and override per request:
160
+
161
+ ```ts fragment
162
+ export default ClientConfig({ timeout: 10_000 });
163
+
164
+ // This one is slow and we accept that
165
+ await Client.post("https://api.example.com/v1/reports", body, { timeout: 60_000 });
166
+ ```
167
+
168
+ `timeout: 0` disables it. Pass your own `signal` to cancel for any other reason; it
169
+ is combined with the timeout rather than replacing it.
87
170
 
88
- > **Note** — The provider carries no framework-level defaults of its own; `ApiClient`
89
- > applies its own internal defaults. The config file is just typed input.
171
+ ## Retries
90
172
 
91
- ## Create a client directly
173
+ A network blip or a `503` is worth trying again; a `422` never is. `retry` retries
174
+ idempotent requests on network errors, 5xx and 429 with exponential backoff, and
175
+ honours a `Retry-After` header when the server sends one:
92
176
 
93
- In the browser (or anywhere you want a standalone instance), build a client with
94
- `createApiClient<Routes>()`:
177
+ ```ts fragment
178
+ export default ClientConfig({ retry: 2 });
179
+
180
+ await Client.post("https://api.example.com/v1/charges", body, { retry: false });
181
+ ```
182
+
183
+ Turning retries **off** for a charge is the deliberate part: retrying a payment you
184
+ cannot prove failed is how a customer gets billed twice. Send an idempotency key if
185
+ you want both. Pass `RetryOptions` instead of a number to tune the delays and which
186
+ statuses qualify.
187
+
188
+ ## Errors
189
+
190
+ A non-2xx throws `ApiClientError`, carrying what you need to decide what happened:
191
+
192
+ ```ts fragment
193
+ import { ApiClientError } from "@zerotal/client";
194
+
195
+ try {
196
+ await Client.post("https://api.example.com/v1/charges", body);
197
+ } catch (error) {
198
+ if (error instanceof ApiClientError) {
199
+ error.status; // 402
200
+ error.body; // raw response text
201
+ error.headers; // Headers, when available
202
+ error.retryAfterMs; // parsed Retry-After, or null
203
+ }
204
+ throw error;
205
+ }
206
+ ```
207
+
208
+ A `422` carrying a field-error body throws `ValidationError`, a subclass that reads
209
+ those fields for you — `has(field)`, `first(field)`, `all()`, `fields()`, and
210
+ `validationMessage` for the top-level message.
211
+
212
+ `onError` fires for every non-2xx before the throw and `onForbidden` for a 403 —
213
+ both good places to log or alert without wrapping each call site.
214
+
215
+ ## Circuit breaker
216
+
217
+ When an upstream is down, continuing to call it wastes your own capacity and slows
218
+ everything queued behind it. A `CircuitBreaker` stops after a threshold of
219
+ consecutive failures, fails fast for a cooldown, then lets a single request through
220
+ to test the water:
221
+
222
+ ```ts fragment
223
+ export default ClientConfig({
224
+ circuitBreaker: { threshold: 5, cooldownMs: 30_000 },
225
+ });
226
+ ```
227
+
228
+ While it is open, calls throw `CircuitBreakerOpenError` **without** a request being
229
+ made — catch that specifically to fall back to a cached answer or a queued retry.
230
+ `state` reports the `CircuitState` (`closed`, `open`, `half-open`), `failures` the
231
+ current count, and `reset()` closes it by hand.
232
+
233
+ Pass a shared `CircuitBreaker` instance instead of options when several clients talk
234
+ to the same upstream and should trip together.
235
+
236
+ ## Files
237
+
238
+ Upload with `FormData`; download by asking for the body you want:
239
+
240
+ ```ts fragment
241
+ const form = new FormData();
242
+ form.append("file", Bun.file("./invoice.pdf"));
243
+ await Client.post("https://api.example.com/v1/documents", form);
244
+
245
+ const pdf = await Client.get("https://api.example.com/v1/documents/doc_1", undefined, {
246
+ responseType: "blob",
247
+ });
248
+ ```
249
+
250
+ `responseType` takes `auto` (the default), `json`, `text`, `blob` or `arrayBuffer`.
251
+
252
+ ## Interceptors
253
+
254
+ `onRequest` runs before every request and can rewrite the outgoing `RequestConfig`;
255
+ `onResponse` runs after every 2xx with the `ResponseContext`. Both take one function
256
+ or an array:
257
+
258
+ ```ts fragment
259
+ export default ClientConfig({
260
+ onRequest: (config) => {
261
+ config.headers["X-Request-Id"] = crypto.randomUUID();
262
+ return config;
263
+ },
264
+ });
265
+ ```
266
+
267
+ To read the status or headers of a single response without a global interceptor,
268
+ pass `meta` in the request options.
269
+
270
+ ## Testing
271
+
272
+ The client calls `fetch`, so a test replaces `fetch`:
95
273
 
96
274
  ```ts
97
- // app/api/client.ts
98
- import { createApiClient } from "@zerotal/client";
99
- import { env } from "zerotal";
100
- import type { Routes } from "./api-types.ts";
275
+ import { test, expect } from "bun:test";
101
276
 
102
- export const api = createApiClient<Routes>({
103
- baseUrl: env("API_BASE_URL", "https://api.example.com"),
104
- headers: { Accept: "application/json" },
277
+ test("charges the card", async () => {
278
+ globalThis.fetch = async () => Response.json({ id: "ch_1", paid: true });
279
+
280
+ const charge = await Client.post("https://api.example.com/v1/charges", { amount: 2000 });
281
+ expect(charge.paid).toBe(true);
105
282
  });
106
283
  ```
107
284
 
108
- > **Tip** Don't instantiate `ApiClient` directly; `createApiClient<Routes>(config)`
109
- > binds the route map type for you.
285
+ Return a non-2xx `Response` to cover the failure paths, and throw from the stub to
286
+ exercise retries and the breaker. For requests your app _receives_ in tests, see
287
+ [Testing](/docs/testing).
288
+
289
+ ## Configuration reference
290
+
291
+ Every field of `ClientConfigShape` (which extends `ApiClientConfig`) is optional:
292
+
293
+ | Field | Description |
294
+ | ----------------- | --------------------------------------------------------------------------- |
295
+ | `baseUrl` | Prepended to every request path. Omit it and pass absolute URLs. |
296
+ | `headers` | Default headers for every request. |
297
+ | `token` | Bearer token — a string, or a (possibly async) resolver called per request. |
298
+ | `timeout` | Default per-request timeout in ms. `0` disables. |
299
+ | `retry` | Default retry policy — a count, or `RetryOptions`. |
300
+ | `circuitBreaker` | A `CircuitBreaker` instance to share, or options for a dedicated one. |
301
+ | `withCredentials` | Send cookies (`credentials: 'include'`), and turn CSRF on. |
302
+ | `csrf` | CSRF cookie/header names, or a boolean. |
303
+ | `onRequest` | Interceptor(s) run before every request. |
304
+ | `onResponse` | Interceptor(s) run after every 2xx. |
305
+ | `onError` | Called for every non-2xx before the error is thrown. |
306
+ | `onUnauthorized` | Called on 401, with a `retry` function. |
307
+ | `onForbidden` | Called on 403. |
308
+
309
+ Per-request options — `RequestOptions`, plus `query` on `GetOptions` and
310
+ `params`/`query` on `MutationOptions` — override the client's defaults: `headers`,
311
+ `timeout`, `retry`, `responseType`, `signal`, `init` and `meta`.
312
+
313
+ ## Calling your own API
314
+
315
+ Everything above treats a URL as a string, which is the right trade for a service
316
+ you hit a handful of endpoints on. Nobody should describe someone else's API in
317
+ types to send three requests to it.
318
+
319
+ If you are calling **your own** API repeatedly, `createApiClient<Routes>(config)`
320
+ binds a route map, so paths, params, query and response types are inferred and a
321
+ renamed endpoint becomes a build error. It returns the same `ApiClient` with a
322
+ narrower type; the `Client` facade above is bound to the base map and answers
323
+ `unknown`. The route-map types are listed in the [API reference](/docs/api).
110
324
 
111
- ## The rest of the guide
325
+ ## Realtime
112
326
 
113
- | Page | What it covers |
114
- | ------------------------------------------ | ------------------------------------------------------------------------------ |
115
- | [Requests](/docs/client/requests) | The typed route map, making requests, and shaping them with interceptors. |
116
- | [Authentication](/docs/client/auth) | Bearer tokens, CSRF, and refreshing credentials on a 401. |
117
- | [Error Handling](/docs/client/errors) | What a failed request throws, and how to tell the failure modes apart. |
118
- | [Resilience](/docs/client/resilience) | Timeouts, retries, and the circuit breaker that spares a failing upstream. |
119
- | [File Transfers](/docs/client/files) | Uploading and downloading binary payloads. |
120
- | [Testing the Client](/docs/client/testing) | Stub the global fetch, cover the failure paths, and drive the circuit breaker. |
121
- | [References](/docs/client/references) | Every ApiClient method, config key, and error type in one table. |
327
+ This package also ships `Socket`, `Channel` and `PresenceChannel` — a WebSocket
328
+ client speaking Zerotal's broadcast protocol. Different job, documented where it is
329
+ used: [Broadcasting → Client](/docs/broadcasting/client).
122
330
 
123
331
  ## Next steps
124
332
 
125
- - [Rate Limiting](/docs/rate-limiting) — throttle outbound and inbound traffic.
126
- - [Authentication](/docs/authentication) — issue the tokens your interceptors attach.
127
- - [Validator](/docs/validator) — the server side that produces the `ValidationError` body.
128
- - [Telemetry](/docs/telemetry) — observe request failures and circuit state.
333
+ - [Rate Limiting](/docs/rate-limiting) — throttle what you send and what you accept.
334
+ - [Telemetry](/docs/telemetry) — trace outbound calls and watch circuit state.
335
+ - [Queues](/docs/queue) — where a slow or flaky integration usually belongs.
package/docs/commands.md CHANGED
@@ -72,7 +72,7 @@ signature's first token is the name; `{arg}` is required, `{arg?}` optional,
72
72
  `{arg=default}` has a default, `{--flag}` is a boolean flag, and `{--flag=}` /
73
73
  `{--flag=default}` is a string flag.
74
74
 
75
- ```typescript
75
+ ```typescript fragment
76
76
  // in a service provider or bootstrap script
77
77
  const runner = app.container.tryMake("commands");
78
78
 
@@ -110,7 +110,7 @@ so it wins a name collision. The directory is configurable via
110
110
 
111
111
  To register a folder from somewhere else, call `discover()` yourself:
112
112
 
113
- ```typescript
113
+ ```typescript fragment
114
114
  // in a service provider or bootstrap script
115
115
  await runner.discover("./vendor/acme/commands");
116
116
  ```
@@ -124,7 +124,7 @@ returns the list of registered names.
124
124
  The `Command` base class provides coloured output helpers and interactive
125
125
  prompts. The prompts read from stdin and only work on a real TTY:
126
126
 
127
- ```typescript
127
+ ```typescript fragment
128
128
  // inside a command's run()
129
129
  this.info("Success"); // green
130
130
  this.warn("Heads up"); // yellow
@@ -291,7 +291,7 @@ Dev processes
291
291
  Your app has the last word. `app.dev.disable` removes a process by name, and
292
292
  registering the same name again replaces it rather than adding a second tab:
293
293
 
294
- ```ts
294
+ ```ts fragment
295
295
  // config/app.ts
296
296
  export default AppConfig({
297
297
  dev: {