@elmohq/sdk 0.0.0-stage → 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.
Files changed (129) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +325 -2
  3. package/dist/calls/index.d.mts +288 -0
  4. package/dist/calls/index.d.mts.map +1 -0
  5. package/dist/calls/index.mjs +390 -0
  6. package/dist/calls/index.mjs.map +1 -0
  7. package/dist/index.d.mts +2 -0
  8. package/dist/index.mjs +1029 -0
  9. package/dist/index.mjs.map +1 -0
  10. package/dist/request-options-DWYI_Ldj.d.mts +1550 -0
  11. package/dist/request-options-DWYI_Ldj.d.mts.map +1 -0
  12. package/dist/tools-7Hs5J2xb.d.mts +1945 -0
  13. package/dist/tools-7Hs5J2xb.d.mts.map +1 -0
  14. package/dist/tools-SwP0sK8H.mjs +2458 -0
  15. package/dist/tools-SwP0sK8H.mjs.map +1 -0
  16. package/dist/types/index.d.mts +2 -0
  17. package/dist/types/index.mjs +1 -0
  18. package/package.json +67 -4
  19. package/src/calls/brands-analytics-get.ts +33 -0
  20. package/src/calls/brands-citations-domains-list.ts +36 -0
  21. package/src/calls/brands-citations-urls-list.ts +32 -0
  22. package/src/calls/brands-create.ts +19 -0
  23. package/src/calls/brands-get.ts +19 -0
  24. package/src/calls/brands-list.ts +18 -0
  25. package/src/calls/brands-opportunities-get.ts +42 -0
  26. package/src/calls/brands-prompt-performance-list.ts +39 -0
  27. package/src/calls/brands-query-fanout-get.ts +30 -0
  28. package/src/calls/brands-tags-list.ts +28 -0
  29. package/src/calls/brands-update.ts +20 -0
  30. package/src/calls/competitors-create.ts +23 -0
  31. package/src/calls/competitors-delete.ts +19 -0
  32. package/src/calls/competitors-get.ts +19 -0
  33. package/src/calls/competitors-list.ts +23 -0
  34. package/src/calls/competitors-update.ts +24 -0
  35. package/src/calls/index.ts +33 -0
  36. package/src/calls/me-get.ts +18 -0
  37. package/src/calls/models-list.ts +20 -0
  38. package/src/calls/organizations-billing-get.ts +37 -0
  39. package/src/calls/organizations-get.ts +24 -0
  40. package/src/calls/organizations-list.ts +27 -0
  41. package/src/calls/prompts-create.ts +27 -0
  42. package/src/calls/prompts-delete.ts +28 -0
  43. package/src/calls/prompts-get.ts +23 -0
  44. package/src/calls/prompts-list.ts +28 -0
  45. package/src/calls/prompts-runs-get.ts +27 -0
  46. package/src/calls/prompts-runs-list.ts +30 -0
  47. package/src/calls/prompts-snapshot-get.ts +28 -0
  48. package/src/calls/prompts-update.ts +29 -0
  49. package/src/calls/reports-create.ts +31 -0
  50. package/src/calls/reports-get.ts +27 -0
  51. package/src/calls/reports-list.ts +28 -0
  52. package/src/calls/tools-analyze.ts +26 -0
  53. package/src/client.ts +662 -0
  54. package/src/index.ts +21 -0
  55. package/src/internal/binding/rest/binding.ts +127 -0
  56. package/src/internal/binding/rest/errors.ts +107 -0
  57. package/src/internal/binding/rest/path.ts +9 -0
  58. package/src/internal/binding/rest/url.ts +97 -0
  59. package/src/internal/codec/json.ts +66 -0
  60. package/src/internal/codec/registry.ts +55 -0
  61. package/src/internal/core/backoff.ts +170 -0
  62. package/src/internal/core/body.ts +54 -0
  63. package/src/internal/core/browser.ts +11 -0
  64. package/src/internal/core/call-promise.ts +145 -0
  65. package/src/internal/core/callable.ts +11 -0
  66. package/src/internal/core/client.ts +89 -0
  67. package/src/internal/core/config.ts +84 -0
  68. package/src/internal/core/credentials.ts +40 -0
  69. package/src/internal/core/dispatch.ts +227 -0
  70. package/src/internal/core/env.ts +68 -0
  71. package/src/internal/core/errors.ts +431 -0
  72. package/src/internal/core/features.ts +60 -0
  73. package/src/internal/core/lazy-promise.ts +39 -0
  74. package/src/internal/core/metadata.ts +79 -0
  75. package/src/internal/core/params.ts +98 -0
  76. package/src/internal/core/protocol.ts +17 -0
  77. package/src/internal/core/read-reply.ts +7 -0
  78. package/src/internal/core/text.ts +7 -0
  79. package/src/internal/core/types.ts +556 -0
  80. package/src/internal/feature/auth.ts +201 -0
  81. package/src/internal/feature/interceptors.ts +36 -0
  82. package/src/internal/feature/logger.ts +222 -0
  83. package/src/internal/feature/retry.ts +165 -0
  84. package/src/internal/feature/rules.ts +54 -0
  85. package/src/internal/feature/timeout.ts +146 -0
  86. package/src/internal/feature/validate.ts +74 -0
  87. package/src/internal/page/page.ts +402 -0
  88. package/src/internal/transport/fetch.ts +82 -0
  89. package/src/resources/analytics.ts +363 -0
  90. package/src/resources/brands.ts +285 -0
  91. package/src/resources/competitors.ts +212 -0
  92. package/src/resources/identity.ts +48 -0
  93. package/src/resources/models.ts +32 -0
  94. package/src/resources/opportunities.ts +79 -0
  95. package/src/resources/organizations.ts +181 -0
  96. package/src/resources/prompts.ts +292 -0
  97. package/src/resources/reports.ts +211 -0
  98. package/src/resources/runs.ts +123 -0
  99. package/src/resources/shared/auth.ts +13 -0
  100. package/src/resources/shared/request-options.ts +26 -0
  101. package/src/resources/shared/resource.ts +23 -0
  102. package/src/resources/shared/rule.ts +25 -0
  103. package/src/resources/snapshots.ts +54 -0
  104. package/src/resources/tags.ts +46 -0
  105. package/src/resources/tools.ts +68 -0
  106. package/src/types/analytics.ts +497 -0
  107. package/src/types/brands.ts +228 -0
  108. package/src/types/competitors.ts +196 -0
  109. package/src/types/identity.ts +55 -0
  110. package/src/types/index.ts +163 -0
  111. package/src/types/models.ts +40 -0
  112. package/src/types/opportunities.ts +84 -0
  113. package/src/types/organizations.ts +157 -0
  114. package/src/types/prompts.ts +231 -0
  115. package/src/types/reports.ts +266 -0
  116. package/src/types/runs.ts +131 -0
  117. package/src/types/shared/brand-id-path.ts +2 -0
  118. package/src/types/shared/conflict-error.ts +7 -0
  119. package/src/types/shared/error.ts +29 -0
  120. package/src/types/shared/limit.ts +8 -0
  121. package/src/types/shared/mention-entry.ts +21 -0
  122. package/src/types/shared/model-filter.ts +15 -0
  123. package/src/types/shared/not-found-error.ts +4 -0
  124. package/src/types/shared/page.ts +7 -0
  125. package/src/types/shared/pagination.ts +7 -0
  126. package/src/types/shared/validation-error.ts +4 -0
  127. package/src/types/snapshots.ts +88 -0
  128. package/src/types/tags.ts +47 -0
  129. package/src/types/tools.ts +78 -0
@@ -0,0 +1,98 @@
1
+ import type { ResolvedOptions } from './types';
2
+
3
+ export type ExtraKeys = 'body' | 'query';
4
+
5
+ export type FlatLayer = Readonly<Record<string, string>> | ReadonlyArray<string>;
6
+
7
+ export interface FlatFields {
8
+ body?: FlatLayer;
9
+ path?: FlatLayer;
10
+ query?: FlatLayer;
11
+ whole?: string;
12
+ }
13
+
14
+ const LAYERS = ['path', 'query'] as const;
15
+
16
+ export function mergeParams(
17
+ inputs: Readonly<Record<string, unknown>> | undefined,
18
+ options: Readonly<Record<string, unknown>> | undefined,
19
+ ): ResolvedOptions {
20
+ const merged: Record<string, unknown> = { ...inputs, ...options };
21
+ if (inputs && options) {
22
+ for (const layer of LAYERS) {
23
+ const declared = inputs[layer] as Record<string, unknown> | undefined;
24
+ const given = options[layer] as Record<string, unknown> | undefined;
25
+ if (!declared || !given) continue;
26
+ const both: Record<string, unknown> = { ...declared };
27
+ for (const [name, value] of Object.entries(given)) {
28
+ if (value !== undefined) both[name] = value;
29
+ }
30
+ merged[layer] = both;
31
+ }
32
+ }
33
+ return merged as ResolvedOptions;
34
+ }
35
+
36
+ interface Placement {
37
+ in: keyof FlatFields;
38
+ map: string;
39
+ }
40
+
41
+ const placed = new WeakMap<FlatFields, ReadonlyMap<string, Placement>>();
42
+
43
+ function placements(fields: FlatFields): ReadonlyMap<string, Placement> {
44
+ let known = placed.get(fields);
45
+ if (!known) {
46
+ const built = new Map<string, Placement>();
47
+ for (const layer of Object.keys(fields) as Array<keyof FlatFields>) {
48
+ const names = fields[layer];
49
+ if (typeof names === 'string') built.set(names, { in: layer, map: names });
50
+ else if (Array.isArray(names)) {
51
+ for (const key of names) built.set(key, { in: layer, map: key });
52
+ } else if (names) {
53
+ for (const [key, wire] of Object.entries(names)) built.set(key, { in: layer, map: wire });
54
+ }
55
+ }
56
+ known = built;
57
+ placed.set(fields, built);
58
+ }
59
+ return known;
60
+ }
61
+
62
+ export function placeParams(
63
+ options: Readonly<Record<string, unknown>> | undefined,
64
+ fields: FlatFields,
65
+ extras?: ExtraKeys,
66
+ ): ResolvedOptions {
67
+ const named = placements(fields);
68
+ const grouped: Record<string, unknown> = {};
69
+ const given: Record<string, unknown> = {};
70
+
71
+ for (const [key, value] of Object.entries(options ?? {})) {
72
+ const place = named.get(key);
73
+
74
+ if (!place) {
75
+ if (extras === undefined) given[key] = value;
76
+ else ((grouped[extras] ??= {}) as Record<string, unknown>)[key] = value;
77
+ continue;
78
+ }
79
+
80
+ if (place.in === 'whole') {
81
+ grouped['body'] = value;
82
+ continue;
83
+ }
84
+
85
+ ((grouped[place.in] ??= {}) as Record<string, unknown>)[place.map] = value;
86
+ }
87
+
88
+ return mergeParams(grouped, given);
89
+ }
90
+
91
+ export function groupParams(
92
+ options: Readonly<Record<string, unknown>> | undefined,
93
+ layer?: ExtraKeys,
94
+ ): ResolvedOptions {
95
+ if (layer === undefined) return { ...options } as ResolvedOptions;
96
+ if (!options || !Object.keys(options).length) return {};
97
+ return { [layer]: { ...options } } as ResolvedOptions;
98
+ }
@@ -0,0 +1,17 @@
1
+ import type { AnyTransport, Binding, Interaction } from './types';
2
+
3
+ export type Protocol = 'channel' | 'rest' | 'websocket';
4
+
5
+ export interface ProtocolSetup {
6
+ binding: Binding;
7
+ transport: AnyTransport;
8
+ }
9
+
10
+ export type Protocols = Partial<Record<Protocol, ProtocolSetup>>;
11
+
12
+ export function onlyProtocol(
13
+ protocols: Protocols,
14
+ _interaction: Interaction | undefined,
15
+ ): ProtocolSetup {
16
+ return Object.values(protocols)[0]!;
17
+ }
@@ -0,0 +1,7 @@
1
+ export async function readReply<T>(
2
+ read: (value: T) => Promise<T> | T,
3
+ value: T,
4
+ _response?: Response,
5
+ ): Promise<T> {
6
+ return await read(value);
7
+ }
@@ -0,0 +1,7 @@
1
+ export function scalarText(value: unknown): string | undefined {
2
+ return value instanceof Date ? value.toISOString() : undefined;
3
+ }
4
+
5
+ export function toText(value: unknown): string {
6
+ return typeof value === 'string' ? value : (scalarText(value) ?? String(value));
7
+ }
@@ -0,0 +1,556 @@
1
+ import type { BackoffOptions, RetryAfterHeader } from './backoff';
2
+ import { APIError, ElmoError } from './errors';
3
+
4
+ /** A request body in a form `fetch` sends as it is. */
5
+ export type BodyInput = NonNullable<RequestInit['body']>;
6
+
7
+ /** An encoded request body, or `null` for a request that sends none. */
8
+ export type BodyPayload = BodyInput | null;
9
+
10
+ /**
11
+ * One reply, as far as it has been read. Until its body is read, only `ok`,
12
+ * `response` and `status` are set.
13
+ */
14
+ export interface Result<TData = unknown, TError = unknown> {
15
+ /** The decoded body of a 2xx reply. */
16
+ data?: TData;
17
+ /** The decoded body of a reply that failed. */
18
+ error?: TError;
19
+ /** `true` for a 2xx status. */
20
+ ok?: boolean;
21
+ /** The reply itself, where one arrived. */
22
+ response?: Response;
23
+ status?: number;
24
+ }
25
+
26
+ export interface CallLog {
27
+ ended(error?: unknown): void;
28
+ failed(error: unknown): void;
29
+ replied(response: Response | undefined): void;
30
+ retrying(wait: number, retry: number, retries: number, after?: Result): void;
31
+ sending(): void;
32
+ }
33
+
34
+ /** One way the API takes a credential, as the API description declares it. */
35
+ export interface AuthScheme {
36
+ /**
37
+ * Where the credential is sent.
38
+ *
39
+ * @default 'header'
40
+ */
41
+ readonly in?: 'cookie' | 'header' | 'query';
42
+ /** Which credential this is. Its option, where it has one, has the same name. */
43
+ readonly key?: string;
44
+ /** The header, query parameter or cookie the credential is sent in. */
45
+ readonly name?: string;
46
+ /** How an `http` credential is written in the `Authorization` header. */
47
+ readonly scheme?: 'basic' | 'bearer';
48
+ /**
49
+ * `'apiKey'` sends the credential as it is, where `in` and `name` say.
50
+ * `'http'` sends it in the `Authorization` header, written as `scheme` says.
51
+ */
52
+ readonly type: 'apiKey' | 'http';
53
+ }
54
+
55
+ export type AuthRequirement = AuthScheme | ReadonlyArray<AuthScheme>;
56
+
57
+ /** A time limit in milliseconds, or `false` for none. */
58
+ export type CallTimeout = number | false;
59
+
60
+ export interface InputRule {
61
+ readonly above?: number;
62
+ readonly below?: number;
63
+ readonly max?: number;
64
+ readonly min?: number;
65
+ readonly name?: string;
66
+ readonly of?: 'characters' | 'entries' | 'items';
67
+ }
68
+
69
+ export type Interaction = 'unary';
70
+
71
+ export type PaginationStyle = 'cursor' | 'link' | 'offset' | 'page';
72
+
73
+ export interface PaginationDescriptor {
74
+ readonly cursor?: string;
75
+ readonly in?: 'body' | 'query';
76
+ readonly items?: string;
77
+ readonly limitParam?: string;
78
+ readonly link?: string;
79
+ readonly more?: string;
80
+ readonly pages?: string;
81
+ readonly param?: string;
82
+ readonly size?: string;
83
+ readonly start?: number;
84
+ readonly style: PaginationStyle;
85
+ readonly total?: string;
86
+ }
87
+
88
+ /** How a failed call is sent again. Anything left out keeps the client's rule. */
89
+ export interface RetryRules extends BackoffOptions {
90
+ /**
91
+ * How long a call may keep trying, in milliseconds, counted from the first
92
+ * attempt with the waits included. A retry that would start past it is not
93
+ * sent, and the last reply or error stands. An attempt that is running is
94
+ * not cut short: `timeout` limits each one.
95
+ *
96
+ * @default no limit
97
+ */
98
+ budget?: number;
99
+ /**
100
+ * How many times a failed call is sent again, after the first attempt.
101
+ *
102
+ * @default 2
103
+ */
104
+ maxRetries?: number;
105
+ /**
106
+ * The longest wait the API may ask for, in milliseconds. Past it, the call
107
+ * gives up and returns the reply as it is.
108
+ *
109
+ * @default 60_000
110
+ */
111
+ maxRetryAfter?: number;
112
+ /**
113
+ * Methods that may be sent again. A failure that never reached the server,
114
+ * a call carrying its API's idempotency key, and 408, 425 and 429 are sent
115
+ * again whatever this says.
116
+ *
117
+ * @default idempotent methods
118
+ */
119
+ methods?: ReadonlyArray<string>;
120
+ /**
121
+ * Headers the API may name its own wait in, read before the backoff
122
+ * applies. `false` reads none.
123
+ *
124
+ * @default `retry-after-ms`, `retry-after`, `x-ratelimit-reset-after`,
125
+ * `x-ratelimit-reset` and `x-rate-limit-reset`, in that order
126
+ */
127
+ retryAfter?: ReadonlyArray<RetryAfterHeader> | boolean;
128
+ /**
129
+ * Send the request again when an attempt runs past its deadline.
130
+ *
131
+ * @default false
132
+ */
133
+ retryOnTimeout?: boolean;
134
+ /**
135
+ * Statuses worth another attempt. A reply with `x-should-retry: false` is
136
+ * never retried.
137
+ */
138
+ statuses?: ReadonlyArray<number>;
139
+ }
140
+
141
+ export interface ParameterSerialization {
142
+ readonly allowReserved?: boolean;
143
+ readonly array?: { readonly explode?: boolean; readonly style?: string };
144
+ readonly mediaType?: string;
145
+ readonly object?: { readonly explode?: boolean; readonly style?: string };
146
+ }
147
+
148
+ export interface SerializationDescriptor {
149
+ readonly query?: Readonly<Record<string, ParameterSerialization>>;
150
+ }
151
+
152
+ export interface CallableDescriptor {
153
+ readonly accept?: string;
154
+ readonly address: string;
155
+ readonly auth?: ReadonlyArray<AuthRequirement>;
156
+ readonly authOptional?: boolean;
157
+ readonly baseURL?: string;
158
+ readonly deprecated?: boolean;
159
+ readonly idempotency?: string;
160
+ readonly interaction?: Interaction;
161
+ readonly mediaType?: string;
162
+ readonly method?: string;
163
+ readonly pagination?: PaginationDescriptor;
164
+ readonly retry?: RetryRules | false;
165
+ /** `'json'` where every success is declared as a JSON object or list. */
166
+ readonly returns?: 'json';
167
+ readonly rules?: ReadonlyArray<InputRule>;
168
+ readonly serialization?: SerializationDescriptor;
169
+ readonly timeout?: CallTimeout;
170
+ readonly validators?: unknown;
171
+ }
172
+
173
+ export type MetadataValue = string | Array<string> | undefined;
174
+
175
+ export type Metadata = Record<string, MetadataValue>;
176
+
177
+ export interface Credential {
178
+ in: 'cookie' | 'header' | 'query';
179
+ name: string;
180
+ value: string;
181
+ }
182
+
183
+ export interface PlacedCredential {
184
+ readonly credential: Credential;
185
+ readonly scheme: AuthScheme;
186
+ }
187
+
188
+ export type BodyOptions = {
189
+ /** The request body, before it is encoded. */
190
+ body?: unknown;
191
+ };
192
+
193
+ export type SignalOptions = {
194
+ /** Cancels this call when it fires, retries included. */
195
+ signal?: AbortSignal;
196
+ };
197
+
198
+ export type ExchangeOptions = BodyOptions & SignalOptions;
199
+
200
+ /** A credential as a string, before it is written into the request. `undefined` sends none. */
201
+ export type AuthToken = string | undefined;
202
+
203
+ /**
204
+ * Returns the credential to send for `scheme`, given the value its option
205
+ * holds. `undefined` sends none for that scheme.
206
+ */
207
+ export type AuthResolver = (scheme: AuthScheme, value: AuthToken) => Promise<AuthToken> | AuthToken;
208
+
209
+ /** What the `auth` option takes: an {@link AuthResolver}, or `null` to send none. */
210
+ export type AuthValue = AuthResolver | null;
211
+
212
+ export type BodySerializerOptions = {
213
+ /**
214
+ * Encodes the request body, in place of the default for its content type.
215
+ * `null` sends the body as given.
216
+ */
217
+ bodySerializer?: ((body: unknown) => BodyPayload) | null;
218
+ };
219
+
220
+ export type BaseURLOptions<TBaseURL = string> = {
221
+ /** The base URL for this call, in place of the client's. */
222
+ baseURL?: TBaseURL;
223
+ };
224
+
225
+ export type CredentialOptions<TAuth = AuthValue> = {
226
+ /**
227
+ * Decides each credential a call sends, given the scheme and the value its
228
+ * option holds. What it returns is sent, so return the value to keep it.
229
+ * `null` sends no credential.
230
+ */
231
+ auth?: TAuth;
232
+ };
233
+
234
+ export type EnvironmentOptions<TEnvironment = string> = {
235
+ /** Which environment to send to. `baseURL` wins over this. */
236
+ environment?: TEnvironment;
237
+ };
238
+
239
+ export type ConnectionOptions<
240
+ TBaseURL = string,
241
+ TAuth = AuthValue,
242
+ TEnvironment = string,
243
+ > = BaseURLOptions<TBaseURL> &
244
+ CredentialOptions<TAuth> &
245
+ ([TEnvironment] extends [never] ? unknown : EnvironmentOptions<TEnvironment>);
246
+
247
+ /**
248
+ * How a failure reaches the caller.
249
+ *
250
+ * - `'throw'` resolves to the payload and throws the failure.
251
+ * - `'return'` resolves to the result envelope and throws no failure of the
252
+ * call. What one of your own functions throws is still thrown.
253
+ */
254
+ export type ErrorMode = 'return' | 'throw';
255
+
256
+ export type ErrorOptions = {
257
+ /**
258
+ * Whether a failure is thrown or handed back beside the data. Set on a
259
+ * client's defaults, not per call.
260
+ *
261
+ * @default 'throw'
262
+ */
263
+ errors?: ErrorMode;
264
+ };
265
+
266
+ /** Headers as a `Headers`, as pairs of name and value, or as an object. */
267
+ export type MetadataInput = Headers | Iterable<readonly [string, string]> | Record<string, unknown>;
268
+
269
+ /** What a call sends beside its body. Each member is merged per name. */
270
+ export type MetadataOptions = {
271
+ /** Headers to send. Merged per name, and `null` drops one. */
272
+ headers?: MetadataInput;
273
+ /** Path parameters. Merged per name. */
274
+ path?: Record<string, unknown>;
275
+ /** Query parameters to add. Merged per name, and `null` drops one. */
276
+ query?: Record<string, unknown>;
277
+ };
278
+
279
+ export type SharedOptions<
280
+ TAddress = string,
281
+ TAuth = AuthValue,
282
+ TEnvironment = string,
283
+ > = BodySerializerOptions &
284
+ ConnectionOptions<TAddress, TAuth, TEnvironment> &
285
+ ErrorOptions &
286
+ MetadataOptions;
287
+
288
+ export type CallOptions = ExchangeOptions & SharedOptions;
289
+
290
+ export interface ResolvedOptions extends CallOptions {
291
+ [key: string]: unknown;
292
+ envelope?: boolean;
293
+ path?: Record<string, unknown>;
294
+ query?: Record<string, unknown>;
295
+ raw?: boolean;
296
+ unread?: boolean;
297
+ }
298
+
299
+ /** A request about to be sent, which a `request` hook reads and may change. */
300
+ export interface PreparedRequest<TAddress = unknown> {
301
+ /** Where the request goes. Over HTTP, the whole URL, query string included. */
302
+ address: TAddress;
303
+ /** How many times this request was sent before. Unset on the first attempt. */
304
+ attempt?: number;
305
+ /** The request body, already encoded. */
306
+ body: BodyPayload | undefined;
307
+ /** The operation this request calls, as declared, `method` included. */
308
+ callable: CallableDescriptor;
309
+ /** How the call exchanges messages, such as one request for one reply. */
310
+ interaction: Interaction;
311
+ /** Where this call writes its log lines. Unset where the client does not log. */
312
+ log?: CallLog;
313
+ /** The headers to send, by lowercase name. */
314
+ meta: Metadata;
315
+ /** The options this call resolved to: the client's, with the call's own on top. */
316
+ options: ResolvedOptions;
317
+ /** Each credential put on the request, with the scheme it answers. */
318
+ placed?: ReadonlyArray<PlacedCredential>;
319
+ /**
320
+ * Set to `manual` by a feature that follows redirects itself. A transport
321
+ * that honors it hands back the redirect rather than following it.
322
+ */
323
+ redirect?: 'manual';
324
+ /** Calls the request off when it aborts. */
325
+ signal?: AbortSignal | undefined;
326
+ /** What to tell the caller where the API refuses a call sent with no credential. */
327
+ unauthenticated?: () => string;
328
+ }
329
+
330
+ /** The kinds of exchange a transport carries. `unary` is one request and one reply. */
331
+ export type TransportInteraction = 'unary';
332
+
333
+ interface UnaryTransport<TAddress> {
334
+ /**
335
+ * Sends one request and resolves to the reply, whatever its status, with the
336
+ * body unread. Rejects only when no reply arrived.
337
+ */
338
+ unary(request: PreparedRequest<TAddress>): Promise<Response>;
339
+ }
340
+
341
+ type TransportMethods<TAddress> = UnaryTransport<TAddress>;
342
+
343
+ /** What carries a request to the API and hands back what the API sends. */
344
+ export type Transport<TAddress, TInteraction extends TransportInteraction> = {
345
+ /** The transport's name, as an error message shows it. */
346
+ readonly name: string;
347
+ } & Pick<TransportMethods<TAddress>, TInteraction>;
348
+
349
+ /** A credential, or a function that returns one when a call needs it. */
350
+ export type CredentialValue = AuthToken | (() => Promise<AuthToken> | AuthToken);
351
+
352
+ export interface Codec {
353
+ readonly contentType: string | undefined;
354
+ decode?(raw: Response): Promise<unknown>;
355
+ encode(value: unknown): BodyPayload;
356
+ readonly mediaTypes: ReadonlyArray<string>;
357
+ readonly name: string;
358
+ }
359
+
360
+ export type AnyTransport<TAddress = unknown> = {
361
+ readonly name: string;
362
+ } & Partial<TransportMethods<TAddress>>;
363
+
364
+ export interface Binding<TAddress = unknown> {
365
+ /** What a request asks for where neither its call nor its client says. */
366
+ readonly accept?: string;
367
+ applyAuth(credential: Credential, request: PreparedRequest<TAddress>): void;
368
+ readonly name: string;
369
+ readError?(result: Result, request: PreparedRequest<TAddress>): unknown;
370
+ readResult?(
371
+ raw: Response,
372
+ request: PreparedRequest<TAddress>,
373
+ codecs: ReadonlyArray<Codec>,
374
+ ): Promise<Result>;
375
+ resolveAddress(callable: CallableDescriptor, options: ResolvedOptions): TAddress;
376
+ }
377
+
378
+ export interface FeatureContext {
379
+ binding: Binding;
380
+ transport: AnyTransport;
381
+ }
382
+
383
+ export type Send = (request: PreparedRequest) => Promise<Result>;
384
+
385
+ export interface Feature {
386
+ readonly name: string;
387
+ onError?(error: unknown, request: PreparedRequest, ctx: FeatureContext): unknown;
388
+ onOpen?(request: PreparedRequest, next: Send, ctx: FeatureContext): Promise<Result>;
389
+ onOptions?(
390
+ options: ResolvedOptions,
391
+ callable: CallableDescriptor,
392
+ ctx: FeatureContext,
393
+ ): Promise<void> | void;
394
+ onPrepare?(request: PreparedRequest, ctx: FeatureContext): Promise<void> | void;
395
+ onRequest?(request: PreparedRequest, ctx: FeatureContext): Promise<void> | void;
396
+ onResult?(
397
+ result: Result,
398
+ request: PreparedRequest,
399
+ ctx: FeatureContext,
400
+ ): Promise<Result> | Result;
401
+ onSend?(request: PreparedRequest, next: Send, ctx: FeatureContext): Promise<Result>;
402
+ }
403
+
404
+ export type CredentialSpec = {
405
+ readonly option?: false;
406
+ readonly prefix?: string;
407
+ readonly variable?: string;
408
+ };
409
+
410
+ type Hundred = 3 | 4 | 5;
411
+
412
+ type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
413
+
414
+ type NumberOf<T> = T extends `${infer N extends number}` ? N : never;
415
+
416
+ type Tens = {
417
+ [H in Hundred]: {
418
+ [T in Digit]: NumberOf<`${H}${T}${Digit}`>;
419
+ };
420
+ };
421
+
422
+ type Block = {
423
+ [H in Hundred]: Tens[H][Digit];
424
+ };
425
+
426
+ type SplitDigits<TText> = TText extends `${infer H}${infer T}${string}`
427
+ ? [NumberOf<H>, NumberOf<T>]
428
+ : never;
429
+
430
+ type DigitsOf<TStatus> = TStatus extends number ? SplitDigits<`${TStatus}`> : never;
431
+
432
+ type LeftIn<H extends Hundred, TExact> = H extends DigitsOf<TExact>[0]
433
+ ? {
434
+ [T in Digit]: [H, T] extends DigitsOf<TExact> ? Exclude<Tens[H][T], TExact> : Tens[H][T];
435
+ }[Digit]
436
+ : Block[H];
437
+
438
+ type Left<THundreds, TExact> = THundreds extends Hundred ? LeftIn<THundreds, TExact> : never;
439
+
440
+ type RangeDigit<TKey> = TKey extends `${infer D extends number}${'X' | 'x'}${'X' | 'x'}`
441
+ ? D & Hundred
442
+ : never;
443
+
444
+ type Open<TErrors> = Exclude<Hundred, RangeDigit<keyof TErrors>>;
445
+
446
+ type StatusKey<TKey> = TKey extends number ? TKey : NumberOf<TKey>;
447
+
448
+ export type DefaultStatus<TErrors> = Left<Open<TErrors>, StatusKey<keyof TErrors>> & number;
449
+
450
+ export type RangeStatus<TErrors, TKey> = Left<RangeDigit<TKey>, StatusKey<keyof TErrors>> & number;
451
+
452
+ export type FailureStatusOf<TErrors, TKey> = TKey extends number
453
+ ? TKey
454
+ : TKey extends `${infer N extends number}`
455
+ ? N
456
+ : Lowercase<TKey & string> extends 'default'
457
+ ? DefaultStatus<TErrors>
458
+ : RangeStatus<TErrors, TKey>;
459
+
460
+ export type UndeclaredStatus<TErrors> = ('default' extends Lowercase<Extract<keyof TErrors, string>>
461
+ ? 0
462
+ : 0 | Left<Open<TErrors>, StatusKey<keyof TErrors>>) &
463
+ number;
464
+
465
+ export type ErrorOf<TErrors> =
466
+ | (TErrors extends object
467
+ ? {
468
+ [K in keyof TErrors]: {
469
+ data?: never;
470
+ error: APIError<TErrors[K]>;
471
+ ok: false;
472
+ status: FailureStatusOf<TErrors, K>;
473
+ };
474
+ }[keyof TErrors]
475
+ : never)
476
+ | {
477
+ data?: never;
478
+ error: APIError;
479
+ ok: false;
480
+ status: UndeclaredStatus<TErrors>;
481
+ };
482
+
483
+ type StatusOf<TKey> = TKey extends number ? TKey : number;
484
+
485
+ type OutputOf<TResponses> = TResponses extends object
486
+ ? {
487
+ [K in keyof TResponses]: {
488
+ data: TResponses[K];
489
+ error?: never;
490
+ ok: true;
491
+ status: StatusOf<K>;
492
+ };
493
+ }[keyof TResponses]
494
+ : Result;
495
+
496
+ export type UnansweredOf = {
497
+ data?: never;
498
+ error: ElmoError;
499
+ ok: false;
500
+ status?: undefined;
501
+ };
502
+
503
+ /**
504
+ * What awaiting a call gives back: the payload, or the whole result where the
505
+ * client returns a failure in place of throwing it.
506
+ */
507
+ export type CallResult<
508
+ TResponses = unknown,
509
+ TErrors = unknown,
510
+ TMode extends ErrorMode = 'throw',
511
+ > = TMode extends 'return'
512
+ ? ErrorOf<TErrors> | OutputOf<TResponses> | UnansweredOf
513
+ : TResponses extends object
514
+ ? TResponses[keyof TResponses]
515
+ : unknown;
516
+
517
+ export interface EncodedBody {
518
+ contentType: string | undefined;
519
+ payload: BodyPayload;
520
+ }
521
+
522
+ export type PageOptions = {
523
+ /**
524
+ * Where to start reading, from an earlier page's `nextPageParam`. Unset, the
525
+ * walk starts at the page the call's own arguments name.
526
+ */
527
+ pageParam?: unknown;
528
+ };
529
+
530
+ /** What `result()` gives back: the payload, or the failure as a value. */
531
+ export type ResultEnvelope<TResponses = unknown, TErrors = unknown> = CallResult<
532
+ TResponses,
533
+ TErrors,
534
+ 'return'
535
+ >;
536
+
537
+ /** What a paged call hands back when its failure is read rather than thrown. */
538
+ export type PageEnvelope<TAwaited = unknown, TErrors = unknown> =
539
+ | ErrorOf<TErrors>
540
+ | UnansweredOf
541
+ | {
542
+ data: TAwaited;
543
+ error?: never;
544
+ ok: true;
545
+ status: number;
546
+ };
547
+
548
+ /**
549
+ * What awaiting a paged call gives back: the page, or the whole result where
550
+ * the client returns a failure in place of throwing it.
551
+ */
552
+ export type PageResult<
553
+ TAwaited = unknown,
554
+ TErrors = unknown,
555
+ TMode extends ErrorMode = 'throw',
556
+ > = TMode extends 'return' ? PageEnvelope<TAwaited, TErrors> : TAwaited;