@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.
- package/LICENSE.md +21 -0
- package/README.md +325 -2
- package/dist/calls/index.d.mts +288 -0
- package/dist/calls/index.d.mts.map +1 -0
- package/dist/calls/index.mjs +390 -0
- package/dist/calls/index.mjs.map +1 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +1029 -0
- package/dist/index.mjs.map +1 -0
- package/dist/request-options-DWYI_Ldj.d.mts +1550 -0
- package/dist/request-options-DWYI_Ldj.d.mts.map +1 -0
- package/dist/tools-7Hs5J2xb.d.mts +1945 -0
- package/dist/tools-7Hs5J2xb.d.mts.map +1 -0
- package/dist/tools-SwP0sK8H.mjs +2458 -0
- package/dist/tools-SwP0sK8H.mjs.map +1 -0
- package/dist/types/index.d.mts +2 -0
- package/dist/types/index.mjs +1 -0
- package/package.json +67 -4
- package/src/calls/brands-analytics-get.ts +33 -0
- package/src/calls/brands-citations-domains-list.ts +36 -0
- package/src/calls/brands-citations-urls-list.ts +32 -0
- package/src/calls/brands-create.ts +19 -0
- package/src/calls/brands-get.ts +19 -0
- package/src/calls/brands-list.ts +18 -0
- package/src/calls/brands-opportunities-get.ts +42 -0
- package/src/calls/brands-prompt-performance-list.ts +39 -0
- package/src/calls/brands-query-fanout-get.ts +30 -0
- package/src/calls/brands-tags-list.ts +28 -0
- package/src/calls/brands-update.ts +20 -0
- package/src/calls/competitors-create.ts +23 -0
- package/src/calls/competitors-delete.ts +19 -0
- package/src/calls/competitors-get.ts +19 -0
- package/src/calls/competitors-list.ts +23 -0
- package/src/calls/competitors-update.ts +24 -0
- package/src/calls/index.ts +33 -0
- package/src/calls/me-get.ts +18 -0
- package/src/calls/models-list.ts +20 -0
- package/src/calls/organizations-billing-get.ts +37 -0
- package/src/calls/organizations-get.ts +24 -0
- package/src/calls/organizations-list.ts +27 -0
- package/src/calls/prompts-create.ts +27 -0
- package/src/calls/prompts-delete.ts +28 -0
- package/src/calls/prompts-get.ts +23 -0
- package/src/calls/prompts-list.ts +28 -0
- package/src/calls/prompts-runs-get.ts +27 -0
- package/src/calls/prompts-runs-list.ts +30 -0
- package/src/calls/prompts-snapshot-get.ts +28 -0
- package/src/calls/prompts-update.ts +29 -0
- package/src/calls/reports-create.ts +31 -0
- package/src/calls/reports-get.ts +27 -0
- package/src/calls/reports-list.ts +28 -0
- package/src/calls/tools-analyze.ts +26 -0
- package/src/client.ts +662 -0
- package/src/index.ts +21 -0
- package/src/internal/binding/rest/binding.ts +127 -0
- package/src/internal/binding/rest/errors.ts +107 -0
- package/src/internal/binding/rest/path.ts +9 -0
- package/src/internal/binding/rest/url.ts +97 -0
- package/src/internal/codec/json.ts +66 -0
- package/src/internal/codec/registry.ts +55 -0
- package/src/internal/core/backoff.ts +170 -0
- package/src/internal/core/body.ts +54 -0
- package/src/internal/core/browser.ts +11 -0
- package/src/internal/core/call-promise.ts +145 -0
- package/src/internal/core/callable.ts +11 -0
- package/src/internal/core/client.ts +89 -0
- package/src/internal/core/config.ts +84 -0
- package/src/internal/core/credentials.ts +40 -0
- package/src/internal/core/dispatch.ts +227 -0
- package/src/internal/core/env.ts +68 -0
- package/src/internal/core/errors.ts +431 -0
- package/src/internal/core/features.ts +60 -0
- package/src/internal/core/lazy-promise.ts +39 -0
- package/src/internal/core/metadata.ts +79 -0
- package/src/internal/core/params.ts +98 -0
- package/src/internal/core/protocol.ts +17 -0
- package/src/internal/core/read-reply.ts +7 -0
- package/src/internal/core/text.ts +7 -0
- package/src/internal/core/types.ts +556 -0
- package/src/internal/feature/auth.ts +201 -0
- package/src/internal/feature/interceptors.ts +36 -0
- package/src/internal/feature/logger.ts +222 -0
- package/src/internal/feature/retry.ts +165 -0
- package/src/internal/feature/rules.ts +54 -0
- package/src/internal/feature/timeout.ts +146 -0
- package/src/internal/feature/validate.ts +74 -0
- package/src/internal/page/page.ts +402 -0
- package/src/internal/transport/fetch.ts +82 -0
- package/src/resources/analytics.ts +363 -0
- package/src/resources/brands.ts +285 -0
- package/src/resources/competitors.ts +212 -0
- package/src/resources/identity.ts +48 -0
- package/src/resources/models.ts +32 -0
- package/src/resources/opportunities.ts +79 -0
- package/src/resources/organizations.ts +181 -0
- package/src/resources/prompts.ts +292 -0
- package/src/resources/reports.ts +211 -0
- package/src/resources/runs.ts +123 -0
- package/src/resources/shared/auth.ts +13 -0
- package/src/resources/shared/request-options.ts +26 -0
- package/src/resources/shared/resource.ts +23 -0
- package/src/resources/shared/rule.ts +25 -0
- package/src/resources/snapshots.ts +54 -0
- package/src/resources/tags.ts +46 -0
- package/src/resources/tools.ts +68 -0
- package/src/types/analytics.ts +497 -0
- package/src/types/brands.ts +228 -0
- package/src/types/competitors.ts +196 -0
- package/src/types/identity.ts +55 -0
- package/src/types/index.ts +163 -0
- package/src/types/models.ts +40 -0
- package/src/types/opportunities.ts +84 -0
- package/src/types/organizations.ts +157 -0
- package/src/types/prompts.ts +231 -0
- package/src/types/reports.ts +266 -0
- package/src/types/runs.ts +131 -0
- package/src/types/shared/brand-id-path.ts +2 -0
- package/src/types/shared/conflict-error.ts +7 -0
- package/src/types/shared/error.ts +29 -0
- package/src/types/shared/limit.ts +8 -0
- package/src/types/shared/mention-entry.ts +21 -0
- package/src/types/shared/model-filter.ts +15 -0
- package/src/types/shared/not-found-error.ts +4 -0
- package/src/types/shared/page.ts +7 -0
- package/src/types/shared/pagination.ts +7 -0
- package/src/types/shared/validation-error.ts +4 -0
- package/src/types/snapshots.ts +88 -0
- package/src/types/tags.ts +47 -0
- 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 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;
|