@rig-ts/client 0.1.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.
@@ -0,0 +1,810 @@
1
+ //#region src/credential.d.ts
2
+ /**
3
+ * What authorizes each request.
4
+ *
5
+ * `apply` is handed the headers a request is about to go out with, rather than
6
+ * a value to return, because a credential that has to refresh first needs
7
+ * somewhere to await — and because a future one may want to set more than a
8
+ * single header.
9
+ */
10
+ type Credential = {
11
+ apply(headers: Headers, signal?: AbortSignal): void | Promise<void>;
12
+ };
13
+ /**
14
+ * A credential that can do something about a 401.
15
+ *
16
+ * The runtime asks once, and only ever once per call: a blind retry on 401 is a
17
+ * way to lock an account out with a wrong password. Answering `false` leaves the
18
+ * 401 as the answer, which is what a credential with nothing left to try should
19
+ * say — throwing would replace the server's refusal with the client's opinion
20
+ * of it.
21
+ */
22
+ type Reauthorizer = Credential & {
23
+ reauthorize(signal?: AbortSignal): Promise<boolean>;
24
+ };
25
+ /**
26
+ * A credential that needs the client it authorizes in order to work.
27
+ *
28
+ * `Session` is the one: refreshing is itself a call, and the only moment the
29
+ * runtime can be known is when the credential is installed. Kept structural so
30
+ * the runtime does not have to import the session, which would be a cycle.
31
+ */
32
+ type Bindable = {
33
+ bind(runtime: unknown): void;
34
+ };
35
+ /** Reports whether a credential can answer a 401 with something new. */
36
+ declare function isReauthorizer(c: Credential | undefined): c is Reauthorizer;
37
+ /**
38
+ * A bearer token that never changes.
39
+ *
40
+ * Right for a token from somewhere else — a test fixture, an environment
41
+ * variable, a token minted by the surrounding application. A token that expires
42
+ * wants a `Session`, which refreshes ahead of the expiry rather than discovering
43
+ * it through a failed request.
44
+ */
45
+ declare function staticToken(token: string): Credential;
46
+ /**
47
+ * An API key, presented the same way a token is.
48
+ *
49
+ * The server tells the two apart by what the value is, not by how it arrived, so
50
+ * this is a separate name only because a caller holding one should not have to
51
+ * know that. It is {@link staticToken} and not a copy of it: two bodies that
52
+ * have to stay identical is how one of them eventually does not, and there is
53
+ * nothing here that could correctly differ — the day a key travels somewhere a
54
+ * token does not, this stops being an alias and starts being a function.
55
+ */
56
+ declare const apiKey: typeof staticToken;
57
+ //#endregion
58
+ //#region src/retry.d.ts
59
+ /**
60
+ * How a call the server could not answer is sent again, and how long the client
61
+ * waits in between.
62
+ *
63
+ * It applies to the calls where sending the same request twice cannot mean two
64
+ * different things. A read and a delete are that by nature. A create and an
65
+ * update are made so: each one goes out named by an idempotency key, and a rig
66
+ * server that sees the same key twice answers the second one with what it
67
+ * answered the first rather than doing the work again.
68
+ *
69
+ * An upload is not, and is the one write this never repeats — a rig server does
70
+ * not record an upload route against a key, so a second send would store the
71
+ * file twice.
72
+ */
73
+ type Retry = {
74
+ /**
75
+ * How many times a call is sent, the first try included. Absent takes
76
+ * {@link DEFAULT_ATTEMPTS}; one sends it once and reports whatever came
77
+ * back.
78
+ */
79
+ attempts?: number;
80
+ /**
81
+ * The first backoff window in milliseconds, doubling per attempt after it.
82
+ * Absent takes {@link DEFAULT_RETRY_BASE_MS}.
83
+ *
84
+ * The first retry does not use it: it goes out immediately, because the
85
+ * commonest retryable failure there is — a pooled connection the server had
86
+ * already closed — is fixed by opening another one, and waiting a second
87
+ * first would only be slower.
88
+ */
89
+ baseMs?: number;
90
+ /**
91
+ * Bounds one backoff window, however many attempts have already failed.
92
+ * Absent takes {@link DEFAULT_RETRY_CAP_MS}.
93
+ *
94
+ * It does not bind at the default attempt count, which is why it is a
95
+ * setting rather than a number anybody has to choose: a caller who raises
96
+ * `attempts` to eight is asking for eight tries, not for a two-minute sleep
97
+ * in the middle of them.
98
+ */
99
+ capMs?: number;
100
+ };
101
+ //#endregion
102
+ //#region src/rate-limit.d.ts
103
+ /**
104
+ * What a response said about the caller's budget.
105
+ *
106
+ * It is the tightest limit the caller is under rather than a list of every one
107
+ * that applied: the server evaluates each and reports the one closest to
108
+ * refusing, because that is the only number a client can act on.
109
+ */
110
+ type RateLimitStatus = {
111
+ /**
112
+ * The operation that was called, as the document names it — `listTodos`.
113
+ * A gauge keyed on it says which call is spending the budget.
114
+ */
115
+ readonly op: string; /** How many calls the window allows. */
116
+ readonly limit: number; /** How many are left. Zero on the response that was refused. */
117
+ readonly remaining: number;
118
+ /**
119
+ * How long until the window frees, in milliseconds.
120
+ *
121
+ * Only stated on a refusal — an allowed response says how much is left but
122
+ * not when it comes back. Zero means the server did not say.
123
+ */
124
+ readonly resetAfterMs: number; /** True when this response was the 429 rather than a call that went through. */
125
+ readonly refused: boolean;
126
+ };
127
+ /** How much of the budget has been spent. */
128
+ declare const used: (s: RateLimitStatus) => number;
129
+ /**
130
+ * How much of the budget is spent, from 0 to 1.
131
+ *
132
+ * It is the number worth alerting on, and it is here rather than left to the
133
+ * caller because the obvious arithmetic divides by zero: a response from a
134
+ * server with no limit configured carries no headers and leaves `limit` at 0.
135
+ */
136
+ declare const fraction: (s: RateLimitStatus) => number;
137
+ /**
138
+ * Reads the status out of a response, or `undefined` when the server said
139
+ * nothing at all.
140
+ *
141
+ * A server with no `throttle:` block sends none of these headers, and a caller
142
+ * that treated an absent header as zero would read "no budget left" out of a
143
+ * server that has no limits.
144
+ */
145
+ declare const rateLimitOf: (op: string, res: Response) => RateLimitStatus | undefined;
146
+ //#endregion
147
+ //#region src/runtime.d.ts
148
+ /**
149
+ * The authentication profile, as the document describes it.
150
+ *
151
+ * The lifetimes here are the ones the server enforces rather than numbers a
152
+ * client author guessed, which is why a `Session` can refresh ahead of an expiry
153
+ * instead of waiting for a 401 to tell it.
154
+ */
155
+ type AuthProfile = {
156
+ /** Where the endpoints sit, for example `/auth`. */basePath: string; /** The lifetime of the token that travels on every request, in milliseconds. */
157
+ accessTtlMs: number; /** How long an ordinary session lasts, in milliseconds. */
158
+ refreshTtlMs: number;
159
+ /**
160
+ * How long a refresh token stays usable after it has been exchanged, in
161
+ * milliseconds. It is also what this package refreshes ahead by: the server
162
+ * having decided how much slack a swap deserves, a client has no business
163
+ * picking a different number.
164
+ */
165
+ rotationLeewayMs: number;
166
+ };
167
+ /**
168
+ * What the generated client knows about the server and this package does not.
169
+ * Generated code fills it in; a caller never sees it.
170
+ */
171
+ type ApiDescriptor = {
172
+ /** The prefix every route sits under, for example `/api/v1`. */basePath: string; /** The authentication profile, or absent for a project with none. */
173
+ auth?: AuthProfile;
174
+ /**
175
+ * The date the API surface this client was generated from last changed. It
176
+ * is sent on every request, which is how a server's logs can answer how old
177
+ * the oldest caller still calling is.
178
+ */
179
+ revision?: string; /** Where `revision` is sent — the header the generated server reads. */
180
+ revisionHeader?: string;
181
+ };
182
+ /** What a caller supplies. Every field but `baseUrl` has a default. */
183
+ type Config = {
184
+ /**
185
+ * The origin the API is served from, for example `https://api.example.com`.
186
+ * A path on it is kept and the API's own base path is appended, so a server
187
+ * behind `/gateway` is a base URL and not a special case.
188
+ *
189
+ * A relative value works in a browser and is the ordinary same-origin case:
190
+ * `""` resolves against the page.
191
+ */
192
+ baseUrl: string;
193
+ /**
194
+ * What authorizes each request. Absent sends no `Authorization` header,
195
+ * which is what a public API or a cookie-authenticated deployment wants.
196
+ */
197
+ credential?: Credential;
198
+ /**
199
+ * Sent on every request. Where a tenant header, a trace header, or anything
200
+ * else a deployment adds belongs.
201
+ */
202
+ headers?: HeadersInit;
203
+ /**
204
+ * How long a whole call may take, in milliseconds, retries and backoff
205
+ * included. Absent leaves it unbounded and lets the caller's own
206
+ * `AbortSignal` decide, which is what a browser application usually wants.
207
+ */
208
+ timeoutMs?: number;
209
+ /**
210
+ * Supplies the value of the request-ID header, so a client-side log line and
211
+ * a server-side one can be joined. Absent sends none, and the server
212
+ * generates its own.
213
+ */
214
+ requestId?: () => string; /** Defaults to `X-Request-Id`, which is what the generated server reads. */
215
+ requestIdHeader?: string;
216
+ /**
217
+ * Overrides the API revision this client says it was built against.
218
+ *
219
+ * Almost nobody should set it. The generated client carries the revision
220
+ * from the document it was generated from, which is the honest answer and
221
+ * the one the server's logs are for.
222
+ */
223
+ revision?: string; /** Overrides where the revision is sent. */
224
+ revisionHeader?: string;
225
+ /**
226
+ * The transport. Swapping it is how a test intercepts requests and how a
227
+ * server runtime supplies its own. Defaults to the ambient `fetch`.
228
+ */
229
+ fetch?: typeof fetch; /** The clock, for a test that has to cross a token expiry without waiting. */
230
+ now?: () => number; /** How a call the server could not answer is sent again. See {@link Retry}. */
231
+ retry?: Retry;
232
+ /**
233
+ * Called with what each response said about the caller's budget, when the
234
+ * server said anything at all.
235
+ *
236
+ * It is the other half of rate limiting, and the half a client can act on. A
237
+ * 429 is already handled without it — the client reads `Retry-After` and
238
+ * backs off — but by then the call has been refused. These numbers arrive on
239
+ * every response, so a caller that watches them can slow down, shed work, or
240
+ * raise an alarm while its calls are still succeeding.
241
+ *
242
+ * Deliberately a callback and not automatic pacing. A client library that
243
+ * silently waited because `remaining` was low would turn a batch job's
244
+ * throughput into a mystery, and it cannot know whether this caller would
245
+ * rather go slower or fail sooner. The numbers are handed over; the policy
246
+ * is the application's.
247
+ *
248
+ * Called once per attempt, before the call returns. Keep it quick, do not
249
+ * throw from it, and do not call back into the client.
250
+ */
251
+ onRateLimit?: (status: RateLimitStatus) => void;
252
+ };
253
+ /** Where the generated server looks for a caller's own request identifier. */
254
+ declare const DEFAULT_REQUEST_ID_HEADER = "X-Request-Id";
255
+ /**
256
+ * Carries the API revision, and is what `rig.yaml`'s `api.revision_header`
257
+ * defaults to. A generated client passes its project's own, so this is the
258
+ * fallback for a client somebody built by hand.
259
+ */
260
+ declare const DEFAULT_REVISION_HEADER = "API-Revision";
261
+ /**
262
+ * One client's configuration and the state it accumulates.
263
+ *
264
+ * The remembered QUERY decision is the reason this is state rather than
265
+ * configuration: a client that learned an intermediary refuses QUERY should not
266
+ * have to learn it again on the next search.
267
+ */
268
+ declare class Runtime {
269
+ readonly api: ApiDescriptor;
270
+ readonly fetch: typeof fetch;
271
+ readonly now: () => number;
272
+ readonly retry: Retry;
273
+ readonly timeoutMs: number | undefined;
274
+ /**
275
+ * Where a caller's own request identifier is sent. Readable rather than
276
+ * private because the transport writes to it too: a call that names its own
277
+ * identifier has to land in the header this client was configured with, or a
278
+ * deployment that moved the header gets two of them disagreeing.
279
+ */
280
+ readonly requestIdHeader: string;
281
+ /** The randomness in a backoff, held here so a test can make one deterministic. */
282
+ jitter: (n: number) => number;
283
+ private readonly baseUrl;
284
+ private readonly headers;
285
+ private readonly requestIdOf;
286
+ private readonly revision;
287
+ private readonly revisionHeader;
288
+ private credential;
289
+ private readonly onRateLimit;
290
+ /** Records that QUERY was refused once and is not worth trying again. */
291
+ private searchByPost;
292
+ constructor(config: Config, api: ApiDescriptor);
293
+ /**
294
+ * Hands the caller's budget to the configured callback, if the response said
295
+ * anything about it and anybody is listening.
296
+ *
297
+ * A throwing callback must not fail the call it was observing: it is
298
+ * telemetry about a request that otherwise succeeded, and turning a bad
299
+ * gauge into a failed write would be the wrong trade in both directions.
300
+ */
301
+ observeRateLimit(op: string, res: Response): void;
302
+ /** The origin requests go to. */
303
+ get origin(): string;
304
+ /** The credential in force, or `undefined`. */
305
+ getCredential(): Credential | undefined;
306
+ /**
307
+ * Installs a credential, replacing whatever was there. It is what signing in
308
+ * does, and what a caller does by hand when they already hold a token from
309
+ * somewhere else.
310
+ */
311
+ use(credential: Credential | undefined): void;
312
+ /** Whether this client has learned that QUERY does not get through. */
313
+ searchesByPost(): boolean;
314
+ /** Remembers a refused QUERY, so it is tried once per client and not once per call. */
315
+ rememberSearchByPost(): void;
316
+ /**
317
+ * The absolute URL for an operation.
318
+ *
319
+ * `extra` is what a call option added, and it wins where the two name the
320
+ * same parameter: the operation's own query came from the typed arguments of
321
+ * a generated method, and an option is the caller saying something about
322
+ * this one call afterwards.
323
+ */
324
+ url(path: string, query: URLSearchParams | undefined, extra: URLSearchParams | undefined, root: boolean): string;
325
+ /** The headers every request from this client carries, before the call's own. */
326
+ baseHeaders(): Headers;
327
+ }
328
+ //#endregion
329
+ //#region src/session.d.ts
330
+ /**
331
+ * The pair a sign-in returns, in the shape the wire uses.
332
+ *
333
+ * The names are the server's, verbatim — a client that renamed them would be a
334
+ * second description of the same exchange, and this is a value programs store
335
+ * between runs.
336
+ */
337
+ type TokenPair = {
338
+ accessToken?: string;
339
+ refreshToken?: string; /** RFC 3339, or absent from a server that did not say. */
340
+ expiresAt?: string;
341
+ /**
342
+ * When the session itself ends. A client needs both: one says when to
343
+ * refresh, the other says when to stop trying.
344
+ */
345
+ refreshExpiresAt?: string;
346
+ sessionId?: string;
347
+ };
348
+ /** Thrown when a session has nothing left to present. */
349
+ declare class NoSessionError extends Error {
350
+ constructor();
351
+ }
352
+ /**
353
+ * A credential that keeps itself fresh.
354
+ *
355
+ * It holds the pair a sign-in returned and exchanges the refresh token before
356
+ * the access token expires, using the leeway in the document's auth profile — so
357
+ * a page open all day makes a handful of refresh calls at moments of its own
358
+ * choosing, rather than discovering the expiry through a failed request in the
359
+ * middle of something.
360
+ *
361
+ * Several calls arriving at an expiry at once take turns: the ones behind find
362
+ * the work already done instead of each spending a rotation. That matters more
363
+ * than it sounds — the server bounds how often a session may rotate, and a
364
+ * client that raced with itself would spend that budget on nothing.
365
+ */
366
+ declare class Session implements Reauthorizer, Credential {
367
+ private tokens;
368
+ private runtime;
369
+ /**
370
+ * Held for the length of an exchange, so a hundred callers discovering the
371
+ * same expiry produce one refresh.
372
+ */
373
+ private inFlight;
374
+ /** Called when a new pair is issued — a place to persist it. */
375
+ onTokens: ((tokens: TokenPair) => void) | undefined;
376
+ constructor(tokens?: TokenPair);
377
+ /** The pair currently held, for a program that stores it between runs. */
378
+ getTokens(): TokenPair;
379
+ /** Identifies the session, for showing it in a list and revoking it. */
380
+ get sessionId(): string;
381
+ /**
382
+ * Swaps in a newly issued pair, which is what a refresh, a tenant switch and
383
+ * a password change all produce.
384
+ */
385
+ replace(pair: TokenPair): void;
386
+ /** Receives the client this session refreshes through. */
387
+ bind(runtime: unknown): void;
388
+ /**
389
+ * Adds the token, refreshing first if this request might outlive it.
390
+ */
391
+ apply(headers: Headers, signal?: AbortSignal): Promise<void>;
392
+ /**
393
+ * A 401 despite a token that looked current is a revoked or invalidated one,
394
+ * and the refresh token is the only thing left to try.
395
+ *
396
+ * It exchanges whatever the expiry says, because the expiry is exactly what
397
+ * has just been proved wrong.
398
+ */
399
+ reauthorize(signal?: AbortSignal): Promise<boolean>;
400
+ /** Whether the access token is expired or close enough to it. */
401
+ private stale;
402
+ /**
403
+ * Exchanges the refresh token for a new pair, once however many callers ask.
404
+ *
405
+ * Answers false rather than throwing when there is nothing to exchange: a
406
+ * session that was never signed in leaves the 401 that prompted this as the
407
+ * answer, which is what the caller needs to see.
408
+ */
409
+ private exchange;
410
+ }
411
+ //#endregion
412
+ //#region src/op.d.ts
413
+ /**
414
+ * One call, as the generated method describes it.
415
+ *
416
+ * The path is relative to the API's base path and already has its parameters
417
+ * substituted: escaping an identifier into a route is the generated method's
418
+ * job, because only it knows which argument goes where.
419
+ */
420
+ type Op = {
421
+ /**
422
+ * The operation this call is, as the document named it — `listTodos`,
423
+ * `createTodo`.
424
+ *
425
+ * A field rather than something derived from method and path, because the
426
+ * path here already has its identifiers substituted: a name built from it
427
+ * would be a new name for every row anybody ever fetched.
428
+ */
429
+ name: string;
430
+ method: string;
431
+ path: string;
432
+ query?: URLSearchParams;
433
+ /**
434
+ * Encoded as JSON when it is present. `undefined` means no body at all,
435
+ * which is not the same as an empty object.
436
+ */
437
+ body?: unknown;
438
+ /**
439
+ * A form body, sent instead of `body`.
440
+ *
441
+ * The two are exclusive rather than ordered: an op carrying both is a
442
+ * generated method with a bug in it, and it is reported rather than resolved
443
+ * by a precedence somebody would have to look up.
444
+ */
445
+ form?: FormData;
446
+ /**
447
+ * The media type this call will take back. Absent means `application/json`,
448
+ * which is every endpoint but a download: a download answers with whatever
449
+ * the file turned out to be, and a client that insisted on JSON would be
450
+ * asking for the one thing it is not.
451
+ */
452
+ accept?: string;
453
+ /**
454
+ * The path to POST to when `method` is QUERY and something between here and
455
+ * the server refuses it — the `_search` alias the router mounts beside the
456
+ * QUERY route. Absent means there is none, and a refusal is reported rather
457
+ * than worked around.
458
+ */
459
+ fallback?: string;
460
+ /**
461
+ * Says the path is relative to the server rather than to the API's base
462
+ * path. The authentication endpoints are: `/auth/login` is mounted beside
463
+ * `/api/v1` and not inside it, because a sign-in is not a version of the
464
+ * application's API.
465
+ */
466
+ root?: boolean;
467
+ };
468
+ /**
469
+ * The HTTP method a generated search uses.
470
+ *
471
+ * A method rather than a POST because a search is a read: it has a body, and it
472
+ * is safe and idempotent, and pretending otherwise is what made every API in the
473
+ * world invent `/_search`.
474
+ */
475
+ declare const METHOD_QUERY = "QUERY";
476
+ //#endregion
477
+ //#region src/errors.d.ts
478
+ /**
479
+ * A request the server refused.
480
+ *
481
+ * The code, not the status, is what to switch on. Three unrelated failures
482
+ * share a 400 and none of them share a code, which is the whole reason the
483
+ * generated server sends one.
484
+ *
485
+ * `TFields` is the shape of the body that caused the refusal. A caller does not
486
+ * name it: the generated client declares a guard per call that does, so
487
+ * `isTodoCreateError(err)` reads back what `todos.create` refused. Naming it by
488
+ * hand is what `fieldsAs` asks for, and it is why the per-call guard exists —
489
+ * the wrong shape decodes perfectly and answers with an empty object, because
490
+ * every member of a field-error shape is optional.
491
+ */
492
+ declare class RigError<TFields = unknown> extends Error {
493
+ /**
494
+ * The HTTP status, for the cases where only it is meaningful — a 502 from
495
+ * something in front of the server, say, which carries no code.
496
+ */
497
+ readonly status: number;
498
+ /**
499
+ * The machine-readable reason. Empty when the failure came from something
500
+ * that is not a rig server.
501
+ */
502
+ readonly code: ErrorCode | "";
503
+ /** Prose, for a person. It is not meant to be parsed. */
504
+ readonly detail: string;
505
+ /**
506
+ * Correlates this failure with the server's logs. Quoting it in a bug
507
+ * report is the difference between a search and a guess.
508
+ */
509
+ readonly requestId: string;
510
+ /**
511
+ * What was wrong with each member of the body — one member per field,
512
+ * holding what was wrong with it.
513
+ *
514
+ * `undefined` for every refusal but a 422. A 404 has a code and a message
515
+ * and nothing to put beside a control, and an empty object there would read
516
+ * as a body nobody complained about.
517
+ */
518
+ readonly fields: TFields | undefined;
519
+ /**
520
+ * How long the server asked the caller to wait, in milliseconds, from the
521
+ * header of the same name in either form it takes: the seconds rig's own
522
+ * server sends, and the date something in front of it might. Zero when it
523
+ * said nothing, or asked for a moment already past.
524
+ *
525
+ * The SDK honours it for a call it may repeat, so this is mostly for the
526
+ * refusal that came back anyway — where the interval was longer than the
527
+ * call had left to spend.
528
+ */
529
+ readonly retryAfterMs: number;
530
+ /**
531
+ * The start of the raw response, kept for a failure that decoded into
532
+ * nothing useful — a proxy's HTML error page, say. Bounded even where the
533
+ * read was not, so a large validation failure is complete in `fields` and
534
+ * cut short here.
535
+ */
536
+ readonly body: string;
537
+ constructor(init: {
538
+ status: number;
539
+ code?: ErrorCode | "";
540
+ detail?: string;
541
+ requestId?: string;
542
+ fields?: TFields;
543
+ retryAfterMs?: number;
544
+ body?: string;
545
+ });
546
+ }
547
+ /**
548
+ * The codes a rig server sends, matching `rig/runtime/rigerr`. The constant a
549
+ * client switches on is the constant a handler returned.
550
+ */
551
+ declare const ErrorCode: {
552
+ readonly BadRequest: "BadRequest";
553
+ readonly Unauthorized: "Unauthorized";
554
+ readonly Forbidden: "Forbidden";
555
+ readonly NotFound: "NotFound";
556
+ readonly Conflict: "Conflict";
557
+ readonly UnprocessableEntity: "UnprocessableEntity";
558
+ readonly RateLimited: "RateLimited";
559
+ readonly TooLarge: "TooLarge";
560
+ readonly UnsupportedMediaType: "UnsupportedMediaType";
561
+ readonly UpgradeRequired: "UpgradeRequired";
562
+ readonly Internal: "Internal";
563
+ };
564
+ /** One of the codes in {@link ErrorCode}. */
565
+ type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
566
+ /**
567
+ * Why one member of a body was refused, matching `rig/runtime/rigerr`.
568
+ *
569
+ * The same nine codes for every project, so a form can decide what to show from
570
+ * the code and fall back to the message rather than parsing it.
571
+ */
572
+ declare const FieldCode: {
573
+ readonly CannotBeEmpty: "CannotBeEmpty";
574
+ readonly CannotBeNull: "CannotBeNull";
575
+ readonly TooLong: "TooLong";
576
+ readonly TooShort: "TooShort";
577
+ readonly OutOfRange: "OutOfRange";
578
+ readonly InvalidValue: "InvalidValue";
579
+ readonly AlreadyExists: "AlreadyExists";
580
+ readonly NotFound: "NotFound";
581
+ readonly NotAllowed: "NotAllowed";
582
+ };
583
+ /** One of the codes in {@link FieldCode}. */
584
+ type FieldCode = (typeof FieldCode)[keyof typeof FieldCode];
585
+ /**
586
+ * What was wrong with one member of a body.
587
+ *
588
+ * It does not name the field. It is reached through the member of a generated
589
+ * field-error shape that stands for that field, so the name is where the value
590
+ * is and there is no second copy of it to disagree.
591
+ */
592
+ type FieldError = {
593
+ code: FieldCode;
594
+ message: string;
595
+ };
596
+ /**
597
+ * Reports whether a thrown value is a refusal the server explained.
598
+ *
599
+ * False for anything that never reached the server — a DNS failure, an aborted
600
+ * request — because there is no envelope for a code or a field to have come
601
+ * from.
602
+ */
603
+ declare function isRigError(err: unknown): err is RigError;
604
+ /**
605
+ * The code on a refusal, or the empty string for anything else.
606
+ *
607
+ * The predicates below are one line each on top of it; reach for it directly
608
+ * when switching over several codes at once.
609
+ */
610
+ declare function codeOf(err: unknown): ErrorCode | "";
611
+ /** True when the row, or the route, was not there. */
612
+ declare const isNotFound: (err: unknown) => boolean;
613
+ /** True when the write lost a race, or would have broken a uniqueness rule. */
614
+ declare const isConflict: (err: unknown) => boolean;
615
+ /** True when the caller was not signed in, or the token had expired. */
616
+ declare const isUnauthorized: (err: unknown) => boolean;
617
+ /** True when the caller was signed in and still not allowed. */
618
+ declare const isForbidden: (err: unknown) => boolean;
619
+ /** True when the body was refused field by field. `fields` says which. */
620
+ declare const isInvalid: (err: unknown) => boolean;
621
+ /** True when the caller was asked to slow down. `retryAfterMs` says how long. */
622
+ declare const isRateLimited: (err: unknown) => boolean;
623
+ /** True when the body, or the upload, was over the limit. */
624
+ declare const isTooLarge: (err: unknown) => boolean;
625
+ /** True when the content type was not one this route accepts. */
626
+ declare const isUnsupportedMediaType: (err: unknown) => boolean;
627
+ /** True when the client is older than the API surface still supports. */
628
+ declare const isUpgradeRequired: (err: unknown) => boolean;
629
+ /**
630
+ * Reads a refusal back as the shape of the body that caused it.
631
+ *
632
+ * This is the hand-written counterpart of the generated per-call guards, for a
633
+ * request made through the runtime directly. It cannot check that the shape is
634
+ * the right one — that is what the generated guard is for.
635
+ */
636
+ declare function fieldsAs<TFields>(err: unknown): TFields | undefined;
637
+ //#endregion
638
+ //#region src/transport.d.ts
639
+ /** What a caller says about one call, on top of what the method already knows. */
640
+ type CallOptions = {
641
+ /** Added to this request, replacing a client-wide header of the same name. */headers?: HeadersInit;
642
+ /**
643
+ * Added to the query string, winning over the operation's own where both
644
+ * name the same parameter.
645
+ */
646
+ query?: Record<string, string> | URLSearchParams; /** Aborts the call, retries and backoff included. */
647
+ signal?: AbortSignal; /** Bounds this call in milliseconds, overriding the client's own. */
648
+ timeoutMs?: number; /** Overrides how many times this one call is sent. */
649
+ attempts?: number;
650
+ /**
651
+ * Names this write, so a server that sees the same key twice answers the
652
+ * second send with what it answered the first.
653
+ *
654
+ * The SDK generates one for a write it may repeat. A caller's own is worth
655
+ * having where it is derived from the data — an import job naming a row by
656
+ * its line deduplicates a re-run of the whole job, which a fresh random name
657
+ * cannot.
658
+ */
659
+ idempotencyKey?: string; /** Overrides the request identifier for this call. */
660
+ requestId?: string; /** Sends no credential, for a route that must be reached signed out. */
661
+ anonymous?: boolean;
662
+ /**
663
+ * Asks for every row in the tenant rather than the caller's own, on a
664
+ * resource that is owner-scoped. Refused unless the caller holds the
665
+ * permission for it.
666
+ */
667
+ wide?: boolean;
668
+ /**
669
+ * Makes the read conditional. A 304 then comes back as `undefined` rather
670
+ * than as a failure — which is why it is gated on the option and not on the
671
+ * status alone: a 304 nobody asked for is an unexplained failure.
672
+ */
673
+ ifNoneMatch?: string;
674
+ };
675
+ /**
676
+ * Performs a call the document says answers with a body, and decodes it.
677
+ *
678
+ * A 204 where a body was promised throws rather than resolving to `undefined`.
679
+ * That is a deliberate trade against {@link sendOptional}: the alternative is
680
+ * every generated method answering `T | undefined`, so every call site narrows a
681
+ * value that in practice is always there — and the one time it is not, the
682
+ * server and the document disagree, which is a bug and reads better as one.
683
+ */
684
+ declare function send<T>(rt: Runtime, op: Op, opts?: CallOptions): Promise<T>;
685
+ /**
686
+ * Performs a call and decodes the response body, or `undefined` when there was
687
+ * none.
688
+ *
689
+ * This is the shape for an endpoint that can honestly answer either way, and for
690
+ * a request made through the runtime by hand.
691
+ */
692
+ declare function sendOptional<T>(rt: Runtime, op: Op, opts?: CallOptions): Promise<T | undefined>;
693
+ /**
694
+ * Performs a call that answers with nothing, such as a delete.
695
+ *
696
+ * Any body is drained and discarded rather than parsed: an endpoint that grows
697
+ * one later should not break a client that never wanted it.
698
+ */
699
+ declare function sendNoContent(rt: Runtime, op: Op, opts?: CallOptions): Promise<void>;
700
+ /**
701
+ * Performs a call and hands back the response unread, for a download.
702
+ *
703
+ * The caller owns the body from here: nothing below reads it, so streaming a
704
+ * large file does not require buffering it first.
705
+ */
706
+ declare function sendContent(rt: Runtime, op: Op, opts?: CallOptions): Promise<Response>;
707
+ //#endregion
708
+ //#region src/paginate.d.ts
709
+ /** One page of a paginated read, as far as the iteration cares. */
710
+ type Page<T> = {
711
+ items: T[];
712
+ /**
713
+ * Every row matching the query, ignoring pagination. It is what says whether
714
+ * there is another page.
715
+ */
716
+ total: number; /** Where this page started, as the server reported it. */
717
+ offset: number;
718
+ };
719
+ /**
720
+ * Walks a paginated read to its end.
721
+ *
722
+ * `fetch` is handed the offset to ask for and returns one page; the limit is
723
+ * whatever the caller's query said, which `fetch` closes over. Iteration stops
724
+ * after the page that reaches the reported total, at the first failure, or when
725
+ * a page comes back empty — that last one is the guard against a server whose
726
+ * total disagrees with what it returns, which would otherwise be an infinite
727
+ * loop rather than a bug report.
728
+ *
729
+ * A failure is thrown, so `for await` reports it where the caller is standing.
730
+ * There is no partial answer: what came before the failure was yielded, and
731
+ * nothing after it is.
732
+ *
733
+ * ```ts
734
+ * for await (const todo of paginate(0, (offset) =>
735
+ * client.todos.list({ limit: 100, offset }).then(toPage)
736
+ * )) {
737
+ * …
738
+ * }
739
+ * ```
740
+ */
741
+ declare function paginate<T>(startOffset: number, fetch: (offset: number) => Promise<Page<T>>): AsyncGenerator<T, void, undefined>;
742
+ //#endregion
743
+ //#region src/query.d.ts
744
+ /**
745
+ * The query-string writers the generated methods call.
746
+ *
747
+ * Every one of them writes nothing for `undefined`, which is the point: the
748
+ * generated server applies a parameter's default only when the parameter is
749
+ * absent, so a client that helpfully sent `limit=0` would get an empty page
750
+ * instead of the default one. Absent has to stay absent.
751
+ *
752
+ * The formats match what the server parses: RFC 3339 for a time, the canonical
753
+ * hyphenated form for a UUID, `"true"`/`"false"` for a boolean.
754
+ */
755
+ /** The types a query parameter can be given as. */
756
+ type ParamValue = string | number | boolean | Date;
757
+ /** Writes one parameter, or nothing when the value is absent. */
758
+ declare function setParam(query: URLSearchParams, key: string, value: ParamValue | null | undefined): void;
759
+ /**
760
+ * Writes a repeated parameter, one key per value.
761
+ *
762
+ * An empty array writes nothing rather than an empty key, for the same reason a
763
+ * single absent value does: the server distinguishes "no filter" from "a filter
764
+ * matching nothing".
765
+ */
766
+ declare function setParams(query: URLSearchParams, key: string, values: readonly ParamValue[] | null | undefined): void;
767
+ /**
768
+ * Escapes a value for a path segment.
769
+ *
770
+ * An identifier that arrived from somewhere else can be anything at all, and a
771
+ * slash in one would otherwise silently address a different route.
772
+ */
773
+ declare function pathValue(value: string): string;
774
+ //#endregion
775
+ //#region src/upload.d.ts
776
+ /** One file a caller is sending. */
777
+ type Upload = {
778
+ /**
779
+ * What the file is called. The server records it on the row and puts it in
780
+ * the download path, and it never becomes the storage key — so a name with a
781
+ * slash in it is a strange name and not a way out of the bucket.
782
+ */
783
+ name: string; /** The bytes. A `File` from an `<input type="file">` is already one of these. */
784
+ body: Blob;
785
+ /**
786
+ * What the caller claims the bytes are. Absent takes the blob's own type,
787
+ * and it makes little difference either way: the server sniffs the content,
788
+ * and the sniffed type is the one it stores and the one it serves back.
789
+ */
790
+ contentType?: string;
791
+ };
792
+ /**
793
+ * Builds the `multipart/form-data` body rig's upload endpoints take: the row,
794
+ * and the files beside it.
795
+ *
796
+ * It is the shape rig's own endpoints take rather than a general form encoder. A
797
+ * generated method calls it; a caller supplies the {@link Upload}s.
798
+ *
799
+ * `row` is left out entirely when absent, which is what a bare upload does:
800
+ * there is no row to send, only bytes for a row that already exists.
801
+ *
802
+ * An absent upload is left out the same way, so a generated method can name
803
+ * every file column of a create unconditionally. What makes a required one
804
+ * impossible to leave out is the generated shape it arrives in — the member is
805
+ * optional there only where the column is nullable — and not a check here,
806
+ * which would report at runtime what the compiler has already refused.
807
+ */
808
+ declare function multipart(row: unknown, files: ReadonlyArray<readonly [field: string, upload: Upload | undefined]>): FormData;
809
+ //#endregion
810
+ export { type ApiDescriptor, type AuthProfile, type Bindable, type CallOptions, type Config, type Credential, DEFAULT_REQUEST_ID_HEADER, DEFAULT_REVISION_HEADER, ErrorCode, FieldCode, type FieldError, METHOD_QUERY, NoSessionError, type Op, type Page, type ParamValue, type RateLimitStatus, type Reauthorizer, type Retry, RigError, Runtime, Session, type TokenPair, type Upload, apiKey, codeOf, fieldsAs, fraction, isConflict, isForbidden, isInvalid, isNotFound, isRateLimited, isReauthorizer, isRigError, isTooLarge, isUnauthorized, isUnsupportedMediaType, isUpgradeRequired, multipart, paginate, pathValue, rateLimitOf, send, sendContent, sendNoContent, sendOptional, setParam, setParams, staticToken, used };