@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.
- package/CHANGELOG.md +34 -2
- package/README.md +4 -2
- package/api-surface.md +4 -4
- package/docs/about.md +16 -16
- package/docs/admin/actions.md +9 -9
- package/docs/admin/auth.md +2 -2
- package/docs/admin/dashboard.md +5 -5
- package/docs/admin/extending-ui.md +12 -12
- package/docs/admin/extending.md +16 -7
- package/docs/admin/forms.md +4 -4
- package/docs/admin/index.md +2 -2
- package/docs/admin/operations.md +9 -9
- package/docs/admin/resources.md +3 -3
- package/docs/admin/structure.md +6 -6
- package/docs/admin/tables.md +8 -8
- package/docs/admin/testing.md +4 -4
- package/docs/ai.md +118 -11
- package/docs/application.md +12 -12
- package/docs/arch.md +25 -3
- package/docs/assets.md +11 -11
- package/docs/audit.md +13 -13
- package/docs/authentication.md +39 -39
- package/docs/authorization.md +13 -13
- package/docs/broadcasting/channels.md +2 -2
- package/docs/broadcasting/client.md +2 -2
- package/docs/broadcasting/events.md +9 -10
- package/docs/broadcasting/index.md +1 -1
- package/docs/broadcasting/testing.md +5 -5
- package/docs/cache.md +15 -13
- package/docs/carbon.md +35 -35
- package/docs/changelog.md +165 -1
- package/docs/client/index.md +277 -70
- package/docs/commands.md +4 -4
- package/docs/components.md +116 -115
- package/docs/config-system.md +4 -4
- package/docs/container.md +27 -27
- package/docs/context.md +27 -27
- package/docs/contributing.md +46 -0
- package/docs/controllers.md +10 -10
- package/docs/conventions.md +23 -15
- package/docs/cookies.md +3 -3
- package/docs/csrf.md +6 -6
- package/docs/database.md +22 -17
- package/docs/deployment.md +2 -2
- package/docs/devtools.md +16 -16
- package/docs/email-verification.md +8 -8
- package/docs/encryption.md +7 -7
- package/docs/errors.md +6 -6
- package/docs/events.md +12 -11
- package/docs/flow/components.md +32 -32
- package/docs/flow/decorators.md +18 -18
- package/docs/flow/events.md +16 -16
- package/docs/flow/forms.md +17 -17
- package/docs/flow/icons.md +199 -0
- package/docs/flow/index.md +20 -20
- package/docs/flow/layouts.md +35 -35
- package/docs/flow/lifecycle.md +16 -16
- package/docs/flow/models.md +9 -9
- package/docs/flow/pagination.md +8 -8
- package/docs/flow/performance.md +7 -7
- package/docs/flow/references.md +3 -3
- package/docs/flow/routing.md +26 -26
- package/docs/flow/testing.md +20 -20
- package/docs/getting-started.md +18 -13
- package/docs/health.md +17 -8
- package/docs/helpers.md +17 -17
- package/docs/i18n.md +10 -10
- package/docs/inertia/devtools.md +4 -4
- package/docs/inertia/index.md +12 -7
- package/docs/inertia/props.md +22 -22
- package/docs/inertia/rendering.md +86 -7
- package/docs/inertia/ssr.md +2 -2
- package/docs/lifecycle.md +2 -2
- package/docs/lock.md +11 -11
- package/docs/logger.md +12 -12
- package/docs/media.md +22 -22
- package/docs/middleware.md +17 -17
- package/docs/migrations.md +13 -13
- package/docs/monitor.md +6 -6
- package/docs/notifications.md +24 -24
- package/docs/orm/casts.md +14 -14
- package/docs/orm/factories.md +8 -8
- package/docs/orm/index.md +22 -20
- package/docs/orm/lifecycle.md +13 -13
- package/docs/orm/queries.md +28 -28
- package/docs/orm/relationships.md +16 -16
- package/docs/orm/serialization.md +11 -11
- package/docs/package-development.md +10 -10
- package/docs/pagination.md +14 -14
- package/docs/password-reset.md +6 -6
- package/docs/providers.md +11 -11
- package/docs/query-builder.md +18 -18
- package/docs/queue.md +14 -12
- package/docs/rate-limiting.md +6 -6
- package/docs/responses.md +14 -14
- package/docs/roles-and-2fa.md +9 -9
- package/docs/routing.md +44 -44
- package/docs/scaffolding.md +1 -1
- package/docs/scheduler.md +10 -10
- package/docs/seeding.md +6 -6
- package/docs/session.md +16 -14
- package/docs/social.md +14 -14
- package/docs/storage.md +25 -13
- package/docs/structure.md +2 -2
- package/docs/support-policy.md +36 -8
- package/docs/telemetry.md +12 -12
- package/docs/tenancy.md +34 -40
- package/docs/testing/browser.md +6 -6
- package/docs/testing/console.md +3 -3
- package/docs/testing/database.md +10 -10
- package/docs/testing/flow-browser.md +6 -6
- package/docs/testing/http.md +12 -12
- package/docs/testing/index.md +3 -3
- package/docs/testing/mocking.md +8 -8
- package/docs/upgrade.md +1 -1
- package/docs/validator.md +19 -19
- package/docs/view.md +14 -14
- package/package.json +4 -4
- package/src/index.ts +25 -1
- package/src/tools/searchDocs.ts +7 -0
- package/docs/client/auth.md +0 -113
- package/docs/client/errors.md +0 -139
- package/docs/client/files.md +0 -118
- package/docs/client/references.md +0 -58
- package/docs/client/requests.md +0 -131
- package/docs/client/resilience.md +0 -141
- package/docs/client/testing.md +0 -146
package/docs/client/index.md
CHANGED
|
@@ -1,35 +1,39 @@
|
|
|
1
1
|
---
|
|
2
|
-
title:
|
|
3
|
-
description: Call
|
|
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
|
-
#
|
|
6
|
+
# HTTP Client
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
- `onBooted` — pre-resolves that async singleton so the `Client` facade can be accessed synchronously after boot.
|
|
54
|
+
## Making requests
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
108
|
+
## Authentication
|
|
62
109
|
|
|
63
|
-
|
|
64
|
-
|
|
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:
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
89
|
-
> applies its own internal defaults. The config file is just typed input.
|
|
171
|
+
## Retries
|
|
90
172
|
|
|
91
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
##
|
|
325
|
+
## Realtime
|
|
112
326
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
126
|
-
- [
|
|
127
|
-
- [
|
|
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: {
|