liaise 5.0.2 → 5.0.3
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 +36 -0
- package/MIGRATION.md +6 -0
- package/README.md +1463 -948
- package/dist/types.d.ts +29 -32
- package/package.json +4 -2
package/dist/types.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
|
43
43
|
* ceremony with no effect. Mismatch it and you get a wrong `TResponse`
|
|
44
44
|
* silently, same as always — declare it as `undefined`.
|
|
45
45
|
*
|
|
46
|
-
* This describes `new Request`'s behaviour specifically.
|
|
46
|
+
* This describes `new Request`'s behaviour specifically.
|
|
47
47
|
* `defineRequest` DOES enforce this pairing at compile time — see
|
|
48
48
|
* `EmptyBodyGuard` in `define-request.ts` — because a curried function,
|
|
49
49
|
* unlike a constructor, has no permissive overload for the guard to fall
|
|
@@ -178,7 +178,7 @@ export interface RequestConfig {
|
|
|
178
178
|
* Declaring `responseType: 'none'` alongside a schema is a contradiction —
|
|
179
179
|
* there is no body to validate, and every call will fail validation. It is
|
|
180
180
|
* not rejected at compile time because the runtime failure is immediate and
|
|
181
|
-
* loud
|
|
181
|
+
* loud.
|
|
182
182
|
*
|
|
183
183
|
* On this class path, `schema` and `TResponse` are also not tied together at
|
|
184
184
|
* compile time: `new Request<P, User>({ ..., schema: numberSchema })`
|
|
@@ -219,12 +219,14 @@ export interface RequestConfig {
|
|
|
219
219
|
* and an `'abort'` give-up does not (see {@link ApiConfig.onError}). A per-*request*
|
|
220
220
|
* {@link RequestConfig.timeout}, by contrast, bounds the shared request
|
|
221
221
|
* itself for everyone.
|
|
222
|
-
* A call whose params
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
222
|
+
* A call whose params cannot be compared by content is also never shared.
|
|
223
|
+
* That is, at any depth: a BigInt, a function or symbol, an `ArrayBuffer`,
|
|
224
|
+
* `Blob`, `FormData` or `URLSearchParams`, a boxed primitive, a circular
|
|
225
|
+
* structure, or a non-plain object with no enumerable keys (an `Error`, a
|
|
226
|
+
* class keeping its state in private fields). These are declined because
|
|
227
|
+
* their content can't be keyed reliably, and a wrong match would hand one
|
|
228
|
+
* caller the response to another's request. A string, `Date`, `Map`, `Set` or typed
|
|
229
|
+
* array is compared by content and shares normally.
|
|
228
230
|
*
|
|
229
231
|
* `result.retry()` on a shared result re-runs the pipeline using the
|
|
230
232
|
* **acquiring caller's** own per-call options (headers, signal, timeout) —
|
|
@@ -233,15 +235,6 @@ export interface RequestConfig {
|
|
|
233
235
|
* non-aborting sharer receiving the literal same `Result` object; it is
|
|
234
236
|
* unavoidable given that design, but worth knowing before relying on it.
|
|
235
237
|
*
|
|
236
|
-
* **Known limitation:** middleware (global or per-request) that replaces
|
|
237
|
-
* `ctx.request.signal` — see {@link MiddlewareContext.request.signal} — is
|
|
238
|
-
* re-merged with the dedupe signal under `dedupe: true`, but is **not**
|
|
239
|
-
* currently re-merged with the share refcount controller. Combining
|
|
240
|
-
* `share` with signal-replacing middleware means that middleware's signal,
|
|
241
|
-
* not the refcount, ends up controlling the shared request: one sharer's
|
|
242
|
-
* middleware-installed signal could cancel the request for every other
|
|
243
|
-
* sharer.
|
|
244
|
-
*
|
|
245
238
|
* @default false
|
|
246
239
|
*/
|
|
247
240
|
share?: boolean;
|
|
@@ -272,8 +265,7 @@ export interface RequestConfig {
|
|
|
272
265
|
* **This is a whole-operation deadline, not a per-attempt budget.** It covers
|
|
273
266
|
* the entire middleware chain including every retry and every backoff delay,
|
|
274
267
|
* so `timeout: 5000` with `retryMiddleware(3)` still means "an answer within
|
|
275
|
-
* 5 seconds" — not five seconds per attempt.
|
|
276
|
-
* axios, XHR and `got`, which apply timeouts per attempt.
|
|
268
|
+
* 5 seconds" — not five seconds per attempt.
|
|
277
269
|
*
|
|
278
270
|
* For a per-attempt budget, use a signal-replacing middleware placed inside
|
|
279
271
|
* the retry middleware instead:
|
|
@@ -313,8 +305,8 @@ export interface RequestConfig {
|
|
|
313
305
|
*/
|
|
314
306
|
export interface SuccessResult<TResponse> {
|
|
315
307
|
/**
|
|
316
|
-
* The parsed response data, narrowed by `error === null`.
|
|
317
|
-
*
|
|
308
|
+
* The parsed response data, narrowed by `error === null`. A 2xx that
|
|
309
|
+
* carries no body under `responseType: 'json'` is a `'parse'` error
|
|
318
310
|
* rather than a success, so this is not `null` for that case — declare
|
|
319
311
|
* `responseType: 'none'` on an endpoint that answers with no body. See the
|
|
320
312
|
* `responseType` reference.
|
|
@@ -340,7 +332,7 @@ export interface SuccessResult<TResponse> {
|
|
|
340
332
|
* A failed API call. `error` is populated and `data` is `null`.
|
|
341
333
|
*
|
|
342
334
|
* `response` is present for HTTP and parse failures (the server responded)
|
|
343
|
-
* and `null` for network failures, aborts and
|
|
335
|
+
* and `null` for network failures, aborts, timeouts and middleware errors. Check
|
|
344
336
|
* {@link ApiError.kind} to tell them apart.
|
|
345
337
|
*/
|
|
346
338
|
export interface ErrorResult<TResponse> {
|
|
@@ -403,7 +395,10 @@ export interface CallOptions {
|
|
|
403
395
|
* new reference that won't match the original.
|
|
404
396
|
*/
|
|
405
397
|
skipMiddleware?: Middleware[];
|
|
406
|
-
/**
|
|
398
|
+
/**
|
|
399
|
+
* Extra headers for this call. Each one replaces the client's and the
|
|
400
|
+
* endpoint's value for the same header; other headers are kept.
|
|
401
|
+
*/
|
|
407
402
|
headers?: HeadersInit;
|
|
408
403
|
/**
|
|
409
404
|
* An `AbortSignal` to cancel this request. When the signal fires,
|
|
@@ -425,8 +420,9 @@ export interface CallOptions {
|
|
|
425
420
|
* cannot outlast it.
|
|
426
421
|
*
|
|
427
422
|
* A fractional or out-of-range value is normalised rather than rejected:
|
|
428
|
-
* rounded down to whole milliseconds, clamped to the
|
|
429
|
-
* and treated as "no timeout" if it is `NaN` or
|
|
423
|
+
* rounded down to whole milliseconds with a 1 ms minimum, clamped to the
|
|
424
|
+
* platform timer ceiling, and treated as "no timeout" if it is `NaN` or
|
|
425
|
+
* non-positive.
|
|
430
426
|
*/
|
|
431
427
|
timeout?: number;
|
|
432
428
|
}
|
|
@@ -440,10 +436,9 @@ export interface CallOptions {
|
|
|
440
436
|
* Middleware can read and modify `ctx.request.headers` and `ctx.request.body`
|
|
441
437
|
* before calling `next()` — changes will propagate to the actual fetch call.
|
|
442
438
|
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
* complex generic inference issues and keeps middleware composable.
|
|
439
|
+
* Middleware is deliberately loosely typed: authors work with `unknown` and
|
|
440
|
+
* cast internally if they need specific types. This avoids complex generic
|
|
441
|
+
* inference issues and keeps middleware composable.
|
|
447
442
|
*/
|
|
448
443
|
export interface MiddlewareContext {
|
|
449
444
|
/** Mutable request details — middleware can modify headers and body. */
|
|
@@ -458,7 +453,7 @@ export interface MiddlewareContext {
|
|
|
458
453
|
params: unknown;
|
|
459
454
|
/** Merged headers — middleware can add/remove headers here. */
|
|
460
455
|
headers: Headers;
|
|
461
|
-
/** Serialized request body, or null
|
|
456
|
+
/** Serialized request body, or null when there is none. */
|
|
462
457
|
body: unknown | null;
|
|
463
458
|
/**
|
|
464
459
|
* The AbortSignal that will be handed to `fetch`.
|
|
@@ -469,7 +464,9 @@ export interface MiddlewareContext {
|
|
|
469
464
|
* middleware takes effect. Under `dedupe: true` the replacement is merged
|
|
470
465
|
* into the dedupe signal rather than discarded: the fetch is then
|
|
471
466
|
* cancelled by whichever fires first, the middleware's signal or a newer
|
|
472
|
-
* call superseding this one.
|
|
467
|
+
* call superseding this one. Under `share: true` the shared request's
|
|
468
|
+
* refcount signal is merged back in the same way, so the request is still
|
|
469
|
+
* cancelled once every sharer has given up.
|
|
473
470
|
*
|
|
474
471
|
* While middleware runs — before `next()` reaches the core fetch — this
|
|
475
472
|
* holds the caller's `CallOptions.signal` merged with the timeout signal
|
|
@@ -685,7 +682,7 @@ export interface OperationConfig {
|
|
|
685
682
|
*
|
|
686
683
|
* Unlike the REST side, a schema here does **not** supply the response type —
|
|
687
684
|
* `Operation`'s `TData` stays explicit, because only the REST pipeline has a
|
|
688
|
-
* factory that can infer it.
|
|
685
|
+
* factory that can infer it.
|
|
689
686
|
*/
|
|
690
687
|
schema?: StandardSchemaV1<unknown>;
|
|
691
688
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "liaise",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.3",
|
|
4
4
|
"description": "Type-safe API client for REST and GraphQL on standard fetch. Never throws. Zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -34,10 +34,11 @@
|
|
|
34
34
|
"test": "vitest",
|
|
35
35
|
"test:run": "vitest run",
|
|
36
36
|
"test:integration": "vitest run --config vitest.integration.ts",
|
|
37
|
+
"docs:check": "node scripts/check-readme.mjs",
|
|
37
38
|
"typecheck": "tsc --noEmit",
|
|
38
39
|
"test:types": "vitest run --typecheck",
|
|
39
40
|
"sessions": "test -f .claude/sessions.mjs && node .claude/sessions.mjs || echo \"npm run sessions is a local-only helper (gitignored .claude/); nothing to list in this clone\"",
|
|
40
|
-
"prepublishOnly": "npm run typecheck && npm run test:types && npm run test:run && npm run test:integration && npm run build && npm run size"
|
|
41
|
+
"prepublishOnly": "npm run typecheck && npm run test:types && npm run test:run && npm run test:integration && npm run docs:check && npm run build && npm run size"
|
|
41
42
|
},
|
|
42
43
|
"repository": {
|
|
43
44
|
"type": "git",
|
|
@@ -69,6 +70,7 @@
|
|
|
69
70
|
"http-client"
|
|
70
71
|
],
|
|
71
72
|
"devDependencies": {
|
|
73
|
+
"@tanstack/query-core": "5.104.1",
|
|
72
74
|
"@types/node": "^22.19.18",
|
|
73
75
|
"esbuild": "0.27.4",
|
|
74
76
|
"typescript": "~5.8.0",
|