yandex-kit-core 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 +21 -0
- package/README.md +35 -0
- package/dist/client.d.ts +54 -0
- package/dist/client.js +266 -0
- package/dist/errors.d.ts +19 -0
- package/dist/errors.js +30 -0
- package/dist/generated/registry.json +3166 -0
- package/dist/generated/spec.json +1 -0
- package/dist/generated/types.d.ts +12928 -0
- package/dist/generated/types.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/registry.d.ts +34 -0
- package/dist/registry.js +22 -0
- package/dist/validate.d.ts +8 -0
- package/dist/validate.js +133 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zinnur Temerbekov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# yandex-kit-core
|
|
2
|
+
|
|
3
|
+
Типизированный клиент **API Яндекс KIT** — конструктора интернет-магазинов ([kit.yandex.ru](https://kit.yandex.ru)), построенный по официальной OpenAPI-спеке (133 операции). Основа MCP-сервера [`mcp-yandex-kit`](https://www.npmjs.com/package/mcp-yandex-kit), но пригоден и как самостоятельный клиент.
|
|
4
|
+
|
|
5
|
+
Что внутри:
|
|
6
|
+
|
|
7
|
+
- **`KitClient`** — Bearer-авторизация, таймаут на попытку, лимитер 3 rps (token bucket), ретраи с бэкоффом на сетевых ошибках, 5xx, 429 и `LIMIT_EXCEEDED` (который KIT возвращает с **HTTP 400**), автопагинация `listAll`, корректные content-type (`merge-patch+json`, `multipart/form-data`) там, где их требует API.
|
|
8
|
+
- **`KitApiError`** — типизированные ошибки API: `status`, `code`, `message`, `trace_id`.
|
|
9
|
+
- **ajv-валидация** — `validateRequestBody` / `resolveOperationSchema` проверяют тела запросов по схемам спеки **до** отправки в сеть.
|
|
10
|
+
- **Сгенерированный реестр операций** — метод, путь, параметры и content-type каждой из 133 операций.
|
|
11
|
+
|
|
12
|
+
## Использование
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
import { KitClient } from "yandex-kit-core";
|
|
16
|
+
|
|
17
|
+
const client = new KitClient({ token: process.env.YANDEX_KIT_TOKEN });
|
|
18
|
+
|
|
19
|
+
// Любая операция по её operationId из спеки
|
|
20
|
+
const store = await client.call("GetStore");
|
|
21
|
+
|
|
22
|
+
// Автопагинация списков (per_page=100, до maxItems элементов)
|
|
23
|
+
const { items } = await client.listAll("GetProducts", {}, { maxItems: 500 });
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Требования: Node.js 20+.
|
|
27
|
+
|
|
28
|
+
## Документация
|
|
29
|
+
|
|
30
|
+
- [Репозиторий проекта](https://github.com/ztemerbekov/a1-yandex-kit-skills) — полный README, MCP-сервер, агентские скиллы.
|
|
31
|
+
- [Официальная документация Яндекс KIT](https://yandex.ru/dev/kit/ru/) — авторизация, лимиты, ошибки, OpenAPI-справочник.
|
|
32
|
+
|
|
33
|
+
## Лицензия
|
|
34
|
+
|
|
35
|
+
MIT — см. [LICENSE](https://github.com/ztemerbekov/a1-yandex-kit-skills/blob/main/LICENSE).
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface KitClientOptions {
|
|
2
|
+
token: string;
|
|
3
|
+
/** Default: https://api.kit.yandex.net */
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
/** Requests per second (token bucket capacity). Default: 3. */
|
|
6
|
+
rps?: number;
|
|
7
|
+
/** Per-attempt timeout in milliseconds. Default: 30000. */
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
/** Maximum number of retries after the first attempt. Default: 3. */
|
|
10
|
+
maxRetries?: number;
|
|
11
|
+
/** Base delay for exponential backoff in milliseconds. Default: 500. */
|
|
12
|
+
retryBaseMs?: number;
|
|
13
|
+
fetchImpl?: typeof fetch;
|
|
14
|
+
}
|
|
15
|
+
export interface CallParams {
|
|
16
|
+
pathParams?: Record<string, string | number>;
|
|
17
|
+
query?: Record<string, unknown>;
|
|
18
|
+
body?: unknown;
|
|
19
|
+
}
|
|
20
|
+
interface RawRequest {
|
|
21
|
+
method: string;
|
|
22
|
+
path: string;
|
|
23
|
+
query?: Record<string, unknown>;
|
|
24
|
+
body?: unknown;
|
|
25
|
+
contentType?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare class KitClient {
|
|
28
|
+
private readonly token;
|
|
29
|
+
private readonly baseUrl;
|
|
30
|
+
private readonly timeoutMs;
|
|
31
|
+
private readonly maxRetries;
|
|
32
|
+
private readonly retryBaseMs;
|
|
33
|
+
private readonly fetchImpl;
|
|
34
|
+
private readonly bucket;
|
|
35
|
+
constructor(opts: KitClientOptions);
|
|
36
|
+
/** Execute an operation from the generated registry by its operationId. */
|
|
37
|
+
call<T = unknown>(operationId: string, params?: CallParams): Promise<T>;
|
|
38
|
+
/** Low-level request with rate limiting, timeout and retries. */
|
|
39
|
+
request<T = unknown>(req: RawRequest): Promise<T>;
|
|
40
|
+
/**
|
|
41
|
+
* Fetch all pages of a paginated list operation (per_page=100).
|
|
42
|
+
* Stops on a short page or once maxItems is reached (truncated=true).
|
|
43
|
+
*/
|
|
44
|
+
listAll<T = unknown>(operationId: string, params?: CallParams, opts?: {
|
|
45
|
+
maxItems?: number;
|
|
46
|
+
}): Promise<{
|
|
47
|
+
items: T[];
|
|
48
|
+
pages: number;
|
|
49
|
+
truncated: boolean;
|
|
50
|
+
}>;
|
|
51
|
+
private backoffDelayMs;
|
|
52
|
+
private fetchWithTimeout;
|
|
53
|
+
}
|
|
54
|
+
export {};
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch-based client for the Yandex KIT API.
|
|
3
|
+
*
|
|
4
|
+
* Features (see PLAN.md §2/§6): Bearer auth, per-attempt timeout via
|
|
5
|
+
* AbortController, token-bucket rate limiter (default 3 rps, gates every
|
|
6
|
+
* attempt including retries), exponential backoff retries (network/abort,
|
|
7
|
+
* 429, >=500, and HTTP 400 with code LIMIT_EXCEEDED — KIT returns rate-limit
|
|
8
|
+
* errors as 400), per-operation content type from the generated registry
|
|
9
|
+
* (json / merge-patch+json / multipart), and auto-pagination via listAll().
|
|
10
|
+
*/
|
|
11
|
+
import { KitApiError, KitValidationError } from "./errors.js";
|
|
12
|
+
import { getOp } from "./registry.js";
|
|
13
|
+
const DEFAULT_BASE_URL = "https://api.kit.yandex.net";
|
|
14
|
+
const LIST_ALL_PER_PAGE = 100;
|
|
15
|
+
/** Codes of a parsed KIT error body. LIMIT_EXCEEDED arrives with HTTP 400. */
|
|
16
|
+
const RETRYABLE_400_CODE = "LIMIT_EXCEEDED";
|
|
17
|
+
// NOTE: the timer must stay ref'd — an in-flight request awaiting its backoff
|
|
18
|
+
// delay has to keep the event loop (and thus the process) alive.
|
|
19
|
+
function sleep(ms) {
|
|
20
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Promise-based FIFO token bucket: capacity = rps tokens, refilled
|
|
24
|
+
* continuously at rps tokens/second. A single pending timer is kept while the
|
|
25
|
+
* queue is non-empty and cleared as soon as it drains; the timer stays ref'd
|
|
26
|
+
* so queued requests keep the process alive until they are dispatched.
|
|
27
|
+
*/
|
|
28
|
+
class TokenBucket {
|
|
29
|
+
rps;
|
|
30
|
+
tokens;
|
|
31
|
+
lastRefill;
|
|
32
|
+
queue = [];
|
|
33
|
+
timer = null;
|
|
34
|
+
constructor(rps) {
|
|
35
|
+
this.rps = rps;
|
|
36
|
+
this.tokens = rps; // allow an initial burst of up to rps requests
|
|
37
|
+
this.lastRefill = Date.now();
|
|
38
|
+
}
|
|
39
|
+
acquire() {
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
this.queue.push(resolve);
|
|
42
|
+
this.drain();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
refill() {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
const elapsedSec = (now - this.lastRefill) / 1000;
|
|
48
|
+
this.lastRefill = now;
|
|
49
|
+
// Cap at no less than 1 token: with rps < 1 a cap of exactly rps would
|
|
50
|
+
// never let tokens reach the 1 needed to dispatch (permanent stall).
|
|
51
|
+
const capacity = Math.max(this.rps, 1);
|
|
52
|
+
this.tokens = Math.min(capacity, this.tokens + elapsedSec * this.rps);
|
|
53
|
+
}
|
|
54
|
+
drain() {
|
|
55
|
+
if (this.timer) {
|
|
56
|
+
clearTimeout(this.timer);
|
|
57
|
+
this.timer = null;
|
|
58
|
+
}
|
|
59
|
+
this.refill();
|
|
60
|
+
while (this.queue.length > 0 && this.tokens >= 1) {
|
|
61
|
+
this.tokens -= 1;
|
|
62
|
+
const release = this.queue.shift();
|
|
63
|
+
release();
|
|
64
|
+
}
|
|
65
|
+
if (this.queue.length > 0) {
|
|
66
|
+
const waitMs = Math.max(1, Math.ceil(((1 - this.tokens) / this.rps) * 1000));
|
|
67
|
+
this.timer = setTimeout(() => {
|
|
68
|
+
this.timer = null;
|
|
69
|
+
this.drain();
|
|
70
|
+
}, waitMs);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function buildQueryString(query) {
|
|
75
|
+
if (!query)
|
|
76
|
+
return "";
|
|
77
|
+
const sp = new URLSearchParams();
|
|
78
|
+
for (const [key, value] of Object.entries(query)) {
|
|
79
|
+
if (value === undefined || value === null)
|
|
80
|
+
continue;
|
|
81
|
+
if (Array.isArray(value)) {
|
|
82
|
+
for (const item of value) {
|
|
83
|
+
if (item === undefined || item === null)
|
|
84
|
+
continue;
|
|
85
|
+
sp.append(key, String(item));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
sp.append(key, String(value));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const s = sp.toString();
|
|
93
|
+
return s ? `?${s}` : "";
|
|
94
|
+
}
|
|
95
|
+
function parseRetryAfterMs(res) {
|
|
96
|
+
const header = res.headers.get("retry-after");
|
|
97
|
+
if (!header)
|
|
98
|
+
return undefined;
|
|
99
|
+
const seconds = Number.parseInt(header, 10);
|
|
100
|
+
return Number.isFinite(seconds) && seconds >= 0 ? seconds * 1000 : undefined;
|
|
101
|
+
}
|
|
102
|
+
export class KitClient {
|
|
103
|
+
token;
|
|
104
|
+
baseUrl;
|
|
105
|
+
timeoutMs;
|
|
106
|
+
maxRetries;
|
|
107
|
+
retryBaseMs;
|
|
108
|
+
fetchImpl;
|
|
109
|
+
bucket;
|
|
110
|
+
constructor(opts) {
|
|
111
|
+
this.token = opts.token;
|
|
112
|
+
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
113
|
+
this.timeoutMs = opts.timeoutMs ?? 30_000;
|
|
114
|
+
this.maxRetries = opts.maxRetries ?? 3;
|
|
115
|
+
this.retryBaseMs = opts.retryBaseMs ?? 500;
|
|
116
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
117
|
+
const rps = opts.rps ?? 3;
|
|
118
|
+
if (!Number.isFinite(rps) || rps <= 0) {
|
|
119
|
+
throw new KitValidationError(`rps must be a positive number, got: ${rps}`);
|
|
120
|
+
}
|
|
121
|
+
this.bucket = new TokenBucket(rps);
|
|
122
|
+
}
|
|
123
|
+
/** Execute an operation from the generated registry by its operationId. */
|
|
124
|
+
async call(operationId, params = {}) {
|
|
125
|
+
const op = getOp(operationId);
|
|
126
|
+
let path = op.path;
|
|
127
|
+
for (const name of op.pathParams) {
|
|
128
|
+
const value = params.pathParams?.[name];
|
|
129
|
+
if (value === undefined || value === null) {
|
|
130
|
+
throw new KitValidationError(`Missing path parameter "${name}" for operation ${operationId}`);
|
|
131
|
+
}
|
|
132
|
+
path = path.replace(`{${name}}`, encodeURIComponent(String(value)));
|
|
133
|
+
}
|
|
134
|
+
return this.request({
|
|
135
|
+
method: op.method,
|
|
136
|
+
path,
|
|
137
|
+
query: params.query,
|
|
138
|
+
body: params.body,
|
|
139
|
+
contentType: op.requestContentType ?? undefined,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/** Low-level request with rate limiting, timeout and retries. */
|
|
143
|
+
async request(req) {
|
|
144
|
+
const url = this.baseUrl + req.path + buildQueryString(req.query);
|
|
145
|
+
const headers = {
|
|
146
|
+
Authorization: `Bearer ${this.token}`,
|
|
147
|
+
Accept: "application/json",
|
|
148
|
+
};
|
|
149
|
+
let body;
|
|
150
|
+
if (req.body !== undefined) {
|
|
151
|
+
const contentType = req.contentType ?? "application/json";
|
|
152
|
+
if (contentType === "multipart/form-data") {
|
|
153
|
+
// Let fetch set the multipart boundary header itself.
|
|
154
|
+
body = req.body;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
headers["Content-Type"] = contentType;
|
|
158
|
+
body = JSON.stringify(req.body);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const init = { method: req.method.toUpperCase(), headers, body };
|
|
162
|
+
for (let attempt = 0;; attempt++) {
|
|
163
|
+
await this.bucket.acquire();
|
|
164
|
+
let res;
|
|
165
|
+
let text;
|
|
166
|
+
try {
|
|
167
|
+
const result = await this.fetchWithTimeout(url, init);
|
|
168
|
+
res = result.res;
|
|
169
|
+
text = result.text;
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
// Network failure or per-attempt timeout (abort): retryable.
|
|
173
|
+
if (attempt < this.maxRetries) {
|
|
174
|
+
await sleep(this.backoffDelayMs(attempt));
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
179
|
+
if (res.ok) {
|
|
180
|
+
if (res.status === 204 || text.trim() === "")
|
|
181
|
+
return undefined;
|
|
182
|
+
try {
|
|
183
|
+
return JSON.parse(text);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
throw new KitApiError(res.status, "INVALID_JSON", `Failed to parse response body as JSON: ${text.slice(0, 500)}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
let parsed;
|
|
190
|
+
try {
|
|
191
|
+
parsed = JSON.parse(text);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
parsed = undefined;
|
|
195
|
+
}
|
|
196
|
+
const retryable = res.status === 429 ||
|
|
197
|
+
res.status >= 500 ||
|
|
198
|
+
(res.status === 400 && parsed?.code === RETRYABLE_400_CODE);
|
|
199
|
+
if (retryable && attempt < this.maxRetries) {
|
|
200
|
+
const delay = parseRetryAfterMs(res) ?? this.backoffDelayMs(attempt);
|
|
201
|
+
await sleep(delay);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (parsed && typeof parsed === "object" && typeof parsed.code === "string") {
|
|
205
|
+
throw new KitApiError(res.status, parsed.code, parsed.message ?? `HTTP ${res.status}`, parsed.trace_id, parsed);
|
|
206
|
+
}
|
|
207
|
+
throw new KitApiError(res.status, "HTTP_ERROR", text.slice(0, 500));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Fetch all pages of a paginated list operation (per_page=100).
|
|
212
|
+
* Stops on a short page or once maxItems is reached (truncated=true).
|
|
213
|
+
*/
|
|
214
|
+
async listAll(operationId, params = {}, opts = {}) {
|
|
215
|
+
const op = getOp(operationId);
|
|
216
|
+
if (!op.paginated || !op.itemsProp) {
|
|
217
|
+
throw new KitValidationError(`Operation ${operationId} is not a paginated list operation`);
|
|
218
|
+
}
|
|
219
|
+
const itemsProp = op.itemsProp;
|
|
220
|
+
const maxItems = opts.maxItems ?? 500;
|
|
221
|
+
const items = [];
|
|
222
|
+
let pages = 0;
|
|
223
|
+
let truncated = false;
|
|
224
|
+
for (let page = 1;; page++) {
|
|
225
|
+
const res = await this.call(operationId, {
|
|
226
|
+
...params,
|
|
227
|
+
query: { ...params.query, page, per_page: LIST_ALL_PER_PAGE },
|
|
228
|
+
});
|
|
229
|
+
pages++;
|
|
230
|
+
const raw = res?.[itemsProp];
|
|
231
|
+
const pageItems = Array.isArray(raw) ? raw : [];
|
|
232
|
+
const room = maxItems - items.length;
|
|
233
|
+
if (pageItems.length > room) {
|
|
234
|
+
items.push(...pageItems.slice(0, room));
|
|
235
|
+
truncated = true;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
items.push(...pageItems);
|
|
239
|
+
if (pageItems.length < LIST_ALL_PER_PAGE)
|
|
240
|
+
break; // last page
|
|
241
|
+
if (items.length >= maxItems) {
|
|
242
|
+
truncated = true; // cap hit exactly on a full page — more may exist
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return { items, pages, truncated };
|
|
247
|
+
}
|
|
248
|
+
backoffDelayMs(attempt) {
|
|
249
|
+
const base = this.retryBaseMs * 2 ** attempt;
|
|
250
|
+
return base + Math.random() * base * 0.25; // +0-25% jitter
|
|
251
|
+
}
|
|
252
|
+
async fetchWithTimeout(url, init) {
|
|
253
|
+
const controller = new AbortController();
|
|
254
|
+
// Ref'd on purpose (cleared in finally): an unref'd abort timer could let
|
|
255
|
+
// the process exit mid-request when no other handle keeps the loop alive.
|
|
256
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
257
|
+
try {
|
|
258
|
+
const res = await this.fetchImpl(url, { ...init, signal: controller.signal });
|
|
259
|
+
const text = res.status === 204 ? "" : await res.text();
|
|
260
|
+
return { res, text };
|
|
261
|
+
}
|
|
262
|
+
finally {
|
|
263
|
+
clearTimeout(timer);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error types for the Yandex KIT API client.
|
|
3
|
+
*
|
|
4
|
+
* The API error contract (verified live) is a JSON body:
|
|
5
|
+
* {"code": "...", "message": "...", "trace_id": "..."}
|
|
6
|
+
*/
|
|
7
|
+
/** HTTP-level error returned by the KIT API. */
|
|
8
|
+
export declare class KitApiError extends Error {
|
|
9
|
+
status: number;
|
|
10
|
+
code: string;
|
|
11
|
+
traceId?: string;
|
|
12
|
+
details?: unknown;
|
|
13
|
+
constructor(status: number, code: string, message: string, traceId?: string, details?: unknown);
|
|
14
|
+
}
|
|
15
|
+
/** Client-side validation error raised before any network call. */
|
|
16
|
+
export declare class KitValidationError extends Error {
|
|
17
|
+
errors: string[];
|
|
18
|
+
constructor(message: string, errors?: string[]);
|
|
19
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error types for the Yandex KIT API client.
|
|
3
|
+
*
|
|
4
|
+
* The API error contract (verified live) is a JSON body:
|
|
5
|
+
* {"code": "...", "message": "...", "trace_id": "..."}
|
|
6
|
+
*/
|
|
7
|
+
/** HTTP-level error returned by the KIT API. */
|
|
8
|
+
export class KitApiError extends Error {
|
|
9
|
+
status;
|
|
10
|
+
code;
|
|
11
|
+
traceId;
|
|
12
|
+
details;
|
|
13
|
+
constructor(status, code, message, traceId, details) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "KitApiError";
|
|
16
|
+
this.status = status;
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.traceId = traceId;
|
|
19
|
+
this.details = details;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Client-side validation error raised before any network call. */
|
|
23
|
+
export class KitValidationError extends Error {
|
|
24
|
+
errors;
|
|
25
|
+
constructor(message, errors = []) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "KitValidationError";
|
|
28
|
+
this.errors = errors.length > 0 ? errors : [message];
|
|
29
|
+
}
|
|
30
|
+
}
|