@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
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1029 @@
|
|
|
1
|
+
import { At as selectCodec, Bt as emptyPathParameter, C as Organizations, Ct as isFieldValue, Dt as toText, Et as scalarText, Ft as MissingCredentialError, Gt as unansweredError, H as Brands, Ht as isStructured, It as TimeoutError, Jt as TIMER_LIMIT, Kt as unsendable, Lt as TransportError, Mt as AbortError, N as Competitors, Nt as DecodeError, Ot as essenceOf, Pt as ElmoError, Qt as sleep, Rt as calledOffWith, St as createConfig, Tt as metadataValue, Ut as refusedInput, Vt as failureOf, Wt as safeAddress, Xt as exceedsMaxRetryAfter, Yt as backoffDelay, Zt as retryAfterDelay, _t as createClient, bt as replyWith, c as Prompts, gt as clientFor, ht as validateFeature, j as Me, jt as APIError, k as Models, kt as readText, mt as ElmoResource, qt as unsendableAddress, r as Reports, t as Tools, vt as discardBody, wt as metadataHeaders, xt as baseURLOf, yt as isStreamed, zt as classifyError } from "./tools-SwP0sK8H.mjs";
|
|
2
|
+
//#region src/internal/binding/rest/errors.ts
|
|
3
|
+
function undeclaredBody(response, value) {
|
|
4
|
+
const where = response.url ? ` for "${safeAddress(response.url)}"` : "";
|
|
5
|
+
const contentType = response.headers.get("content-type");
|
|
6
|
+
const said = contentType ? ` Its content type said "${contentType}".` : "";
|
|
7
|
+
const got = value === null || value === void 0 ? "no JSON object or list, where the API description declares one." : `a body that is not the JSON object or list the API description declares.${said}`;
|
|
8
|
+
return new DecodeError(`The API answered ${response.status}${where} with ${got}`, {
|
|
9
|
+
response,
|
|
10
|
+
value
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
function unreadableBody(response, cause) {
|
|
14
|
+
const where = response.url ? ` for "${safeAddress(response.url)}"` : "";
|
|
15
|
+
const contentType = response.headers.get("content-type");
|
|
16
|
+
const said = contentType ? ` Its content type said "${contentType}".` : "";
|
|
17
|
+
return new DecodeError(`The API answered ${response.status}${where} with a body this client could not read.${said}`, {
|
|
18
|
+
cause,
|
|
19
|
+
response
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** The API could not read the request, so it did not act on it. */
|
|
23
|
+
var BadRequestError = class extends APIError {
|
|
24
|
+
name = "BadRequestError";
|
|
25
|
+
};
|
|
26
|
+
/** No usable credential reached the API: it was missing, unreadable, or rejected. */
|
|
27
|
+
var AuthenticationError = class extends APIError {
|
|
28
|
+
name = "AuthenticationError";
|
|
29
|
+
};
|
|
30
|
+
/** The plan or the quota on this account does not cover the call. */
|
|
31
|
+
var PaymentRequiredError = class extends APIError {
|
|
32
|
+
name = "PaymentRequiredError";
|
|
33
|
+
};
|
|
34
|
+
/** The credential was accepted, but it does not grant this call. */
|
|
35
|
+
var PermissionDeniedError = class extends APIError {
|
|
36
|
+
name = "PermissionDeniedError";
|
|
37
|
+
};
|
|
38
|
+
/** Nothing is at this address, or the credential may not see what is. */
|
|
39
|
+
var NotFoundError = class extends APIError {
|
|
40
|
+
name = "NotFoundError";
|
|
41
|
+
};
|
|
42
|
+
/** The call collided with the resource's current state: a duplicate, or a concurrent change. */
|
|
43
|
+
var ConflictError = class extends APIError {
|
|
44
|
+
name = "ConflictError";
|
|
45
|
+
};
|
|
46
|
+
/** The request was read, and its contents were rejected. */
|
|
47
|
+
var UnprocessableEntityError = class extends APIError {
|
|
48
|
+
name = "UnprocessableEntityError";
|
|
49
|
+
};
|
|
50
|
+
/** Too many calls. {@link APIError.retryAfterMs} carries how long the API asked to wait. */
|
|
51
|
+
var RateLimitError = class extends APIError {
|
|
52
|
+
name = "RateLimitError";
|
|
53
|
+
};
|
|
54
|
+
const BY_STATUS = {
|
|
55
|
+
400: BadRequestError,
|
|
56
|
+
401: AuthenticationError,
|
|
57
|
+
402: PaymentRequiredError,
|
|
58
|
+
403: PermissionDeniedError,
|
|
59
|
+
404: NotFoundError,
|
|
60
|
+
409: ConflictError,
|
|
61
|
+
422: UnprocessableEntityError,
|
|
62
|
+
429: RateLimitError
|
|
63
|
+
};
|
|
64
|
+
/** The API failed after accepting the call. Every status from 500 up arrives as this. */
|
|
65
|
+
var InternalServerError = class extends APIError {
|
|
66
|
+
name = "InternalServerError";
|
|
67
|
+
};
|
|
68
|
+
function toAPIError(status, error, response, unauthenticated) {
|
|
69
|
+
return new (BY_STATUS[status] ?? (status >= 500 ? InternalServerError : APIError))(status, error, response, status === 401 || status === 403 ? unauthenticated?.() : void 0);
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/internal/binding/rest/binding.ts
|
|
73
|
+
const DEFAULT_ACCEPT = "application/json";
|
|
74
|
+
function placeInHeader(credential, request) {
|
|
75
|
+
request.meta[credential.name.toLowerCase()] = credential.value;
|
|
76
|
+
}
|
|
77
|
+
const PLACERS = { header: placeInHeader };
|
|
78
|
+
function declaredReply(value, raw, request) {
|
|
79
|
+
if (request.callable.returns !== "json" || isStructured(value)) return value;
|
|
80
|
+
throw undeclaredBody(raw, value);
|
|
81
|
+
}
|
|
82
|
+
function parserFor(contentType) {
|
|
83
|
+
if (!contentType) return "text";
|
|
84
|
+
const essence = essenceOf(contentType);
|
|
85
|
+
if (essence === "application/json" || essence.endsWith("+json")) return "json";
|
|
86
|
+
if (essence.startsWith("text/")) return "text";
|
|
87
|
+
return "blob";
|
|
88
|
+
}
|
|
89
|
+
function asksForJSON(request) {
|
|
90
|
+
const accept = metadataValue(request.meta["accept"]);
|
|
91
|
+
return !!accept && accept.split(",").every((one) => parserFor(one) === "json");
|
|
92
|
+
}
|
|
93
|
+
function jsonOrText(text) {
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(text);
|
|
96
|
+
} catch {
|
|
97
|
+
return text;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function readBody(raw, request, codecs = []) {
|
|
101
|
+
if (raw.body === null || raw.headers.get("content-length") === "0") return null;
|
|
102
|
+
const contentType = raw.headers.get("content-type");
|
|
103
|
+
const codec = selectCodec(codecs, contentType ?? void 0);
|
|
104
|
+
const parser = parserFor(contentType);
|
|
105
|
+
const copy = raw.ok ? void 0 : raw.clone();
|
|
106
|
+
try {
|
|
107
|
+
if (codec?.decode) return await codec.decode(raw);
|
|
108
|
+
if (parser === "blob") return await raw.blob();
|
|
109
|
+
const text = await readText(raw, parser === "json");
|
|
110
|
+
if (text === "") return null;
|
|
111
|
+
if (parser === "json") return JSON.parse(text);
|
|
112
|
+
return asksForJSON(request) ? jsonOrText(text) : text;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (error instanceof ElmoError) throw error;
|
|
115
|
+
if (classifyError(error) === "abort") throw unansweredError(error);
|
|
116
|
+
if (copy) return readText(copy).catch(() => {
|
|
117
|
+
throw unreadableBody(raw, error);
|
|
118
|
+
});
|
|
119
|
+
throw unreadableBody(raw, error);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function createRestBinding(buildURL) {
|
|
123
|
+
return {
|
|
124
|
+
accept: DEFAULT_ACCEPT,
|
|
125
|
+
applyAuth(credential, request) {
|
|
126
|
+
const place = PLACERS[credential.in];
|
|
127
|
+
if (!place) throw new ElmoError(`This client places no credential in the ${credential.in}, so "${request.callable.address}" cannot be authenticated.`);
|
|
128
|
+
place(credential, request);
|
|
129
|
+
},
|
|
130
|
+
name: "rest",
|
|
131
|
+
readError(result, request) {
|
|
132
|
+
const { response } = result;
|
|
133
|
+
if (!response || response.ok && result.error === void 0) return;
|
|
134
|
+
return toAPIError(response.status, result.error, response, request.unauthenticated);
|
|
135
|
+
},
|
|
136
|
+
async readResult(raw, request, codecs) {
|
|
137
|
+
if (request.options.unread) return {
|
|
138
|
+
ok: raw.ok,
|
|
139
|
+
response: raw,
|
|
140
|
+
status: raw.status
|
|
141
|
+
};
|
|
142
|
+
const value = raw.status === 204 || raw.status === 205 ? null : await readBody(raw, request, codecs);
|
|
143
|
+
return raw.ok ? {
|
|
144
|
+
data: declaredReply(value, raw, request),
|
|
145
|
+
ok: true,
|
|
146
|
+
response: raw,
|
|
147
|
+
status: raw.status
|
|
148
|
+
} : {
|
|
149
|
+
error: value,
|
|
150
|
+
ok: false,
|
|
151
|
+
response: raw,
|
|
152
|
+
status: raw.status
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
resolveAddress(callable, options) {
|
|
156
|
+
return buildURL({
|
|
157
|
+
address: callable.address,
|
|
158
|
+
baseURL: baseURLOf(options, callable.baseURL),
|
|
159
|
+
path: options.path,
|
|
160
|
+
query: options.query,
|
|
161
|
+
serialization: callable.serialization
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region src/internal/binding/rest/path.ts
|
|
168
|
+
function encodeValue(value) {
|
|
169
|
+
return encodeURIComponent(toText(value));
|
|
170
|
+
}
|
|
171
|
+
function encodeName(name) {
|
|
172
|
+
return encodeURIComponent(name).replace(/%5B/g, "[").replace(/%5D/g, "]");
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/internal/binding/rest/url.ts
|
|
176
|
+
const NAMED_PATH_PARAM_RE = /\{([^{}]+)\}/g;
|
|
177
|
+
const ABSOLUTE_URL_RE = /^[a-z][a-z\d+.-]*:\/\//i;
|
|
178
|
+
function joinPath(base, path) {
|
|
179
|
+
return base.endsWith("/") && path.startsWith("/") ? base + path.slice(1) : base + path;
|
|
180
|
+
}
|
|
181
|
+
function joinURL(baseURL, path) {
|
|
182
|
+
const base = baseURL ?? "";
|
|
183
|
+
const hash = base.indexOf("#");
|
|
184
|
+
const kept = hash === -1 ? base : base.slice(0, hash);
|
|
185
|
+
const mark = kept.indexOf("?");
|
|
186
|
+
if (mark === -1) return joinPath(kept, path);
|
|
187
|
+
const joined = joinPath(kept.slice(0, mark), path);
|
|
188
|
+
const query = kept.slice(mark + 1);
|
|
189
|
+
if (!query) return joined;
|
|
190
|
+
return `${joined}${joined.includes("?") ? "&" : "?"}${query}`;
|
|
191
|
+
}
|
|
192
|
+
function addressURL(address, baseURL) {
|
|
193
|
+
const absolute = ABSOLUTE_URL_RE.test(address);
|
|
194
|
+
const pathURL = absolute || address.startsWith("/") ? address : `/${address}`;
|
|
195
|
+
return absolute ? pathURL : joinURL(baseURL, pathURL);
|
|
196
|
+
}
|
|
197
|
+
function pathValue(value) {
|
|
198
|
+
if (Array.isArray(value)) return value.map(encodeValue).join(",");
|
|
199
|
+
if (typeof value === "object" && value !== null && scalarText(value) === void 0) return Object.entries(value).flatMap(([key, item]) => [encodeURIComponent(key), encodeValue(item)]).join(",");
|
|
200
|
+
return encodeValue(value);
|
|
201
|
+
}
|
|
202
|
+
function filledURL(address, baseURL, path) {
|
|
203
|
+
const url = addressURL(address, baseURL);
|
|
204
|
+
if (!path) return url;
|
|
205
|
+
return url.replace(NAMED_PATH_PARAM_RE, (_match, name) => {
|
|
206
|
+
const value = path[name];
|
|
207
|
+
if (value === void 0 || value === null || value === "") throw emptyPathParameter(name, address);
|
|
208
|
+
return pathValue(value);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function withQuery(url, query) {
|
|
212
|
+
if (!query) return url;
|
|
213
|
+
const search = [];
|
|
214
|
+
for (const [name, value] of Object.entries(query)) {
|
|
215
|
+
if (value === void 0 || value === null) continue;
|
|
216
|
+
if (Array.isArray(value)) {
|
|
217
|
+
for (const item of value) if (item !== void 0 && item !== null) search.push(`${encodeName(name)}=${encodeValue(item)}`);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (typeof value === "object" && scalarText(value) === void 0) {
|
|
221
|
+
for (const [key, item] of Object.entries(value)) if (item !== void 0 && item !== null) search.push(`${encodeName(key)}=${encodeValue(item)}`);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
search.push(`${encodeName(name)}=${encodeValue(value)}`);
|
|
225
|
+
}
|
|
226
|
+
if (!search.length) return url;
|
|
227
|
+
return `${url}${url.includes("?") ? "&" : "?"}${search.join("&")}`;
|
|
228
|
+
}
|
|
229
|
+
const simpleURL = ({ address, baseURL, path, query }) => withQuery(filledURL(address, baseURL, path), query);
|
|
230
|
+
//#endregion
|
|
231
|
+
//#region src/internal/codec/json.ts
|
|
232
|
+
function finite(value) {
|
|
233
|
+
if (!Number.isFinite(value)) throw new ElmoError("NaN and Infinity cannot be written as JSON.");
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
236
|
+
function refused(value) {
|
|
237
|
+
const name = typeof value === "object" && value !== null ? value.constructor?.name ?? "Object" : typeof value;
|
|
238
|
+
return new ElmoError(`A value of type ${name} cannot be written as JSON.`);
|
|
239
|
+
}
|
|
240
|
+
function mapKey(key) {
|
|
241
|
+
if (typeof key === "number") return String(finite(key));
|
|
242
|
+
if (typeof key === "string" || typeof key === "boolean" || typeof key === "bigint") return String(key);
|
|
243
|
+
throw refused(key);
|
|
244
|
+
}
|
|
245
|
+
function jsonValue(_key, value) {
|
|
246
|
+
if (typeof value === "bigint") return value.toString();
|
|
247
|
+
if (typeof value === "number") return finite(value);
|
|
248
|
+
if (typeof value !== "object" || value === null) return value;
|
|
249
|
+
if (value instanceof Set) return [...value];
|
|
250
|
+
if (value instanceof Map) return Object.fromEntries([...value].map(([key, item]) => [mapKey(key), item]));
|
|
251
|
+
if (value instanceof ArrayBuffer || ArrayBuffer.isView(value) || typeof Blob !== "undefined" && value instanceof Blob) throw refused(value);
|
|
252
|
+
return value;
|
|
253
|
+
}
|
|
254
|
+
function jsonText(value) {
|
|
255
|
+
try {
|
|
256
|
+
return JSON.stringify(value, jsonValue);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
if (error instanceof ElmoError) throw error;
|
|
259
|
+
throw new ElmoError(`A value cannot be written as JSON: ${error.message}`, { cause: error });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const jsonCodec = {
|
|
263
|
+
contentType: "application/json",
|
|
264
|
+
decode: async (raw) => {
|
|
265
|
+
const text = await readText(raw, true);
|
|
266
|
+
return text === "" ? null : JSON.parse(text);
|
|
267
|
+
},
|
|
268
|
+
encode: jsonText,
|
|
269
|
+
mediaTypes: ["application/json"],
|
|
270
|
+
name: "json"
|
|
271
|
+
};
|
|
272
|
+
//#endregion
|
|
273
|
+
//#region src/internal/core/browser.ts
|
|
274
|
+
function refuseBrowser(options) {
|
|
275
|
+
if (options["dangerouslyAllowBrowser"] === true) return;
|
|
276
|
+
const scope = globalThis;
|
|
277
|
+
if (scope.window === void 0 || scope.document === void 0) return;
|
|
278
|
+
throw new ElmoError("This client was built in a browser, where every visitor to the page can read the credential it sends. Build it on a server instead. If the credential is meant to be public, or a proxy adds it, pass `dangerouslyAllowBrowser: true`.");
|
|
279
|
+
}
|
|
280
|
+
//#endregion
|
|
281
|
+
//#region src/internal/feature/auth.ts
|
|
282
|
+
function schemesOf(requirement) {
|
|
283
|
+
return Array.isArray(requirement) ? requirement : [requirement];
|
|
284
|
+
}
|
|
285
|
+
function candidates(requirements, ..._environment) {
|
|
286
|
+
return requirements.map(schemesOf);
|
|
287
|
+
}
|
|
288
|
+
function formatBearer(token) {
|
|
289
|
+
return `Bearer ${token}`;
|
|
290
|
+
}
|
|
291
|
+
const SCHEME_FORMATS = { bearer: formatBearer };
|
|
292
|
+
function formatToken(scheme, token, prefix) {
|
|
293
|
+
if (!token) return void 0;
|
|
294
|
+
if (prefix !== void 0) return `${prefix}${token}`;
|
|
295
|
+
const format = scheme.scheme ? SCHEME_FORMATS[scheme.scheme] : void 0;
|
|
296
|
+
return format ? format(token) : token;
|
|
297
|
+
}
|
|
298
|
+
function labeled(key, options) {
|
|
299
|
+
const variable = options?.schemes?.[key]?.variable;
|
|
300
|
+
return variable ? `\`${key}\` (\`${variable}\`)` : `\`${key}\``;
|
|
301
|
+
}
|
|
302
|
+
function credentialFor(scheme, token, options) {
|
|
303
|
+
const prefix = scheme.key ? options?.schemes?.[scheme.key]?.prefix : void 0;
|
|
304
|
+
const value = formatToken(scheme, token?.trim(), prefix);
|
|
305
|
+
if (!value) return void 0;
|
|
306
|
+
const credential = {
|
|
307
|
+
in: scheme.in ?? "header",
|
|
308
|
+
name: scheme.name ?? "Authorization",
|
|
309
|
+
value
|
|
310
|
+
};
|
|
311
|
+
if (credential.in !== "query" && !isFieldValue(value)) {
|
|
312
|
+
const held = scheme.key ? labeled(scheme.key, options) : `The credential \`auth\` returned for \`${credential.name}\``;
|
|
313
|
+
throw new MissingCredentialError(unsendable(held), scheme.key ? [scheme.key] : []);
|
|
314
|
+
}
|
|
315
|
+
return credential;
|
|
316
|
+
}
|
|
317
|
+
async function credentialsFor(schemes, ask, options) {
|
|
318
|
+
const placed = [];
|
|
319
|
+
for (const scheme of schemes) {
|
|
320
|
+
const credential = credentialFor(scheme, await ask(scheme), options);
|
|
321
|
+
if (!credential) return void 0;
|
|
322
|
+
placed.push({
|
|
323
|
+
credential,
|
|
324
|
+
scheme
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
return placed;
|
|
328
|
+
}
|
|
329
|
+
function keysOf(open) {
|
|
330
|
+
return [...new Set(open.flat())];
|
|
331
|
+
}
|
|
332
|
+
function conjoined(keys, options) {
|
|
333
|
+
return keys.map((key) => labeled(key, options)).join(" and ");
|
|
334
|
+
}
|
|
335
|
+
function either(alternatives) {
|
|
336
|
+
if (alternatives.length <= 2) return alternatives.join(" or ");
|
|
337
|
+
return `${alternatives.slice(0, -1).join(", ")}, or ${alternatives.at(-1)}`;
|
|
338
|
+
}
|
|
339
|
+
function advice(open, options) {
|
|
340
|
+
if (!open.length) return "";
|
|
341
|
+
const alternatives = open.map((keys) => conjoined(keys, options));
|
|
342
|
+
if (alternatives.length > 1 && open.some((keys) => keys.length > 1)) return ` Either ${alternatives.map((one) => `set ${one}`).join(", or ")}.`;
|
|
343
|
+
return ` Set ${either(alternatives)}.`;
|
|
344
|
+
}
|
|
345
|
+
function missing(open, options) {
|
|
346
|
+
return `This call needs a credential that was not set.${advice(open, options)}`;
|
|
347
|
+
}
|
|
348
|
+
function offered(alternatives) {
|
|
349
|
+
return alternatives.map((schemes) => schemes.map((scheme) => scheme.key).filter((key) => key !== void 0)).filter((keys) => keys.length > 0);
|
|
350
|
+
}
|
|
351
|
+
async function tokenFor(scheme, held, options) {
|
|
352
|
+
const stated = scheme.key !== void 0 && options?.schemes?.[scheme.key]?.option !== false ? held[scheme.key] : void 0;
|
|
353
|
+
const value = typeof stated === "function" ? await stated() : stated;
|
|
354
|
+
return typeof held.auth === "function" ? held.auth(scheme, value) : value;
|
|
355
|
+
}
|
|
356
|
+
function unauthenticated(open, options) {
|
|
357
|
+
return `This call was sent with no credential.${advice(open, options)}`;
|
|
358
|
+
}
|
|
359
|
+
function writtenByHand(scheme, request) {
|
|
360
|
+
return (scheme.in ?? "header") === "header" && request.meta[(scheme.name ?? "Authorization").toLowerCase()] !== void 0;
|
|
361
|
+
}
|
|
362
|
+
function authFeature(options) {
|
|
363
|
+
return {
|
|
364
|
+
name: "auth",
|
|
365
|
+
async onPrepare(request, ctx) {
|
|
366
|
+
const declared = request.callable.auth;
|
|
367
|
+
if (!declared?.length) return;
|
|
368
|
+
const { auth, environment } = request.options;
|
|
369
|
+
const alternatives = candidates(declared, environment, options).map((schemes) => schemes.filter((scheme) => !writtenByHand(scheme, request)));
|
|
370
|
+
const asked = /* @__PURE__ */ new Map();
|
|
371
|
+
function ask(scheme) {
|
|
372
|
+
const key = scheme.key ?? scheme;
|
|
373
|
+
let token = asked.get(key);
|
|
374
|
+
if (!token) {
|
|
375
|
+
token = tokenFor(scheme, request.options, options);
|
|
376
|
+
asked.set(key, token);
|
|
377
|
+
}
|
|
378
|
+
return token;
|
|
379
|
+
}
|
|
380
|
+
if (auth !== null) {
|
|
381
|
+
for (const schemes of alternatives) {
|
|
382
|
+
const placed = await credentialsFor(schemes, ask, options);
|
|
383
|
+
if (!placed) continue;
|
|
384
|
+
for (const { credential } of placed) ctx.binding.applyAuth(credential, request);
|
|
385
|
+
request.placed = placed;
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
await void 0;
|
|
389
|
+
}
|
|
390
|
+
const all = offered(declared.map(schemesOf));
|
|
391
|
+
if (auth === null || request.callable.authOptional) {
|
|
392
|
+
request.unauthenticated = () => unauthenticated(all, options);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
throw new MissingCredentialError(missing(all, options), keysOf(all));
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
//#endregion
|
|
400
|
+
//#region src/internal/feature/interceptors.ts
|
|
401
|
+
function interceptorsOf(request) {
|
|
402
|
+
return request.options["interceptors"];
|
|
403
|
+
}
|
|
404
|
+
function interceptorsFeature() {
|
|
405
|
+
return {
|
|
406
|
+
name: "interceptors",
|
|
407
|
+
async onError(error, request) {
|
|
408
|
+
let current = error;
|
|
409
|
+
for (const fn of interceptorsOf(request)?.error ?? []) current = await fn(current, request);
|
|
410
|
+
return current;
|
|
411
|
+
},
|
|
412
|
+
async onRequest(request) {
|
|
413
|
+
for (const fn of interceptorsOf(request)?.request ?? []) await fn(request);
|
|
414
|
+
},
|
|
415
|
+
async onResult(result, request) {
|
|
416
|
+
let current = result;
|
|
417
|
+
for (const fn of interceptorsOf(request)?.response ?? []) current = await fn(current, request);
|
|
418
|
+
return current;
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
//#endregion
|
|
423
|
+
//#region src/internal/feature/logger.ts
|
|
424
|
+
const LEVELS = {
|
|
425
|
+
debug: 40,
|
|
426
|
+
error: 10,
|
|
427
|
+
info: 30,
|
|
428
|
+
off: 0,
|
|
429
|
+
warn: 20
|
|
430
|
+
};
|
|
431
|
+
function describeCall(request) {
|
|
432
|
+
const method = request.callable.method?.toUpperCase();
|
|
433
|
+
const address = typeof request.address === "string" ? safeAddress(request.address) : "";
|
|
434
|
+
return method ? `${method} ${address}` : address;
|
|
435
|
+
}
|
|
436
|
+
const ADDRESS_RE = /\b[a-z][a-z\d+.-]*:\/\/[^\s"'<>]+/gi;
|
|
437
|
+
function failureText(error) {
|
|
438
|
+
const messages = [];
|
|
439
|
+
const seen = /* @__PURE__ */ new Set();
|
|
440
|
+
let current = error;
|
|
441
|
+
while (current !== void 0 && current !== null && !seen.has(current)) {
|
|
442
|
+
seen.add(current);
|
|
443
|
+
const failed = current instanceof Error;
|
|
444
|
+
messages.push(failed ? current.message : String(current));
|
|
445
|
+
current = failed ? current.cause : void 0;
|
|
446
|
+
}
|
|
447
|
+
return messages.join(" Caused by: ").replace(ADDRESS_RE, safeAddress);
|
|
448
|
+
}
|
|
449
|
+
function declaredHeaders(callable) {
|
|
450
|
+
const names = /* @__PURE__ */ new Set();
|
|
451
|
+
for (const requirement of callable.auth ?? []) for (const scheme of Array.isArray(requirement) ? requirement : [requirement]) if (scheme.in === "header" && scheme.name) names.add(scheme.name.toLowerCase());
|
|
452
|
+
return names;
|
|
453
|
+
}
|
|
454
|
+
const SECRET_HEADERS = [
|
|
455
|
+
"authorization",
|
|
456
|
+
"cookie",
|
|
457
|
+
"proxy-authorization",
|
|
458
|
+
"set-cookie"
|
|
459
|
+
];
|
|
460
|
+
function isSecret(name, declared) {
|
|
461
|
+
const lower = name.toLowerCase();
|
|
462
|
+
return SECRET_HEADERS.includes(lower) || declared.has(lower) || lower.endsWith("-key") || lower.endsWith("-token") || lower.endsWith("-secret");
|
|
463
|
+
}
|
|
464
|
+
function safeHeaders(meta, callable) {
|
|
465
|
+
const declared = declaredHeaders(callable);
|
|
466
|
+
const safe = {};
|
|
467
|
+
for (const [name, value] of Object.entries(meta)) safe[name] = isSecret(name, declared) ? "***" : value;
|
|
468
|
+
return safe;
|
|
469
|
+
}
|
|
470
|
+
function callLog(id, request, at, sink) {
|
|
471
|
+
const label = describeCall(request);
|
|
472
|
+
let started = Date.now();
|
|
473
|
+
function line(text) {
|
|
474
|
+
return `[${id}] ${label} ${text}`;
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
ended(error) {
|
|
478
|
+
const ms = Date.now() - started;
|
|
479
|
+
if (error !== void 0) {
|
|
480
|
+
if (at >= LEVELS.error) sink.error(line(`stream failed after ${ms} ms: ${failureText(error)}`));
|
|
481
|
+
} else if (at >= LEVELS.info) sink.info(line(`stream ended after ${ms} ms`));
|
|
482
|
+
},
|
|
483
|
+
failed(error) {
|
|
484
|
+
if (at >= LEVELS.error) sink.error(line(`failed: ${failureText(error)}`));
|
|
485
|
+
},
|
|
486
|
+
replied(response) {
|
|
487
|
+
const ms = Date.now() - started;
|
|
488
|
+
if (response?.redirected && at >= LEVELS.warn) sink.warn(line(`was redirected to ${safeAddress(response.url)}`));
|
|
489
|
+
if (at >= LEVELS.info) sink.info(line(`→ ${response?.status ?? "?"} in ${ms} ms`));
|
|
490
|
+
if (at >= LEVELS.debug) sink.debug(`[${id}] received`, {
|
|
491
|
+
headers: safeHeaders(Object.fromEntries(response?.headers ?? []), request.callable),
|
|
492
|
+
ms,
|
|
493
|
+
status: response?.status
|
|
494
|
+
});
|
|
495
|
+
},
|
|
496
|
+
retrying(wait, retry, retries, after) {
|
|
497
|
+
if (at < LEVELS.warn) return;
|
|
498
|
+
const again = `retrying in ${Math.round(wait)} ms (${retry} of ${retries})`;
|
|
499
|
+
if (after?.status !== void 0) sink.warn(line(`→ ${after.status}, ${again}`));
|
|
500
|
+
else if (after?.error !== void 0) sink.warn(line(`failed: ${failureText(after.error)}, ${again}`));
|
|
501
|
+
else sink.warn(line(again));
|
|
502
|
+
},
|
|
503
|
+
sending() {
|
|
504
|
+
started = Date.now();
|
|
505
|
+
if (at >= LEVELS.info) sink.info(`[${id}] ${label}`);
|
|
506
|
+
if (at >= LEVELS.debug) sink.debug(`[${id}] sending`, { headers: safeHeaders(request.meta, request.callable) });
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function readLevel(named) {
|
|
511
|
+
const spelled = named.toLowerCase();
|
|
512
|
+
if (spelled === "warning") return "warn";
|
|
513
|
+
return Object.prototype.hasOwnProperty.call(LEVELS, spelled) ? spelled : void 0;
|
|
514
|
+
}
|
|
515
|
+
function resolveLogging(base, request, unknown) {
|
|
516
|
+
const named = String(request.options["logLevel"] || base.level || "off");
|
|
517
|
+
const level = readLevel(named);
|
|
518
|
+
const sink = request.options["logger"] ?? base.logger ?? globalThis.console;
|
|
519
|
+
if (level === void 0 && sink && !unknown.has(named)) {
|
|
520
|
+
unknown.add(named);
|
|
521
|
+
sink.warn(`Unknown log level "${named}": nothing is logged. Use "debug", "info", "warn", "error" or "off".`);
|
|
522
|
+
}
|
|
523
|
+
if (level === void 0 || level === "off" || !sink) return void 0;
|
|
524
|
+
return {
|
|
525
|
+
at: LEVELS[level],
|
|
526
|
+
sink
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
async function written(request, next) {
|
|
530
|
+
const { log } = request;
|
|
531
|
+
if (!log) return next(request);
|
|
532
|
+
const result = await next(request);
|
|
533
|
+
log.replied(result.response);
|
|
534
|
+
return result;
|
|
535
|
+
}
|
|
536
|
+
function loggerFeature(options = {}) {
|
|
537
|
+
let counter = 0;
|
|
538
|
+
const unknown = /* @__PURE__ */ new Set();
|
|
539
|
+
const warned = /* @__PURE__ */ new Set();
|
|
540
|
+
return {
|
|
541
|
+
name: "logger",
|
|
542
|
+
async onError(error, request) {
|
|
543
|
+
request.log?.failed(error);
|
|
544
|
+
return error;
|
|
545
|
+
},
|
|
546
|
+
onOpen: written,
|
|
547
|
+
onPrepare(request) {
|
|
548
|
+
const on = resolveLogging(options, request, unknown);
|
|
549
|
+
if (!on) return;
|
|
550
|
+
const id = `req_${++counter}`;
|
|
551
|
+
request.log = callLog(id, request, on.at, on.sink);
|
|
552
|
+
const { callable } = request;
|
|
553
|
+
if (callable.deprecated && on.at >= LEVELS.warn && !warned.has(callable)) {
|
|
554
|
+
warned.add(callable);
|
|
555
|
+
const method = callable.method?.toUpperCase();
|
|
556
|
+
const operation = method ? `${method} ${callable.address}` : callable.address;
|
|
557
|
+
on.sink.warn(`[${id}] ${operation} is deprecated`);
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
onSend: written
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function askedFor(config, result) {
|
|
564
|
+
const headers = config.retryAfter;
|
|
565
|
+
if (headers === false || !result.response) return void 0;
|
|
566
|
+
return retryAfterDelay(result.response, headers === true ? void 0 : headers);
|
|
567
|
+
}
|
|
568
|
+
const IDEMPOTENT_METHODS = [
|
|
569
|
+
"DELETE",
|
|
570
|
+
"GET",
|
|
571
|
+
"HEAD",
|
|
572
|
+
"OPTIONS",
|
|
573
|
+
"PUT",
|
|
574
|
+
"TRACE"
|
|
575
|
+
];
|
|
576
|
+
function isRepeatable(config, request) {
|
|
577
|
+
const method = request.callable.method?.toUpperCase();
|
|
578
|
+
if (!method) return false;
|
|
579
|
+
if ((config.methods ?? IDEMPOTENT_METHODS).includes(method)) return true;
|
|
580
|
+
const { idempotency } = request.callable;
|
|
581
|
+
return idempotency !== void 0 && request.meta[idempotency] !== void 0;
|
|
582
|
+
}
|
|
583
|
+
function retriesFor(base, override, request) {
|
|
584
|
+
if (override && override !== true && override.maxRetries !== void 0) return Math.max(0, override.maxRetries);
|
|
585
|
+
const retries = request.options["maxRetries"];
|
|
586
|
+
if (typeof retries === "number" && Number.isFinite(retries)) return Math.max(0, retries);
|
|
587
|
+
return base.maxRetries ?? 2;
|
|
588
|
+
}
|
|
589
|
+
function resolveRetry(base, request) {
|
|
590
|
+
const override = request.options["retry"];
|
|
591
|
+
if (override === false) return void 0;
|
|
592
|
+
const declared = request.callable.retry;
|
|
593
|
+
if (declared === false && override === void 0) return void 0;
|
|
594
|
+
const stated = declared ? {
|
|
595
|
+
...base,
|
|
596
|
+
...declared
|
|
597
|
+
} : base;
|
|
598
|
+
const config = override && override !== true ? {
|
|
599
|
+
...stated,
|
|
600
|
+
...override
|
|
601
|
+
} : stated;
|
|
602
|
+
const maxRetries = retriesFor(stated, override, request);
|
|
603
|
+
return maxRetries < 1 ? void 0 : {
|
|
604
|
+
...config,
|
|
605
|
+
maxRetries
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
const REFUSED_STATUSES = [
|
|
609
|
+
408,
|
|
610
|
+
425,
|
|
611
|
+
429
|
|
612
|
+
];
|
|
613
|
+
const RETRY_HEADER = "x-should-retry";
|
|
614
|
+
const PERMANENT_STATUSES = [
|
|
615
|
+
501,
|
|
616
|
+
505,
|
|
617
|
+
506,
|
|
618
|
+
508,
|
|
619
|
+
510,
|
|
620
|
+
511
|
|
621
|
+
];
|
|
622
|
+
function worthAnotherAttempt(status) {
|
|
623
|
+
if (status >= 500) return !PERMANENT_STATUSES.includes(status);
|
|
624
|
+
return REFUSED_STATUSES.includes(status);
|
|
625
|
+
}
|
|
626
|
+
function shouldRetry(config, result, request, repeatable) {
|
|
627
|
+
const { response } = result;
|
|
628
|
+
if (!response) return false;
|
|
629
|
+
if (!repeatable && !REFUSED_STATUSES.includes(response.status)) return false;
|
|
630
|
+
if (config.retryOn) return config.retryOn(result, request);
|
|
631
|
+
if (response.headers.get(RETRY_HEADER) === "false") return false;
|
|
632
|
+
const { statuses } = config;
|
|
633
|
+
return statuses ? statuses.includes(response.status) : worthAnotherAttempt(response.status);
|
|
634
|
+
}
|
|
635
|
+
function shouldRetryError(config, error, request, repeatable) {
|
|
636
|
+
if (error instanceof ElmoError && !(error instanceof TransportError)) return false;
|
|
637
|
+
const kind = classifyError(error);
|
|
638
|
+
if (kind === "abort") return false;
|
|
639
|
+
if (failureOf(error).reachedServer !== "no" && !repeatable) return false;
|
|
640
|
+
if (config.retryOn) return config.retryOn({ error }, request);
|
|
641
|
+
return kind !== "timeout" || config.retryOnTimeout === true;
|
|
642
|
+
}
|
|
643
|
+
function retryFeature(options = {}) {
|
|
644
|
+
return {
|
|
645
|
+
name: "retry",
|
|
646
|
+
async onSend(request, next) {
|
|
647
|
+
const config = isStreamed(request.body) ? void 0 : resolveRetry(options, request);
|
|
648
|
+
if (!config) return next(request);
|
|
649
|
+
const attempts = (config.maxRetries ?? 2) + 1;
|
|
650
|
+
const repeatable = isRepeatable(config, request);
|
|
651
|
+
const started = Date.now();
|
|
652
|
+
for (let attempt = 0;; attempt++) {
|
|
653
|
+
const last = attempt >= attempts - 1;
|
|
654
|
+
let result;
|
|
655
|
+
let threw = false;
|
|
656
|
+
let wait;
|
|
657
|
+
if (attempt) request.attempt = attempt;
|
|
658
|
+
try {
|
|
659
|
+
result = await next(request);
|
|
660
|
+
if (last || !shouldRetry(config, result, request, repeatable)) return result;
|
|
661
|
+
wait = config.retryDelay?.(result, request, attempt);
|
|
662
|
+
if (wait === void 0) {
|
|
663
|
+
wait = askedFor(config, result);
|
|
664
|
+
if (exceedsMaxRetryAfter(wait, config.maxRetryAfter)) return result;
|
|
665
|
+
}
|
|
666
|
+
} catch (error) {
|
|
667
|
+
if (last || !shouldRetryError(config, error, request, repeatable)) throw error;
|
|
668
|
+
threw = true;
|
|
669
|
+
result = { error };
|
|
670
|
+
wait = config.retryDelay?.(result, request, attempt);
|
|
671
|
+
}
|
|
672
|
+
wait ??= backoffDelay(attempt, config);
|
|
673
|
+
if (config.budget !== void 0 && Date.now() - started + wait >= config.budget) {
|
|
674
|
+
if (threw) throw result.error;
|
|
675
|
+
return result;
|
|
676
|
+
}
|
|
677
|
+
discardBody(result);
|
|
678
|
+
request.log?.retrying(wait, attempt + 1, attempts - 1, result);
|
|
679
|
+
await sleep(wait, request.signal).catch((reason) => {
|
|
680
|
+
throw calledOffWith(reason);
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
//#endregion
|
|
687
|
+
//#region src/internal/feature/rules.ts
|
|
688
|
+
const COUNTED = {
|
|
689
|
+
characters: "character",
|
|
690
|
+
entries: "entry",
|
|
691
|
+
items: "item"
|
|
692
|
+
};
|
|
693
|
+
function brokenBound(rule, size) {
|
|
694
|
+
if (rule.min !== void 0 && size < rule.min) return ["at least", rule.min];
|
|
695
|
+
if (rule.max !== void 0 && size > rule.max) return ["at most", rule.max];
|
|
696
|
+
if (rule.above !== void 0 && size <= rule.above) return ["more than", rule.above];
|
|
697
|
+
if (rule.below !== void 0 && size >= rule.below) return ["less than", rule.below];
|
|
698
|
+
}
|
|
699
|
+
function isPlainObject(value) {
|
|
700
|
+
if (typeof value !== "object" || value === null) return false;
|
|
701
|
+
const prototype = Object.getPrototypeOf(value);
|
|
702
|
+
return prototype === Object.prototype || prototype === null;
|
|
703
|
+
}
|
|
704
|
+
function inputOf(options, name) {
|
|
705
|
+
if (name === void 0) return options.body;
|
|
706
|
+
return isPlainObject(options.body) ? options.body[name] : void 0;
|
|
707
|
+
}
|
|
708
|
+
function sizeOf(value, of) {
|
|
709
|
+
if (of === void 0) return typeof value === "number" || typeof value === "bigint" ? value : void 0;
|
|
710
|
+
if (of === "characters") return typeof value === "string" ? Array.from(value).length : void 0;
|
|
711
|
+
if (of === "items") return Array.isArray(value) ? value.length : void 0;
|
|
712
|
+
if (!isPlainObject(value)) return void 0;
|
|
713
|
+
return Object.values(value).filter((entry) => entry !== void 0).length;
|
|
714
|
+
}
|
|
715
|
+
function rulesFeature() {
|
|
716
|
+
return {
|
|
717
|
+
name: "rules",
|
|
718
|
+
onOptions(options, callable) {
|
|
719
|
+
for (const rule of callable.rules ?? []) {
|
|
720
|
+
const size = sizeOf(inputOf(options, rule.name), rule.of);
|
|
721
|
+
if (size === void 0) continue;
|
|
722
|
+
const broken = brokenBound(rule, size);
|
|
723
|
+
if (!broken) continue;
|
|
724
|
+
const [bound, limit] = broken;
|
|
725
|
+
const held = rule.name === void 0 ? "The request body" : `\`${rule.name}\``;
|
|
726
|
+
const stated = rule.of === void 0 ? `is ${bound} ${limit}` : `takes ${bound} ${limit} ${limit === 1 ? COUNTED[rule.of] : rule.of}`;
|
|
727
|
+
throw refusedInput(held, stated, size);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
//#endregion
|
|
733
|
+
//#region src/internal/feature/timeout.ts
|
|
734
|
+
function describeTarget(request) {
|
|
735
|
+
const { method } = request.callable;
|
|
736
|
+
if (typeof request.address !== "string") return void 0;
|
|
737
|
+
const address = safeAddress(request.address);
|
|
738
|
+
return method ? `${method.toUpperCase()} "${address}"` : `"${address}"`;
|
|
739
|
+
}
|
|
740
|
+
function operationKey({ address, method }) {
|
|
741
|
+
return method ? `${method.toUpperCase()} ${address}` : address;
|
|
742
|
+
}
|
|
743
|
+
function resolveTimeout(base, request) {
|
|
744
|
+
const own = request.callable.timeout ?? base.ms ?? 6e4;
|
|
745
|
+
const option = request.options["timeout"];
|
|
746
|
+
const ms = typeof option === "function" ? option(operationKey(request.callable), own) ?? own : option ?? own;
|
|
747
|
+
if (ms === false) return void 0;
|
|
748
|
+
return ms > 0 && Number.isFinite(ms) ? ms : void 0;
|
|
749
|
+
}
|
|
750
|
+
function watchedReply(response, wait, done) {
|
|
751
|
+
const reader = response.body.getReader();
|
|
752
|
+
const body = new ReadableStream({
|
|
753
|
+
cancel(reason) {
|
|
754
|
+
done();
|
|
755
|
+
return reader.cancel(reason);
|
|
756
|
+
},
|
|
757
|
+
async pull(controller) {
|
|
758
|
+
const step = await wait(reader.read()).catch((error) => {
|
|
759
|
+
done();
|
|
760
|
+
reader.cancel(error).catch(done);
|
|
761
|
+
throw error;
|
|
762
|
+
});
|
|
763
|
+
if (step.done) {
|
|
764
|
+
done();
|
|
765
|
+
controller.close();
|
|
766
|
+
} else controller.enqueue(step.value);
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
return replyWith(body, response);
|
|
770
|
+
}
|
|
771
|
+
function timeoutFeature(options = {}) {
|
|
772
|
+
async function bounded(request, next, stream) {
|
|
773
|
+
const ms = resolveTimeout(options, request);
|
|
774
|
+
if (ms === void 0) return next(request);
|
|
775
|
+
const delay = Math.min(ms, TIMER_LIMIT);
|
|
776
|
+
let left = delay;
|
|
777
|
+
const caller = request.signal;
|
|
778
|
+
const controller = new AbortController();
|
|
779
|
+
const { signal } = controller;
|
|
780
|
+
function abortWithCaller() {
|
|
781
|
+
controller.abort(caller?.reason);
|
|
782
|
+
}
|
|
783
|
+
if (caller?.aborted) abortWithCaller();
|
|
784
|
+
caller?.addEventListener("abort", abortWithCaller, { once: true });
|
|
785
|
+
const abandoned = new Promise((_, reject) => {
|
|
786
|
+
function giveUp() {
|
|
787
|
+
reject(calledOffWith(signal.reason));
|
|
788
|
+
}
|
|
789
|
+
if (signal.aborted) giveUp();
|
|
790
|
+
else signal.addEventListener("abort", giveUp, { once: true });
|
|
791
|
+
});
|
|
792
|
+
abandoned.catch(() => {});
|
|
793
|
+
function expire() {
|
|
794
|
+
controller.abort(new TimeoutError(ms, void 0, describeTarget(request)));
|
|
795
|
+
}
|
|
796
|
+
async function wait(pending) {
|
|
797
|
+
const started = Date.now();
|
|
798
|
+
const timer = setTimeout(expire, left);
|
|
799
|
+
try {
|
|
800
|
+
return await Promise.race([pending, abandoned]);
|
|
801
|
+
} finally {
|
|
802
|
+
clearTimeout(timer);
|
|
803
|
+
left = stream ? delay : left - (Date.now() - started);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
function done() {
|
|
807
|
+
caller?.removeEventListener("abort", abortWithCaller);
|
|
808
|
+
}
|
|
809
|
+
request.signal = signal;
|
|
810
|
+
const result = await wait(next(request)).catch((error) => {
|
|
811
|
+
done();
|
|
812
|
+
throw error;
|
|
813
|
+
}).finally(() => {
|
|
814
|
+
request.signal = caller;
|
|
815
|
+
});
|
|
816
|
+
const { response } = result;
|
|
817
|
+
if (response?.body && (stream || request.options.unread)) return {
|
|
818
|
+
...result,
|
|
819
|
+
response: watchedReply(response, wait, done)
|
|
820
|
+
};
|
|
821
|
+
done();
|
|
822
|
+
return result;
|
|
823
|
+
}
|
|
824
|
+
return {
|
|
825
|
+
name: "timeout",
|
|
826
|
+
onOpen: (request, next) => bounded(request, next, true),
|
|
827
|
+
onSend: (request, next) => bounded(request, next, false)
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
//#endregion
|
|
831
|
+
//#region src/internal/transport/fetch.ts
|
|
832
|
+
function checkAddress(address) {
|
|
833
|
+
let url;
|
|
834
|
+
try {
|
|
835
|
+
url = new URL(address, globalThis.location?.href);
|
|
836
|
+
} catch {
|
|
837
|
+
throw unsendableAddress(safeAddress(address), "is not a full address", "Start the base URL with `https://`.");
|
|
838
|
+
}
|
|
839
|
+
const shown = `${url.protocol}//${url.host}${url.pathname}`;
|
|
840
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") throw unsendableAddress(shown, `uses \`${url.protocol}\`, which fetch does not send`, "Start the base URL with `https://`.");
|
|
841
|
+
if (url.username || url.password) throw unsendableAddress(shown, "holds a user name or password, which fetch refuses", "Pass the credential as an option instead.");
|
|
842
|
+
}
|
|
843
|
+
const OWNED = [
|
|
844
|
+
"body",
|
|
845
|
+
"headers",
|
|
846
|
+
"method",
|
|
847
|
+
"signal"
|
|
848
|
+
];
|
|
849
|
+
function extraInit(request) {
|
|
850
|
+
const extra = request.options["fetchOptions"];
|
|
851
|
+
if (!extra) return void 0;
|
|
852
|
+
const rest = { ...extra };
|
|
853
|
+
for (const key of OWNED) delete rest[key];
|
|
854
|
+
return rest;
|
|
855
|
+
}
|
|
856
|
+
function toRequestInit(request) {
|
|
857
|
+
return {
|
|
858
|
+
...extraInit(request),
|
|
859
|
+
...request.body !== void 0 && { body: request.body },
|
|
860
|
+
...isStreamed(request.body) && { duplex: "half" },
|
|
861
|
+
headers: metadataHeaders(request.meta),
|
|
862
|
+
method: (request.callable.method ?? "get").toUpperCase(),
|
|
863
|
+
...request.signal !== void 0 && { signal: request.signal }
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
function createFetchTransport(fetchFn) {
|
|
867
|
+
function send(request) {
|
|
868
|
+
const call = request.options["fetch"] ?? fetchFn ?? globalThis.fetch;
|
|
869
|
+
if (call === globalThis.fetch) checkAddress(request.address);
|
|
870
|
+
return call(request.address, toRequestInit(request));
|
|
871
|
+
}
|
|
872
|
+
return {
|
|
873
|
+
name: "fetch",
|
|
874
|
+
unary: send
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
//#endregion
|
|
878
|
+
//#region src/client.ts
|
|
879
|
+
/** The version of this package. */
|
|
880
|
+
const VERSION = "0.1.0";
|
|
881
|
+
const declaredCredentials = { apiKey: { variable: "ELMO_API_KEY" } };
|
|
882
|
+
let fetchTransportInstance;
|
|
883
|
+
function fetchTransport() {
|
|
884
|
+
return fetchTransportInstance ??= createFetchTransport();
|
|
885
|
+
}
|
|
886
|
+
let clientInstance;
|
|
887
|
+
function client() {
|
|
888
|
+
return clientInstance ??= createClient({
|
|
889
|
+
codecs: [jsonCodec],
|
|
890
|
+
credentials: declaredCredentials,
|
|
891
|
+
defaults: createConfig({
|
|
892
|
+
baseURL: "/api/v1",
|
|
893
|
+
headers: { "user-agent": `elmo-sdk/${VERSION} (typescript)` }
|
|
894
|
+
}),
|
|
895
|
+
env: {
|
|
896
|
+
baseURL: "ELMO_BASE_URL",
|
|
897
|
+
logLevel: "ELMO_LOG"
|
|
898
|
+
},
|
|
899
|
+
features: [
|
|
900
|
+
rulesFeature(),
|
|
901
|
+
authFeature({ schemes: declaredCredentials }),
|
|
902
|
+
retryFeature(),
|
|
903
|
+
loggerFeature(),
|
|
904
|
+
timeoutFeature({ ms: { overrides: [{
|
|
905
|
+
include: { openapi: { operations: ["GET /brands/{brandId}/opportunities", "POST /tools/analyze"] } },
|
|
906
|
+
ms: 6e5
|
|
907
|
+
}] } }),
|
|
908
|
+
interceptorsFeature(),
|
|
909
|
+
validateFeature()
|
|
910
|
+
],
|
|
911
|
+
guard: refuseBrowser,
|
|
912
|
+
protocols: { rest: {
|
|
913
|
+
binding: createRestBinding(simpleURL),
|
|
914
|
+
transport: fetchTransport()
|
|
915
|
+
} }
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* A client for this API. Options given here apply to every call made
|
|
920
|
+
* through it. Pass it to a call, or to an SDK, in place of the default one.
|
|
921
|
+
*
|
|
922
|
+
* @example
|
|
923
|
+
* const elmo = createElmoClient({ apiKey: '…' });
|
|
924
|
+
*/
|
|
925
|
+
function createElmoClient(options) {
|
|
926
|
+
return clientFor(options, client());
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* The Elmo API, as one object to call through.
|
|
930
|
+
*
|
|
931
|
+
* Pass `apiKey`, or set `ELMO_API_KEY` and pass nothing.
|
|
932
|
+
*
|
|
933
|
+
* Options given here apply to every call it makes.
|
|
934
|
+
*
|
|
935
|
+
* @example
|
|
936
|
+
* const elmo = new Elmo();
|
|
937
|
+
* await elmo.brands.list();
|
|
938
|
+
*/
|
|
939
|
+
var Elmo = class extends ElmoResource {
|
|
940
|
+
constructor(args) {
|
|
941
|
+
super(clientFor(args, client()));
|
|
942
|
+
}
|
|
943
|
+
_brands;
|
|
944
|
+
/** Manage brand records */
|
|
945
|
+
get brands() {
|
|
946
|
+
return this._brands ??= new Brands(this.client);
|
|
947
|
+
}
|
|
948
|
+
_competitors;
|
|
949
|
+
/** Manage brand competitors */
|
|
950
|
+
get competitors() {
|
|
951
|
+
return this._competitors ??= new Competitors(this.client);
|
|
952
|
+
}
|
|
953
|
+
_me;
|
|
954
|
+
/** What the calling key is and what it may reach */
|
|
955
|
+
get me() {
|
|
956
|
+
return this._me ??= new Me(this.client);
|
|
957
|
+
}
|
|
958
|
+
_models;
|
|
959
|
+
/** The answer engines this deployment can track */
|
|
960
|
+
get models() {
|
|
961
|
+
return this._models ??= new Models(this.client);
|
|
962
|
+
}
|
|
963
|
+
_organizations;
|
|
964
|
+
/** Organizations, their plan limits, and their usage */
|
|
965
|
+
get organizations() {
|
|
966
|
+
return this._organizations ??= new Organizations(this.client);
|
|
967
|
+
}
|
|
968
|
+
_prompts;
|
|
969
|
+
/** Manage brand prompts */
|
|
970
|
+
get prompts() {
|
|
971
|
+
return this._prompts ??= new Prompts(this.client);
|
|
972
|
+
}
|
|
973
|
+
_reports;
|
|
974
|
+
/** Generate and retrieve AI Share of Voice reports */
|
|
975
|
+
get reports() {
|
|
976
|
+
return this._reports ??= new Reports(this.client);
|
|
977
|
+
}
|
|
978
|
+
_tools;
|
|
979
|
+
/** One-shot helpers (e.g. brand analysis) that don't persist anything */
|
|
980
|
+
get tools() {
|
|
981
|
+
return this._tools ??= new Tools(this.client);
|
|
982
|
+
}
|
|
983
|
+
/** A failure the API answered with, so there is a status and a reply to read. */
|
|
984
|
+
static APIError = APIError;
|
|
985
|
+
/**
|
|
986
|
+
* Thrown when the caller called the request off before the API answered.
|
|
987
|
+
*
|
|
988
|
+
* Not a {@link TransportError}, so code that retries or reports a failed
|
|
989
|
+
* connection leaves a call the caller cancelled alone.
|
|
990
|
+
*/
|
|
991
|
+
static AbortError = AbortError;
|
|
992
|
+
/** No usable credential reached the API: it was missing, unreadable, or rejected. */
|
|
993
|
+
static AuthenticationError = AuthenticationError;
|
|
994
|
+
/** The API could not read the request, so it did not act on it. */
|
|
995
|
+
static BadRequestError = BadRequestError;
|
|
996
|
+
/** The call collided with the resource's current state: a duplicate, or a concurrent change. */
|
|
997
|
+
static ConflictError = ConflictError;
|
|
998
|
+
/** A reply from the API that this SDK could not read, whatever its status. */
|
|
999
|
+
static DecodeError = DecodeError;
|
|
1000
|
+
/** The base of every error this SDK throws, including one from a call that never arrived. */
|
|
1001
|
+
static ElmoError = ElmoError;
|
|
1002
|
+
/** The API failed after accepting the call. Every status from 500 up arrives as this. */
|
|
1003
|
+
static InternalServerError = InternalServerError;
|
|
1004
|
+
/** Thrown before the request goes out, when no credential satisfied the call. */
|
|
1005
|
+
static MissingCredentialError = MissingCredentialError;
|
|
1006
|
+
/** Nothing is at this address, or the credential may not see what is. */
|
|
1007
|
+
static NotFoundError = NotFoundError;
|
|
1008
|
+
/** The plan or the quota on this account does not cover the call. */
|
|
1009
|
+
static PaymentRequiredError = PaymentRequiredError;
|
|
1010
|
+
/** The credential was accepted, but it does not grant this call. */
|
|
1011
|
+
static PermissionDeniedError = PermissionDeniedError;
|
|
1012
|
+
/** Too many calls. {@link APIError.retryAfterMs} carries how long the API asked to wait. */
|
|
1013
|
+
static RateLimitError = RateLimitError;
|
|
1014
|
+
/**
|
|
1015
|
+
* Thrown when a call waited on the API for as long as it allowed, from sending
|
|
1016
|
+
* the request to the last byte of the reply.
|
|
1017
|
+
*
|
|
1018
|
+
* A reply with status 408 is an {@link APIError}, not one of these.
|
|
1019
|
+
*/
|
|
1020
|
+
static TimeoutError = TimeoutError;
|
|
1021
|
+
/** Thrown when no whole answer arrived, so there is no reply to go on. */
|
|
1022
|
+
static TransportError = TransportError;
|
|
1023
|
+
/** The request was read, and its contents were rejected. */
|
|
1024
|
+
static UnprocessableEntityError = UnprocessableEntityError;
|
|
1025
|
+
};
|
|
1026
|
+
//#endregion
|
|
1027
|
+
export { APIError, AbortError, AuthenticationError, BadRequestError, ConflictError, DecodeError, Elmo, ElmoError, InternalServerError, MissingCredentialError, NotFoundError, PaymentRequiredError, PermissionDeniedError, RateLimitError, TimeoutError, TransportError, UnprocessableEntityError, VERSION, createElmoClient };
|
|
1028
|
+
|
|
1029
|
+
//# sourceMappingURL=index.mjs.map
|