itd-api 0.10.3 → 0.11.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/README.md +2 -1
- package/dist/events/index.cjs +27 -25
- package/dist/events/index.d.cts +3 -3
- package/dist/events/index.d.ts +3 -3
- package/dist/events/index.js +5 -5
- package/dist/index.cjs +136 -116
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -7
- package/dist/index.d.ts +18 -7
- package/dist/index.js +46 -30
- package/dist/index.js.map +1 -1
- package/dist/node/index.cjs +13 -13
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.js +5 -5
- package/dist/rest/index.cjs +43 -40
- package/dist/rest/index.cjs.map +1 -1
- package/dist/rest/index.d.cts +3 -3
- package/dist/rest/index.d.ts +3 -3
- package/dist/rest/index.js +6 -6
- package/dist/shared/{events-BVfqWyBw.cjs → events-C6tmj8aW.cjs} +63 -59
- package/dist/shared/events-C6tmj8aW.cjs.map +1 -0
- package/dist/shared/{events-Cp7ITueA.d.ts → events-CAb9G7Sf.d.ts} +2 -2
- package/dist/shared/{events-Cwj7UPAa.js → events-MWNcnfrq.js} +19 -15
- package/dist/shared/events-MWNcnfrq.js.map +1 -0
- package/dist/shared/{events-D0Wtt-If.d.cts → events-Sc75x6rT.d.cts} +2 -2
- package/dist/shared/{key-value-store-B5GYVEYZ.cjs → key-value-store-DYSPsqwq.cjs} +11 -14
- package/dist/shared/key-value-store-DYSPsqwq.cjs.map +1 -0
- package/dist/shared/{key-value-store-Bt2PSMxY.js → key-value-store-w1Dn5Pjb.js} +5 -8
- package/dist/shared/key-value-store-w1Dn5Pjb.js.map +1 -0
- package/dist/shared/{multi-storage-C3Ngtop1.js → multi-storage-BLBrGN9Q.js} +6 -6
- package/dist/shared/multi-storage-BLBrGN9Q.js.map +1 -0
- package/dist/shared/{multi-storage-s-fPtjoy.cjs → multi-storage-C7-Gz6Z7.cjs} +8 -8
- package/dist/shared/multi-storage-C7-Gz6Z7.cjs.map +1 -0
- package/dist/shared/{options-DjEtbVaf.cjs → options-5tywTYId.cjs} +13 -13
- package/dist/shared/{options-DjEtbVaf.cjs.map → options-5tywTYId.cjs.map} +1 -1
- package/dist/shared/{options-i4LrKfqx.js → options-BerKicCs.js} +3 -3
- package/dist/shared/{options-i4LrKfqx.js.map → options-BerKicCs.js.map} +1 -1
- package/dist/shared/{redact-DJV0XM3n.js → redact-C5oxIvcc.js} +79 -18
- package/dist/shared/redact-C5oxIvcc.js.map +1 -0
- package/dist/shared/{redact-lAmck1Z6.cjs → redact-DoC7lSsp.cjs} +109 -24
- package/dist/shared/redact-DoC7lSsp.cjs.map +1 -0
- package/dist/shared/{render-DRmuHoEy.d.ts → render-CBroJPcV.d.ts} +178 -252
- package/dist/shared/{render-CJO_L2CD.d.cts → render-DM7AhTZW.d.cts} +178 -252
- package/dist/shared/{render-BT74nC1l.js → render-DRh5_aRe.js} +128 -135
- package/dist/shared/render-DRh5_aRe.js.map +1 -0
- package/dist/shared/{render-CYJSDypQ.cjs → render-Dnc84B4M.cjs} +234 -241
- package/dist/shared/render-Dnc84B4M.cjs.map +1 -0
- package/dist/shared/{storage-DiDe0PZM.js → storage-86wiHeXD.js} +3 -3
- package/dist/shared/{storage-DiDe0PZM.js.map → storage-86wiHeXD.js.map} +1 -1
- package/dist/shared/{storage-CZJZoaz9.cjs → storage-CIjejZDt.cjs} +4 -4
- package/dist/shared/{storage-CZJZoaz9.cjs.map → storage-CIjejZDt.cjs.map} +1 -1
- package/dist/shared/{url-BLnX-LYt.d.cts → url-BOi-_mJT.d.cts} +143 -79
- package/dist/shared/{url-BLnX-LYt.d.ts → url-BOi-_mJT.d.ts} +143 -79
- package/dist/shared/{url-DS0-lrl-.js → url-Bg61sk_Y.js} +741 -598
- package/dist/shared/url-Bg61sk_Y.js.map +1 -0
- package/dist/shared/{url-BnO1aWsd.cjs → url-DTgkCWv5.cjs} +848 -717
- package/dist/shared/url-DTgkCWv5.cjs.map +1 -0
- package/dist/shared/{errors-BmP3TKoW.cjs → validate-CFx2dISs.cjs} +84 -5
- package/dist/shared/validate-CFx2dISs.cjs.map +1 -0
- package/dist/shared/{errors-GI10kZxk.js → validate-D7_kq3g5.js} +55 -6
- package/dist/shared/validate-D7_kq3g5.js.map +1 -0
- package/dist/web/index.cjs +4 -4
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.js +3 -3
- package/package.json +1 -1
- package/dist/shared/errors-BmP3TKoW.cjs.map +0 -1
- package/dist/shared/errors-GI10kZxk.js.map +0 -1
- package/dist/shared/events-BVfqWyBw.cjs.map +0 -1
- package/dist/shared/events-Cwj7UPAa.js.map +0 -1
- package/dist/shared/key-value-store-B5GYVEYZ.cjs.map +0 -1
- package/dist/shared/key-value-store-Bt2PSMxY.js.map +0 -1
- package/dist/shared/multi-storage-C3Ngtop1.js.map +0 -1
- package/dist/shared/multi-storage-s-fPtjoy.cjs.map +0 -1
- package/dist/shared/redact-DJV0XM3n.js.map +0 -1
- package/dist/shared/redact-lAmck1Z6.cjs.map +0 -1
- package/dist/shared/render-BT74nC1l.js.map +0 -1
- package/dist/shared/render-CYJSDypQ.cjs.map +0 -1
- package/dist/shared/url-BnO1aWsd.cjs.map +0 -1
- package/dist/shared/url-DS0-lrl-.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const require_url = require("./url-
|
|
2
|
-
const
|
|
3
|
-
const require_redact = require("./redact-
|
|
4
|
-
const require_options = require("./options-
|
|
5
|
-
const require_key_value_store = require("./key-value-store-
|
|
1
|
+
const require_url = require("./url-DTgkCWv5.cjs");
|
|
2
|
+
const require_validate = require("./validate-CFx2dISs.cjs");
|
|
3
|
+
const require_redact = require("./redact-DoC7lSsp.cjs");
|
|
4
|
+
const require_options = require("./options-5tywTYId.cjs");
|
|
5
|
+
const require_key_value_store = require("./key-value-store-DYSPsqwq.cjs");
|
|
6
6
|
//#region src/core/feature-catalog.ts
|
|
7
7
|
/**
|
|
8
8
|
* Изменяемый слой над каталогом конкретного API.
|
|
@@ -16,19 +16,16 @@ var ExtensibleOperationCatalog = class {
|
|
|
16
16
|
#base;
|
|
17
17
|
#operations = /* @__PURE__ */ new Map();
|
|
18
18
|
#operationOwners = /* @__PURE__ */ new Map();
|
|
19
|
-
|
|
20
|
-
#
|
|
21
|
-
#bucketOverrides;
|
|
19
|
+
/** Бакеты feature: владелец и ограничения. Таблицы базового каталога не изменяются. */
|
|
20
|
+
#buckets = /* @__PURE__ */ new Map();
|
|
22
21
|
constructor(base) {
|
|
23
22
|
this.#base = base;
|
|
24
|
-
this.#bucketLimits = { ...base.bucketLimits };
|
|
25
|
-
this.#bucketOverrides = { ...base.bucketOverrides };
|
|
26
23
|
}
|
|
27
24
|
get bucketLimits() {
|
|
28
|
-
return this.#bucketLimits;
|
|
25
|
+
return this.#base.bucketLimits;
|
|
29
26
|
}
|
|
30
27
|
get bucketOverrides() {
|
|
31
|
-
return this.#bucketOverrides;
|
|
28
|
+
return this.#base.bucketOverrides;
|
|
32
29
|
}
|
|
33
30
|
get defaultBucket() {
|
|
34
31
|
return this.#base.defaultBucket;
|
|
@@ -36,41 +33,37 @@ var ExtensibleOperationCatalog = class {
|
|
|
36
33
|
definitionOf(id) {
|
|
37
34
|
return this.#operations.get(id) ?? this.#base.definitionOf(id);
|
|
38
35
|
}
|
|
39
|
-
retrySafetyOf(id) {
|
|
40
|
-
return this.#operations.get(id)?.retrySafety ?? this.#base.retrySafetyOf(id);
|
|
41
|
-
}
|
|
42
|
-
methodOf(id) {
|
|
43
|
-
return this.#operations.get(id)?.method ?? this.#base.methodOf(id);
|
|
44
|
-
}
|
|
45
36
|
bucketOf(id) {
|
|
46
37
|
return this.#operations.get(id)?.bucket ?? this.#base.bucketOf(id);
|
|
47
38
|
}
|
|
48
39
|
isKnownBucket(name) {
|
|
49
|
-
return this.#
|
|
40
|
+
return this.#buckets.has(name) || this.#base.isKnownBucket(name);
|
|
41
|
+
}
|
|
42
|
+
bucketDefinitionOf(name) {
|
|
43
|
+
return this.#buckets.get(name)?.definition ?? this.#base.bucketDefinitionOf(name);
|
|
50
44
|
}
|
|
51
45
|
/** Регистрирует принадлежащий feature бакет и возвращает откат регистрации. */
|
|
52
46
|
registerBucket(owner, name, definition) {
|
|
53
|
-
if (this.#base.isKnownBucket(name) || this.#
|
|
54
|
-
this.#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
if (this.#base.isKnownBucket(name) || this.#buckets.has(name)) throw new require_validate.ItdConfigError(`feature «${owner}»: бакет «${name}» уже зарегистрирован`);
|
|
48
|
+
this.#buckets.set(name, {
|
|
49
|
+
owner,
|
|
50
|
+
definition: Object.freeze({
|
|
51
|
+
...definition.limit === void 0 ? {} : { limit: definition.limit },
|
|
52
|
+
...definition.concurrency === void 0 ? {} : { concurrency: definition.concurrency },
|
|
53
|
+
...definition.rps === void 0 ? {} : { rps: definition.rps }
|
|
54
|
+
})
|
|
59
55
|
});
|
|
60
56
|
return () => {
|
|
61
|
-
if (this.#
|
|
62
|
-
this.#bucketOwners.delete(name);
|
|
63
|
-
delete this.#bucketLimits[name];
|
|
64
|
-
delete this.#bucketOverrides[name];
|
|
57
|
+
if (this.#buckets.get(name)?.owner === owner) this.#buckets.delete(name);
|
|
65
58
|
};
|
|
66
59
|
}
|
|
67
60
|
/** Регистрирует операцию и возвращает откат регистрации. */
|
|
68
61
|
registerOperation(owner, id, definition) {
|
|
69
62
|
const ownerPrefix = `${owner}.`;
|
|
70
|
-
if (!id.startsWith(ownerPrefix) || id.length === ownerPrefix.length) throw new
|
|
63
|
+
if (!id.startsWith(ownerPrefix) || id.length === ownerPrefix.length) throw new require_validate.ItdConfigError(`feature «${owner}»: операция «${id}» должна находиться в пространстве имён «${owner}»`);
|
|
71
64
|
const occupiedBy = this.#operationOwners.get(id);
|
|
72
|
-
if (occupiedBy !== void 0) throw new
|
|
73
|
-
if (this.#base.
|
|
65
|
+
if (occupiedBy !== void 0) throw new require_validate.ItdConfigError(`feature «${owner}»: операция «${id}» уже зарегистрирована feature «${occupiedBy}»`);
|
|
66
|
+
if (this.#base.definitionOf(id) !== void 0) throw new require_validate.ItdConfigError(`feature «${owner}»: операция «${id}» уже существует в базовом каталоге`);
|
|
74
67
|
this.#operationOwners.set(id, owner);
|
|
75
68
|
this.#operations.set(id, Object.freeze({ ...definition }));
|
|
76
69
|
return () => {
|
|
@@ -111,17 +104,17 @@ async function fetchFile(target, options, context) {
|
|
|
111
104
|
try {
|
|
112
105
|
requested = new URL(target);
|
|
113
106
|
} catch {
|
|
114
|
-
throw new
|
|
107
|
+
throw new require_validate.ItdConfigError(`«${require_redact.redactUrl(target)}» не разбирается как адрес`);
|
|
115
108
|
}
|
|
116
|
-
if (requested.protocol !== "http:" && requested.protocol !== "https:") throw new
|
|
109
|
+
if (requested.protocol !== "http:" && requested.protocol !== "https:") throw new require_validate.ItdConfigError(`вложение по адресу поддерживает только http и https, получено: ${requested.protocol}`);
|
|
117
110
|
let response;
|
|
118
111
|
try {
|
|
119
112
|
response = await context.fetch(requested, { ...context.signal ? { signal: context.signal } : {} });
|
|
120
113
|
} catch (error) {
|
|
121
114
|
if (context.signal?.aborted || error instanceof Error && error.name === "AbortError") throw error;
|
|
122
115
|
const safeUrl = require_redact.redactUrl(requested.href);
|
|
123
|
-
throw new
|
|
124
|
-
reason:
|
|
116
|
+
throw new require_validate.ItdFileError(`не удалось получить файл по адресу ${safeUrl}`, {
|
|
117
|
+
reason: require_validate.ItdFileErrorReason.Network,
|
|
125
118
|
url: safeUrl,
|
|
126
119
|
retryable: true,
|
|
127
120
|
cause: require_redact.redactErrorCause(error)
|
|
@@ -131,8 +124,8 @@ async function fetchFile(target, options, context) {
|
|
|
131
124
|
if (!response.ok) {
|
|
132
125
|
await response.body?.cancel().catch(() => {});
|
|
133
126
|
const safeUrl = require_redact.redactUrl(finalUrl.href);
|
|
134
|
-
throw new
|
|
135
|
-
reason:
|
|
127
|
+
throw new require_validate.ItdFileError(`источник ${safeUrl} ответил статусом ${response.status}`, {
|
|
128
|
+
reason: require_validate.ItdFileErrorReason.Http,
|
|
136
129
|
url: safeUrl,
|
|
137
130
|
status: response.status,
|
|
138
131
|
retryable: response.status === 408 || response.status === 429 || response.status >= 500
|
|
@@ -153,11 +146,11 @@ async function fetchFile(target, options, context) {
|
|
|
153
146
|
};
|
|
154
147
|
}
|
|
155
148
|
/** Читает ответ с контролем размера до создания итогового `Blob`. */
|
|
156
|
-
async function responseBlob(response, url, maxBytes, streamBufferBytes, signal) {
|
|
149
|
+
async function responseBlob(response, url, maxBytes, streamBufferBytes, signal, type) {
|
|
157
150
|
if (!response.body) {
|
|
158
151
|
const blob = await response.blob();
|
|
159
152
|
if (maxBytes !== void 0 && blob.size > maxBytes) throw require_options.fileTooLarge(url, maxBytes, blob.size);
|
|
160
|
-
return blob;
|
|
153
|
+
return blob.type === type ? blob : new Blob([blob], { type });
|
|
161
154
|
}
|
|
162
155
|
const chunks = [];
|
|
163
156
|
const reader = require_options.boundedFileStream(response.body, {
|
|
@@ -176,17 +169,17 @@ async function responseBlob(response, url, maxBytes, streamBufferBytes, signal)
|
|
|
176
169
|
} finally {
|
|
177
170
|
reader.releaseLock();
|
|
178
171
|
}
|
|
179
|
-
return new Blob(chunks
|
|
172
|
+
return new Blob(chunks, { type });
|
|
180
173
|
}
|
|
181
174
|
/** Скачивает файл целиком с ограничением размера. @internal */
|
|
182
175
|
async function downloadFile(target, options, context) {
|
|
183
176
|
const resolved = require_options.resolveFileStreamOptions(options, require_options.DEFAULT_URL_FILE_MAX_BYTES);
|
|
184
177
|
const { response, url } = await fetchFile(target, options, context);
|
|
185
|
-
const blob = await responseBlob(response, url.href, resolved.maxBytes, resolved.streamBufferBytes, context.signal);
|
|
186
178
|
const contentType = normalizeMimeType(options.contentType ?? response.headers.get("content-type") ?? void 0);
|
|
179
|
+
const file = await responseBlob(response, url.href, resolved.maxBytes, resolved.streamBufferBytes, context.signal, contentType ?? "");
|
|
187
180
|
const filename = options.filename ?? filenameFromUrl(url);
|
|
188
181
|
return {
|
|
189
|
-
file
|
|
182
|
+
file,
|
|
190
183
|
...filename ? { filename } : {},
|
|
191
184
|
...contentType ? { contentType } : {}
|
|
192
185
|
};
|
|
@@ -197,8 +190,8 @@ async function openUrlFile(target, options, context) {
|
|
|
197
190
|
const { response, url, size } = await fetchFile(target, options, context);
|
|
198
191
|
if (!response.body) {
|
|
199
192
|
const safeUrl = require_redact.redactUrl(url.href);
|
|
200
|
-
throw new
|
|
201
|
-
reason:
|
|
193
|
+
throw new require_validate.ItdFileError(`источник ${safeUrl} не предоставил потоковое тело`, {
|
|
194
|
+
reason: require_validate.ItdFileErrorReason.StreamUnavailable,
|
|
202
195
|
url: safeUrl
|
|
203
196
|
});
|
|
204
197
|
}
|
|
@@ -245,7 +238,7 @@ var DefaultFileResolver = class {
|
|
|
245
238
|
}
|
|
246
239
|
async resolve(input, options = {}, context = {}) {
|
|
247
240
|
const attempt = context.attempt ?? 1;
|
|
248
|
-
if (!Number.isInteger(attempt) || attempt < 1) throw new
|
|
241
|
+
if (!Number.isInteger(attempt) || attempt < 1) throw new require_validate.ItdConfigError(`attempt должен быть целым числом от 1, получено: ${attempt}`);
|
|
249
242
|
const maxBytes = require_options.optionalBytes(options.maxBytes, "maxBytes");
|
|
250
243
|
if (context.signal?.aborted) throw context.signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
251
244
|
const fileContext = {
|
|
@@ -261,9 +254,9 @@ var DefaultFileResolver = class {
|
|
|
261
254
|
}
|
|
262
255
|
return source;
|
|
263
256
|
} catch (error) {
|
|
264
|
-
if (error instanceof
|
|
265
|
-
throw new
|
|
266
|
-
reason:
|
|
257
|
+
if (error instanceof require_validate.ItdError || context.signal?.aborted) throw error;
|
|
258
|
+
throw new require_validate.ItdFileError("не удалось получить содержимое вложения", {
|
|
259
|
+
reason: require_validate.ItdFileErrorReason.Read,
|
|
267
260
|
retryable: true,
|
|
268
261
|
cause: require_redact.redactErrorCause(error)
|
|
269
262
|
});
|
|
@@ -271,8 +264,8 @@ var DefaultFileResolver = class {
|
|
|
271
264
|
}
|
|
272
265
|
async #loadBuffer(input, options, context, maxBytes) {
|
|
273
266
|
const content = await this.#resolveBuffer(input, context);
|
|
274
|
-
if (typeof content !== "object" || content === null || !("file" in content)) throw new
|
|
275
|
-
if (!require_redact.isBlob(content.file) && !(content.file instanceof ArrayBuffer) && !ArrayBuffer.isView(content.file)) throw new
|
|
267
|
+
if (typeof content !== "object" || content === null || !("file" in content)) throw new require_validate.ItdConfigError("буферный источник должен вернуть объект с полем file");
|
|
268
|
+
if (!require_redact.isBlob(content.file) && !(content.file instanceof ArrayBuffer) && !ArrayBuffer.isView(content.file)) throw new require_validate.ItdConfigError("поле file должно содержать Blob, ArrayBuffer или Uint8Array");
|
|
276
269
|
const contentType = normalizeMimeType(options.contentType ?? content.contentType) ?? normalizeMimeType(require_redact.isBlob(content.file) ? content.file.type : void 0);
|
|
277
270
|
const blob = require_redact.isBlob(content.file) && (!contentType || content.file.type === contentType) ? content.file : new Blob([content.file], { type: contentType ?? "" });
|
|
278
271
|
if (maxBytes !== void 0 && blob.size > maxBytes) throw require_options.fileTooLarge(void 0, maxBytes, blob.size);
|
|
@@ -288,7 +281,7 @@ var DefaultFileResolver = class {
|
|
|
288
281
|
const opened = await this.#resolveStream(input, context);
|
|
289
282
|
if (!opened || !require_options.isReadableByteStream(opened.stream)) {
|
|
290
283
|
await opened?.close?.();
|
|
291
|
-
throw new
|
|
284
|
+
throw new require_validate.ItdConfigError("потоковый источник должен вернуть { stream: ReadableStream }");
|
|
292
285
|
}
|
|
293
286
|
let handedOff = false;
|
|
294
287
|
const closeOpened = closeOnce(async () => {
|
|
@@ -297,7 +290,7 @@ var DefaultFileResolver = class {
|
|
|
297
290
|
try {
|
|
298
291
|
require_options.optionalBytes(opened.size, "size");
|
|
299
292
|
if (maxBytes !== void 0 && opened.size !== void 0 && opened.size > maxBytes) throw require_options.fileTooLarge(void 0, maxBytes, opened.size);
|
|
300
|
-
if (options.requireKnownSize === true && opened.size === void 0) throw new
|
|
293
|
+
if (options.requireKnownSize === true && opened.size === void 0) throw new require_validate.ItdConfigError("размер потокового вложения должен быть известен до чтения");
|
|
301
294
|
const limits = require_options.resolveFileStreamOptions({
|
|
302
295
|
mode: require_options.FileTransferMode.Stream,
|
|
303
296
|
...maxBytes !== void 0 ? { maxBytes } : {},
|
|
@@ -345,15 +338,15 @@ var DefaultFileResolver = class {
|
|
|
345
338
|
file: input,
|
|
346
339
|
...require_redact.isFile(input) ? { filename: input.name } : {}
|
|
347
340
|
};
|
|
348
|
-
if (typeof input !== "object" || input === null) throw new
|
|
341
|
+
if (typeof input !== "object" || input === null) throw new require_validate.ItdConfigError(`вложение задано значением типа ${typeof input}; ожидается бинарное значение или объект { file }, { url }, { load }, { open }. Для файла на диске используйте fromPath('./photo.jpg') из itd-api/node`);
|
|
349
342
|
if ("load" in input) return input.load(context);
|
|
350
343
|
if ("url" in input) {
|
|
351
344
|
const { url, ...urlOptions } = input;
|
|
352
345
|
return downloadFile(url, urlOptions, context);
|
|
353
346
|
}
|
|
354
347
|
if ("file" in input) return input;
|
|
355
|
-
if ("open" in input) throw new
|
|
356
|
-
throw new
|
|
348
|
+
if ("open" in input) throw new require_validate.ItdConfigError("потоковый источник нельзя использовать как буферный");
|
|
349
|
+
throw new require_validate.ItdConfigError("вложение не распознано: ожидается { file }, { url }, { load } или { open }");
|
|
357
350
|
}
|
|
358
351
|
#resolveStream(input, context) {
|
|
359
352
|
if (typeof input === "object" && input !== null && "open" in input) return input.open(context);
|
|
@@ -361,7 +354,7 @@ var DefaultFileResolver = class {
|
|
|
361
354
|
const { url, ...urlOptions } = input;
|
|
362
355
|
return openUrlFile(url, urlOptions, context);
|
|
363
356
|
}
|
|
364
|
-
throw new
|
|
357
|
+
throw new require_validate.ItdConfigError("потоковое вложение должно иметь форму { open } или { url, mode }");
|
|
365
358
|
}
|
|
366
359
|
};
|
|
367
360
|
/** Создаёт механизм подготовки файлов, привязанный к настроенному `fetch` клиента. @internal */
|
|
@@ -387,34 +380,120 @@ var FeatureInstallationTransaction = class {
|
|
|
387
380
|
}
|
|
388
381
|
};
|
|
389
382
|
function requireName(value, kind) {
|
|
390
|
-
if (typeof value !== "string" || value.trim() === "") throw new
|
|
383
|
+
if (typeof value !== "string" || value.trim() === "") throw new require_validate.ItdConfigError(`${kind} должен иметь непустое имя`);
|
|
391
384
|
return value.trim();
|
|
392
385
|
}
|
|
393
386
|
function requireFeatureName(value) {
|
|
394
387
|
const name = requireName(value, "Feature");
|
|
395
|
-
if (!/^[a-z][a-z0-9-]*$/.test(name)) throw new
|
|
388
|
+
if (!/^[a-z][a-z0-9-]*$/.test(name)) throw new require_validate.ItdConfigError(`имя feature «${name}» должно соответствовать [a-z][a-z0-9-]*`);
|
|
396
389
|
return name;
|
|
397
390
|
}
|
|
398
391
|
function validateBucket(owner, localName, bucket) {
|
|
399
|
-
if (typeof bucket !== "object" || bucket === null || Array.isArray(bucket)) throw new
|
|
400
|
-
if (bucket.limit !== void 0 && (!Number.isFinite(bucket.limit) || bucket.limit <= 0)) throw new
|
|
401
|
-
if (bucket.concurrency !== void 0 && (!Number.isInteger(bucket.concurrency) || bucket.concurrency < 1)) throw new
|
|
402
|
-
if (bucket.rps !== void 0 && (!Number.isFinite(bucket.rps) || bucket.rps <= 0)) throw new
|
|
392
|
+
if (typeof bucket !== "object" || bucket === null || Array.isArray(bucket)) throw new require_validate.ItdConfigError(`feature «${owner}»: бакет «${localName}» должен быть объектом`);
|
|
393
|
+
if (bucket.limit !== void 0 && (!Number.isFinite(bucket.limit) || bucket.limit <= 0)) throw new require_validate.ItdConfigError(`feature «${owner}»: limit бакета «${localName}» должен быть положительным числом`);
|
|
394
|
+
if (bucket.concurrency !== void 0 && (!Number.isInteger(bucket.concurrency) || bucket.concurrency < 1)) throw new require_validate.ItdConfigError(`feature «${owner}»: concurrency бакета «${localName}» должен быть целым числом от 1`);
|
|
395
|
+
if (bucket.rps !== void 0 && (!Number.isFinite(bucket.rps) || bucket.rps <= 0)) throw new require_validate.ItdConfigError(`feature «${owner}»: rps бакета «${localName}» должен быть положительным числом`);
|
|
403
396
|
}
|
|
404
397
|
function validateFeatureDefinition(feature, isInstalled) {
|
|
405
|
-
if (typeof feature !== "object" || feature === null) throw new
|
|
398
|
+
if (typeof feature !== "object" || feature === null) throw new require_validate.ItdConfigError("feature должен быть объектом");
|
|
406
399
|
const name = requireFeatureName(feature.name);
|
|
407
|
-
if (isInstalled(name)) throw new
|
|
408
|
-
if (typeof feature.setup !== "function") throw new
|
|
409
|
-
if (typeof feature.operations !== "object" || feature.operations === null || Array.isArray(feature.operations)) throw new
|
|
410
|
-
if (feature.services !== void 0 && !Array.isArray(feature.services)) throw new
|
|
411
|
-
if (feature.buckets !== void 0 && (typeof feature.buckets !== "object" || feature.buckets === null || Array.isArray(feature.buckets))) throw new
|
|
400
|
+
if (isInstalled(name)) throw new require_validate.ItdConfigError(`feature «${name}» уже установлен`);
|
|
401
|
+
if (typeof feature.setup !== "function") throw new require_validate.ItdConfigError(`feature «${name}» должен предоставлять setup()`);
|
|
402
|
+
if (typeof feature.operations !== "object" || feature.operations === null || Array.isArray(feature.operations)) throw new require_validate.ItdConfigError(`feature «${name}» должен объявлять operations`);
|
|
403
|
+
if (feature.services !== void 0 && !Array.isArray(feature.services)) throw new require_validate.ItdConfigError(`feature «${name}»: services должен быть массивом`);
|
|
404
|
+
if (feature.buckets !== void 0 && (typeof feature.buckets !== "object" || feature.buckets === null || Array.isArray(feature.buckets))) throw new require_validate.ItdConfigError(`feature «${name}»: buckets должен быть объектом`);
|
|
412
405
|
return name;
|
|
413
406
|
}
|
|
414
407
|
function validateFeatureInstallation(name, installation) {
|
|
415
|
-
if (typeof installation !== "object" || installation === null || !("api" in installation)) throw new
|
|
416
|
-
if (installation.close !== void 0 && typeof installation.close !== "function") throw new
|
|
417
|
-
if (installation.dispose !== void 0 && typeof installation.dispose !== "function") throw new
|
|
408
|
+
if (typeof installation !== "object" || installation === null || !("api" in installation)) throw new require_validate.ItdConfigError(`setup() feature «${name}» должен вернуть объект с полем api`);
|
|
409
|
+
if (installation.close !== void 0 && typeof installation.close !== "function") throw new require_validate.ItdConfigError(`feature «${name}»: close должен быть функцией`);
|
|
410
|
+
if (installation.dispose !== void 0 && typeof installation.dispose !== "function") throw new require_validate.ItdConfigError(`feature «${name}»: dispose должен быть функцией`);
|
|
411
|
+
}
|
|
412
|
+
/** Пишет ошибку отката в журнал; сбой самого журнала не заменяет ошибку установки. */
|
|
413
|
+
function reportRollbackError(logger, message, error) {
|
|
414
|
+
try {
|
|
415
|
+
logger?.error(message, error);
|
|
416
|
+
} catch {}
|
|
417
|
+
}
|
|
418
|
+
/** Останавливает ресурс, зарегистрированный до публикации модуля, при откате установки. */
|
|
419
|
+
function stopUnpublishedResource(resource, release, featureName, logger) {
|
|
420
|
+
try {
|
|
421
|
+
resource.stop();
|
|
422
|
+
} catch (error) {
|
|
423
|
+
reportRollbackError(logger, `Не удалось остановить ресурс «${resource.kind}» при откате feature «${featureName}»`, error);
|
|
424
|
+
} finally {
|
|
425
|
+
release();
|
|
426
|
+
}
|
|
427
|
+
Promise.resolve().then(() => resource.drain()).catch((error) => {
|
|
428
|
+
reportRollbackError(logger, `Не удалось освободить ресурс «${resource.kind}» при откате feature «${featureName}»`, error);
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
/** Собирает {@link FeatureContext} модуля: доступ к клиенту с проверкой, что модуль активен. */
|
|
432
|
+
function createFeatureContext(deps, activation) {
|
|
433
|
+
const { name, controller, services, operations, transaction } = activation;
|
|
434
|
+
const inactive = () => new require_validate.ItdStateError(`feature «${name}» не активен`);
|
|
435
|
+
const assertActive = (action) => {
|
|
436
|
+
deps.assertActive(action);
|
|
437
|
+
if (!activation.committed || controller.signal.aborted) throw inactive();
|
|
438
|
+
};
|
|
439
|
+
const requireService = (serviceName) => {
|
|
440
|
+
if (!services.has(serviceName)) throw new require_validate.ItdConfigError(`feature «${name}» не объявляет сервис «${serviceName}»`);
|
|
441
|
+
};
|
|
442
|
+
return Object.freeze({
|
|
443
|
+
featureName: name,
|
|
444
|
+
baseUrl: deps.baseUrl,
|
|
445
|
+
signal: controller.signal,
|
|
446
|
+
clock: deps.clock,
|
|
447
|
+
logger: deps.logger,
|
|
448
|
+
files: Object.freeze({ resolve: (input, options, context) => {
|
|
449
|
+
try {
|
|
450
|
+
assertActive(`открыть файловый источник feature «${name}»`);
|
|
451
|
+
return deps.files.resolve(input, options, context);
|
|
452
|
+
} catch (error) {
|
|
453
|
+
return Promise.reject(error);
|
|
454
|
+
}
|
|
455
|
+
} }),
|
|
456
|
+
request: (operation, options) => {
|
|
457
|
+
try {
|
|
458
|
+
assertActive(`выполнить операцию feature «${name}»`);
|
|
459
|
+
const resolved = operations.get(operation);
|
|
460
|
+
if (!resolved) throw new require_validate.ItdConfigError(`feature «${name}» не объявляет операцию «${operation}»`);
|
|
461
|
+
const { operationId: _operationId, method: _method, service: _service, baseUrl: _baseUrl, retrySafety: _retrySafety, rateLimitBucket: _rateLimitBucket, ...scoped } = options;
|
|
462
|
+
return deps.http.execute(resolved.contract, {
|
|
463
|
+
...scoped,
|
|
464
|
+
...resolved.service === void 0 ? {} : { service: resolved.service }
|
|
465
|
+
});
|
|
466
|
+
} catch (error) {
|
|
467
|
+
return Promise.reject(error);
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
serviceBaseUrl: (serviceName) => {
|
|
471
|
+
requireService(serviceName);
|
|
472
|
+
return deps.services.resolveBaseUrl(serviceName);
|
|
473
|
+
},
|
|
474
|
+
connection: (serviceName) => {
|
|
475
|
+
requireService(serviceName);
|
|
476
|
+
return deps.connection(serviceName);
|
|
477
|
+
},
|
|
478
|
+
manage: (resource) => {
|
|
479
|
+
deps.assertActive(`зарегистрировать ресурс feature «${name}»`);
|
|
480
|
+
if (controller.signal.aborted) throw inactive();
|
|
481
|
+
const unregister = deps.manage(resource);
|
|
482
|
+
let registered = true;
|
|
483
|
+
const release = () => {
|
|
484
|
+
if (!registered) return;
|
|
485
|
+
registered = false;
|
|
486
|
+
unregister();
|
|
487
|
+
};
|
|
488
|
+
if (!activation.committed) transaction.add(() => {
|
|
489
|
+
if (!registered) return;
|
|
490
|
+
if (activation.committed) release();
|
|
491
|
+
else stopUnpublishedResource(resource, release, name, deps.logger);
|
|
492
|
+
});
|
|
493
|
+
return release;
|
|
494
|
+
},
|
|
495
|
+
assertActive
|
|
496
|
+
});
|
|
418
497
|
}
|
|
419
498
|
/** Реестр подключаемых модулей клиента. @internal */
|
|
420
499
|
var FeatureRegistry = class {
|
|
@@ -430,8 +509,8 @@ var FeatureRegistry = class {
|
|
|
430
509
|
for (const service of definitions) {
|
|
431
510
|
const serviceName = requireName(service?.name, `Сервис feature «${featureName}»`);
|
|
432
511
|
const owner = this.#serviceOwners.get(serviceName);
|
|
433
|
-
if (owner !== void 0) throw new
|
|
434
|
-
if (services.has(serviceName)) throw new
|
|
512
|
+
if (owner !== void 0) throw new require_validate.ItdConfigError(`feature «${featureName}»: сервис «${serviceName}» уже принадлежит feature «${owner}»`);
|
|
513
|
+
if (services.has(serviceName)) throw new require_validate.ItdConfigError(`feature «${featureName}»: сервис «${serviceName}» объявлен повторно`);
|
|
435
514
|
if (!this.#deps.services.has(serviceName)) {
|
|
436
515
|
this.#deps.services.define(service);
|
|
437
516
|
transaction.add(() => void this.#deps.services.delete(serviceName));
|
|
@@ -457,8 +536,6 @@ var FeatureRegistry = class {
|
|
|
457
536
|
validateBucket(featureName, localName, bucket);
|
|
458
537
|
const globalName = `feature:${featureName}/${localName}`;
|
|
459
538
|
transaction.add(this.#deps.catalog.registerBucket(featureName, globalName, bucket));
|
|
460
|
-
const unregisterQueue = this.#deps.registerBucket(globalName, bucket);
|
|
461
|
-
if (unregisterQueue) transaction.add(unregisterQueue);
|
|
462
539
|
bucketNames.set(localName, globalName);
|
|
463
540
|
}
|
|
464
541
|
return bucketNames;
|
|
@@ -467,22 +544,16 @@ var FeatureRegistry = class {
|
|
|
467
544
|
const operations = /* @__PURE__ */ new Map();
|
|
468
545
|
for (const [rawLocalName, definition] of Object.entries(definitions)) {
|
|
469
546
|
const localName = requireName(rawLocalName, `Операция feature «${featureName}»`);
|
|
470
|
-
if (typeof definition !== "object" || definition === null) throw new
|
|
471
|
-
if (!
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
if (!Object.values(require_url.RetrySafety).includes(definition.retrySafety)) throw new require_errors.ItdConfigError(`feature «${featureName}»: операция «${localName}» содержит неизвестную retrySafety`);
|
|
479
|
-
if (definition.service !== void 0 && typeof definition.service !== "string") throw new require_errors.ItdConfigError(`feature «${featureName}»: service операции «${localName}» должен быть строкой`);
|
|
480
|
-
if (definition.bucket !== void 0 && typeof definition.bucket !== "string") throw new require_errors.ItdConfigError(`feature «${featureName}»: bucket операции «${localName}» должен быть строкой`);
|
|
481
|
-
if (definition.read !== void 0 && typeof definition.read !== "function") throw new require_errors.ItdConfigError(`feature «${featureName}»: read операции «${localName}» должна быть функцией`);
|
|
482
|
-
if (definition.annotations !== void 0 && (typeof definition.annotations !== "object" || definition.annotations === null || Array.isArray(definition.annotations))) throw new require_errors.ItdConfigError(`feature «${featureName}»: annotations операции «${localName}» должны быть объектом`);
|
|
483
|
-
if (definition.service !== void 0 && !services.has(definition.service)) throw new require_errors.ItdConfigError(`feature «${featureName}»: операция «${localName}» ссылается на необъявленный сервис «${definition.service}»`);
|
|
547
|
+
if (typeof definition !== "object" || definition === null) throw new require_validate.ItdConfigError(`feature «${featureName}»: операция «${localName}» должна быть объектом`);
|
|
548
|
+
if (!Object.values(require_url.OperationMethod).includes(definition.method)) throw new require_validate.ItdConfigError(`feature «${featureName}»: операция «${localName}» содержит неизвестный HTTP-метод`);
|
|
549
|
+
if (!Object.values(require_url.RetrySafety).includes(definition.retrySafety)) throw new require_validate.ItdConfigError(`feature «${featureName}»: операция «${localName}» содержит неизвестную retrySafety`);
|
|
550
|
+
if (definition.service !== void 0 && typeof definition.service !== "string") throw new require_validate.ItdConfigError(`feature «${featureName}»: service операции «${localName}» должен быть строкой`);
|
|
551
|
+
if (definition.bucket !== void 0 && typeof definition.bucket !== "string") throw new require_validate.ItdConfigError(`feature «${featureName}»: bucket операции «${localName}» должен быть строкой`);
|
|
552
|
+
if (definition.read !== void 0 && typeof definition.read !== "function") throw new require_validate.ItdConfigError(`feature «${featureName}»: read операции «${localName}» должна быть функцией`);
|
|
553
|
+
if (definition.annotations !== void 0 && (typeof definition.annotations !== "object" || definition.annotations === null || Array.isArray(definition.annotations))) throw new require_validate.ItdConfigError(`feature «${featureName}»: annotations операции «${localName}» должны быть объектом`);
|
|
554
|
+
if (definition.service !== void 0 && !services.has(definition.service)) throw new require_validate.ItdConfigError(`feature «${featureName}»: операция «${localName}» ссылается на необъявленный сервис «${definition.service}»`);
|
|
484
555
|
const bucket = definition.bucket === void 0 ? this.#deps.catalog.defaultBucket : bucketNames.get(definition.bucket);
|
|
485
|
-
if (bucket === void 0) throw new
|
|
556
|
+
if (bucket === void 0) throw new require_validate.ItdConfigError(`feature «${featureName}»: операция «${localName}» ссылается на необъявленный бакет «${definition.bucket}»`);
|
|
486
557
|
const operationId = `${featureName}.${localName}`;
|
|
487
558
|
const contract = require_url.defineOperation(operationId, {
|
|
488
559
|
method: definition.method,
|
|
@@ -505,99 +576,22 @@ var FeatureRegistry = class {
|
|
|
505
576
|
}
|
|
506
577
|
install(feature) {
|
|
507
578
|
this.#deps.assertActive("установить feature");
|
|
508
|
-
if (this.#disposed) throw new
|
|
579
|
+
if (this.#disposed) throw new require_validate.ItdStateError("Реестр feature уже освобождён");
|
|
509
580
|
const name = validateFeatureDefinition(feature, (candidate) => this.#features.has(candidate));
|
|
510
581
|
const transaction = new FeatureInstallationTransaction();
|
|
511
582
|
const controller = new AbortController();
|
|
512
|
-
let committed = false;
|
|
513
|
-
const reportRollbackError = (message, error) => {
|
|
514
|
-
try {
|
|
515
|
-
this.#deps.logger?.error(message, error);
|
|
516
|
-
} catch {}
|
|
517
|
-
};
|
|
518
583
|
try {
|
|
519
584
|
const services = this.#registerServices(name, feature.services ?? [], transaction);
|
|
520
585
|
const bucketNames = this.#registerBuckets(name, feature.buckets ?? {}, transaction);
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
this.#deps.assertActive(`открыть файловый источник feature «${name}»`);
|
|
531
|
-
if (!committed || controller.signal.aborted) throw new require_errors.ItdStateError(`feature «${name}» не активен`);
|
|
532
|
-
return this.#deps.files.resolve(input, options, context);
|
|
533
|
-
} catch (error) {
|
|
534
|
-
return Promise.reject(error);
|
|
535
|
-
}
|
|
536
|
-
} }),
|
|
537
|
-
request: (operation, options) => {
|
|
538
|
-
try {
|
|
539
|
-
this.#deps.assertActive(`выполнить операцию feature «${name}»`);
|
|
540
|
-
if (!committed || controller.signal.aborted) throw new require_errors.ItdStateError(`feature «${name}» не активен`);
|
|
541
|
-
const resolved = operations.get(operation);
|
|
542
|
-
if (!resolved) throw new require_errors.ItdConfigError(`feature «${name}» не объявляет операцию «${operation}»`);
|
|
543
|
-
const scoped = { ...options };
|
|
544
|
-
delete scoped.operationId;
|
|
545
|
-
delete scoped.method;
|
|
546
|
-
delete scoped.service;
|
|
547
|
-
delete scoped.baseUrl;
|
|
548
|
-
delete scoped.retrySafety;
|
|
549
|
-
delete scoped.rateLimitBucket;
|
|
550
|
-
return this.#deps.http.execute(resolved.contract, {
|
|
551
|
-
...scoped,
|
|
552
|
-
...resolved.service === void 0 ? {} : { service: resolved.service }
|
|
553
|
-
});
|
|
554
|
-
} catch (error) {
|
|
555
|
-
return Promise.reject(error);
|
|
556
|
-
}
|
|
557
|
-
},
|
|
558
|
-
serviceBaseUrl: (serviceName) => {
|
|
559
|
-
if (!services.has(serviceName)) throw new require_errors.ItdConfigError(`feature «${name}» не объявляет сервис «${serviceName}»`);
|
|
560
|
-
return this.#deps.services.resolveBaseUrl(serviceName);
|
|
561
|
-
},
|
|
562
|
-
connection: (serviceName) => {
|
|
563
|
-
if (!services.has(serviceName)) throw new require_errors.ItdConfigError(`feature «${name}» не объявляет сервис «${serviceName}»`);
|
|
564
|
-
return this.#deps.connection(serviceName);
|
|
565
|
-
},
|
|
566
|
-
manage: (resource) => {
|
|
567
|
-
this.#deps.assertActive(`зарегистрировать ресурс feature «${name}»`);
|
|
568
|
-
if (controller.signal.aborted) throw new require_errors.ItdStateError(`feature «${name}» не активен`);
|
|
569
|
-
const unregister = this.#deps.manage(resource);
|
|
570
|
-
let registered = true;
|
|
571
|
-
const release = () => {
|
|
572
|
-
if (!registered) return;
|
|
573
|
-
registered = false;
|
|
574
|
-
unregister();
|
|
575
|
-
};
|
|
576
|
-
if (!committed) transaction.add(() => {
|
|
577
|
-
if (!registered) return;
|
|
578
|
-
if (committed) {
|
|
579
|
-
release();
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
582
|
-
try {
|
|
583
|
-
resource.stop();
|
|
584
|
-
} catch (error) {
|
|
585
|
-
reportRollbackError(`Не удалось остановить ресурс «${resource.kind}» при откате feature «${name}»`, error);
|
|
586
|
-
} finally {
|
|
587
|
-
release();
|
|
588
|
-
}
|
|
589
|
-
Promise.resolve().then(() => resource.drain()).catch((error) => {
|
|
590
|
-
reportRollbackError(`Не удалось освободить ресурс «${resource.kind}» при откате feature «${name}»`, error);
|
|
591
|
-
});
|
|
592
|
-
});
|
|
593
|
-
return release;
|
|
594
|
-
},
|
|
595
|
-
assertActive: (action) => {
|
|
596
|
-
this.#deps.assertActive(action);
|
|
597
|
-
if (!committed || controller.signal.aborted) throw new require_errors.ItdStateError(`feature «${name}» не активен`);
|
|
598
|
-
}
|
|
599
|
-
});
|
|
600
|
-
const installation = feature.setup(context);
|
|
586
|
+
const activation = {
|
|
587
|
+
name,
|
|
588
|
+
controller,
|
|
589
|
+
services,
|
|
590
|
+
operations: this.#registerOperations(name, feature.operations, services, bucketNames, transaction),
|
|
591
|
+
transaction,
|
|
592
|
+
committed: false
|
|
593
|
+
};
|
|
594
|
+
const installation = feature.setup(createFeatureContext(this.#deps, activation));
|
|
601
595
|
validateFeatureInstallation(name, installation);
|
|
602
596
|
this.#features.set(name, {
|
|
603
597
|
name,
|
|
@@ -606,12 +600,12 @@ var FeatureRegistry = class {
|
|
|
606
600
|
dispose: installation.dispose,
|
|
607
601
|
cleanup: transaction.snapshot()
|
|
608
602
|
});
|
|
609
|
-
committed = true;
|
|
603
|
+
activation.committed = true;
|
|
610
604
|
return installation.api;
|
|
611
605
|
} catch (error) {
|
|
612
|
-
controller.abort(new
|
|
606
|
+
controller.abort(new require_validate.ItdAbortError(`Установка feature «${name}» отменена`));
|
|
613
607
|
transaction.rollback((rollbackError) => {
|
|
614
|
-
reportRollbackError(`Не удалось полностью откатить feature «${name}»`, rollbackError);
|
|
608
|
+
reportRollbackError(this.#deps.logger, `Не удалось полностью откатить feature «${name}»`, rollbackError);
|
|
615
609
|
});
|
|
616
610
|
throw error;
|
|
617
611
|
}
|
|
@@ -633,7 +627,7 @@ var FeatureRegistry = class {
|
|
|
633
627
|
const disposals = [];
|
|
634
628
|
const features = [...this.#features.values()].reverse();
|
|
635
629
|
for (const feature of features) {
|
|
636
|
-
feature.controller.abort(new
|
|
630
|
+
feature.controller.abort(new require_validate.ItdAbortError(`Feature «${feature.name}» освобождён`));
|
|
637
631
|
try {
|
|
638
632
|
disposals.push(Promise.resolve(feature.dispose?.()).then(() => void 0));
|
|
639
633
|
} catch (error) {
|
|
@@ -658,8 +652,8 @@ var ManagedResourceRegistry = class {
|
|
|
658
652
|
#resources = /* @__PURE__ */ new Set();
|
|
659
653
|
#disposed = false;
|
|
660
654
|
register(resource) {
|
|
661
|
-
if (this.#disposed) throw new
|
|
662
|
-
if (typeof resource !== "object" || resource === null || typeof resource.kind !== "string" || resource.kind.trim() === "" || typeof resource.stop !== "function" || typeof resource.drain !== "function") throw new
|
|
655
|
+
if (this.#disposed) throw new require_validate.ItdStateError("Lifecycle клиента уже освобождён");
|
|
656
|
+
if (typeof resource !== "object" || resource === null || typeof resource.kind !== "string" || resource.kind.trim() === "" || typeof resource.stop !== "function" || typeof resource.drain !== "function") throw new require_validate.ItdConfigError("Managed resource должен предоставлять kind, stop() и drain()");
|
|
663
657
|
this.#resources.add(resource);
|
|
664
658
|
let registered = true;
|
|
665
659
|
return () => {
|
|
@@ -714,8 +708,7 @@ var ClientFeatureHost = class {
|
|
|
714
708
|
files: this.files,
|
|
715
709
|
assertActive: options.assertActive,
|
|
716
710
|
connection: (serviceName) => runtime.connection(serviceName),
|
|
717
|
-
manage: (resource) => this.#resources.register(resource)
|
|
718
|
-
registerBucket: (name, definition) => runtime.registerRateLimitBucket(name, definition)
|
|
711
|
+
manage: (resource) => this.#resources.register(resource)
|
|
719
712
|
});
|
|
720
713
|
}
|
|
721
714
|
install(feature) {
|
|
@@ -755,7 +748,7 @@ var ClientFeatureHost = class {
|
|
|
755
748
|
});
|
|
756
749
|
return await deadline.wait(result) ? void 0 : kind;
|
|
757
750
|
}))).filter((kind) => kind !== void 0);
|
|
758
|
-
if (stuck.length > 0) errors.push(new
|
|
751
|
+
if (stuck.length > 0) errors.push(new require_validate.ItdStateError(`ресурсы (${stuck.join(", ")}) не завершились за ${this.#runtime.config.shutdownTimeout} мс`));
|
|
759
752
|
} finally {
|
|
760
753
|
deadline.cancel();
|
|
761
754
|
}
|
|
@@ -772,7 +765,7 @@ var ClientFeatureHost = class {
|
|
|
772
765
|
failure = error;
|
|
773
766
|
});
|
|
774
767
|
try {
|
|
775
|
-
if (!await deadline.wait(settled)) throw new
|
|
768
|
+
if (!await deadline.wait(settled)) throw new require_validate.ItdStateError(`освобождение подключаемых модулей не завершилось за ${this.#runtime.config.shutdownTimeout} мс`);
|
|
776
769
|
} finally {
|
|
777
770
|
deadline.cancel();
|
|
778
771
|
}
|
|
@@ -940,7 +933,7 @@ var Paginator = class {
|
|
|
940
933
|
#finished = false;
|
|
941
934
|
#pagesLoaded = 0;
|
|
942
935
|
constructor(options) {
|
|
943
|
-
if (options.maxPages !== void 0 && (!Number.isInteger(options.maxPages) || options.maxPages < 1)) throw new
|
|
936
|
+
if (options.maxPages !== void 0 && (!Number.isInteger(options.maxPages) || options.maxPages < 1)) throw new require_validate.ItdConfigError(`maxPages должен быть целым числом не меньше 1, получено: ${options.maxPages}`);
|
|
944
937
|
this.#options = options;
|
|
945
938
|
this.#maxPages = options.maxPages ?? 1e3;
|
|
946
939
|
this.#state = options.start ?? {};
|
|
@@ -1293,7 +1286,7 @@ function toDate(value) {
|
|
|
1293
1286
|
//#region src/resources/status.ts
|
|
1294
1287
|
/** Приводит `last_checked` каждого сервиса к ISO. Остальное остаётся как прислал сервер. */
|
|
1295
1288
|
function normalizeStatus(body) {
|
|
1296
|
-
if (!
|
|
1289
|
+
if (!require_validate.isRecord(body) || !Array.isArray(body.services)) return body;
|
|
1297
1290
|
return {
|
|
1298
1291
|
...body,
|
|
1299
1292
|
services: body.services.map((service) => typeof service?.last_checked === "string" ? {
|
|
@@ -1372,14 +1365,14 @@ function validateComment(input) {
|
|
|
1372
1365
|
const files = input?.files ?? [];
|
|
1373
1366
|
const hasContent = content.trim() !== "";
|
|
1374
1367
|
const hasAttachments = attachmentIds.length > 0 || files.length > 0;
|
|
1375
|
-
if (!hasContent && !hasAttachments) throw new
|
|
1368
|
+
if (!hasContent && !hasAttachments) throw new require_validate.ItdConfigError("Комментарий пуст: нужен текст или вложение");
|
|
1376
1369
|
return input;
|
|
1377
1370
|
}
|
|
1378
1371
|
/** Дополнительная проверка голосового комментария. */
|
|
1379
1372
|
function validateVoice(state) {
|
|
1380
|
-
if (state.content.trim() !== "") throw new
|
|
1373
|
+
if (state.content.trim() !== "") throw new require_validate.ItdConfigError("У голосового комментария не может быть текста: API принимает либо текст, либо аудио");
|
|
1381
1374
|
const total = state.files.length + state.attachmentIds.length;
|
|
1382
|
-
if (total !== 1) throw new
|
|
1375
|
+
if (total !== 1) throw new require_validate.ItdConfigError(`Голосовой комментарий требует ровно одно аудиовложение, передано: ${total}`);
|
|
1383
1376
|
}
|
|
1384
1377
|
/**
|
|
1385
1378
|
* Билдер комментария и ответа на комментарий.
|
|
@@ -1488,7 +1481,7 @@ function comment(content = "") {
|
|
|
1488
1481
|
*/
|
|
1489
1482
|
function resolveComment(input, allowReplyTo = false) {
|
|
1490
1483
|
const resolved = resolveInput(input, () => comment(), validateComment);
|
|
1491
|
-
if (!allowReplyTo && resolved.replyToUserId !== void 0) throw new
|
|
1484
|
+
if (!allowReplyTo && resolved.replyToUserId !== void 0) throw new require_validate.ItdConfigError("replyTo применим только к ответу на комментарий (itd.comments.reply). В комментарии к посту адресат не указывается.");
|
|
1492
1485
|
return resolved;
|
|
1493
1486
|
}
|
|
1494
1487
|
//#endregion
|
|
@@ -1733,8 +1726,8 @@ function isAllowedMime(mimeType) {
|
|
|
1733
1726
|
* @throws {ItdConfigError} если тип не поддерживается
|
|
1734
1727
|
*/
|
|
1735
1728
|
function assertAllowedMime(mimeType, filename) {
|
|
1736
|
-
if (!mimeType) throw new
|
|
1737
|
-
if (!isAllowedMime(mimeType)) throw new
|
|
1729
|
+
if (!mimeType) throw new require_validate.ItdConfigError(`Не удалось определить тип файла${filename ? ` «${filename}»` : ""}. Укажите его явно: { file, filename, contentType }.`);
|
|
1730
|
+
if (!isAllowedMime(mimeType)) throw new require_validate.ItdConfigError(`Тип «${mimeType}» не поддерживается загрузкой. Допустимые: ${ALLOWED_MIME_TYPES.join(", ")}.`);
|
|
1738
1731
|
}
|
|
1739
1732
|
//#endregion
|
|
1740
1733
|
//#region src/resources/files.ts
|
|
@@ -1982,12 +1975,12 @@ var PlatformResource = class extends BaseResource {
|
|
|
1982
1975
|
* Возвращает копии, чтобы последующая мутация входного массива не меняла уже собранные данные.
|
|
1983
1976
|
*/
|
|
1984
1977
|
function validateSpans(content, spans) {
|
|
1985
|
-
if (!Array.isArray(spans)) throw new
|
|
1986
|
-
if (content.length === 0 && spans.length > 0) throw new
|
|
1978
|
+
if (!Array.isArray(spans)) throw new require_validate.ItdConfigError("spans должен быть массивом");
|
|
1979
|
+
if (content.length === 0 && spans.length > 0) throw new require_validate.ItdConfigError("Нельзя задать spans без текста: сначала укажите непустой content");
|
|
1987
1980
|
return spans.map((span, index) => {
|
|
1988
|
-
if (!span || typeof span !== "object") throw new
|
|
1981
|
+
if (!span || typeof span !== "object") throw new require_validate.ItdConfigError(`Некорректный span №${index + 1}: ожидался объект`);
|
|
1989
1982
|
const candidate = span;
|
|
1990
|
-
if (typeof candidate.type !== "string" || !Number.isInteger(candidate.offset) || !Number.isInteger(candidate.length) || (candidate.offset ?? -1) < 0 || (candidate.length ?? 0) <= 0 || (candidate.offset ?? 0) + (candidate.length ?? 0) > content.length) throw new
|
|
1983
|
+
if (typeof candidate.type !== "string" || !Number.isInteger(candidate.offset) || !Number.isInteger(candidate.length) || (candidate.offset ?? -1) < 0 || (candidate.length ?? 0) <= 0 || (candidate.offset ?? 0) + (candidate.length ?? 0) > content.length) throw new require_validate.ItdConfigError(`Некорректный span №${index + 1}: offset и length должны указывать на непустой фрагмент content`);
|
|
1991
1984
|
return { ...candidate };
|
|
1992
1985
|
});
|
|
1993
1986
|
}
|
|
@@ -2459,7 +2452,7 @@ function parseHtml(source, options = {}) {
|
|
|
2459
2452
|
//#endregion
|
|
2460
2453
|
//#region src/builders/markup.ts
|
|
2461
2454
|
function validateMarkup(value) {
|
|
2462
|
-
if (!value || typeof value.content !== "string" || !Array.isArray(value.spans)) throw new
|
|
2455
|
+
if (!value || typeof value.content !== "string" || !Array.isArray(value.spans)) throw new require_validate.ItdConfigError("Разметка должна содержать строку content и массив spans");
|
|
2463
2456
|
return {
|
|
2464
2457
|
content: value.content,
|
|
2465
2458
|
spans: validateSpans(value.content, value.spans).sort((left, right) => left.offset - right.offset || right.length - left.length)
|
|
@@ -2467,7 +2460,7 @@ function validateMarkup(value) {
|
|
|
2467
2460
|
}
|
|
2468
2461
|
function entityName(value, prefix, label) {
|
|
2469
2462
|
const name = value.startsWith(prefix) ? value.slice(prefix.length) : value;
|
|
2470
|
-
if (name === "" || /\s/u.test(name)) throw new
|
|
2463
|
+
if (name === "" || /\s/u.test(name)) throw new require_validate.ItdConfigError(`${label} должен быть непустым и не содержать пробелов`);
|
|
2471
2464
|
return name;
|
|
2472
2465
|
}
|
|
2473
2466
|
/**
|
|
@@ -2492,7 +2485,7 @@ var MarkupBuilder = class MarkupBuilder {
|
|
|
2492
2485
|
}
|
|
2493
2486
|
/** Дописывает переводы строк. */
|
|
2494
2487
|
newline(count = 1) {
|
|
2495
|
-
if (!Number.isInteger(count) || count < 0) throw new
|
|
2488
|
+
if (!Number.isInteger(count) || count < 0) throw new require_validate.ItdConfigError("Количество переводов строк должно быть целым неотрицательным");
|
|
2496
2489
|
return this.text("\n".repeat(count));
|
|
2497
2490
|
}
|
|
2498
2491
|
/** @internal Добавляет готовый фрагмент и сдвигает его spans к текущему концу текста. */
|
|
@@ -2501,7 +2494,7 @@ var MarkupBuilder = class MarkupBuilder {
|
|
|
2501
2494
|
content: value,
|
|
2502
2495
|
spans: []
|
|
2503
2496
|
} : resolveMarkup(value);
|
|
2504
|
-
if (fragment.content.length === 0) throw new
|
|
2497
|
+
if (fragment.content.length === 0) throw new require_validate.ItdConfigError("Размечаемый фрагмент не может быть пустым");
|
|
2505
2498
|
const offset = this.#content.length;
|
|
2506
2499
|
const shifted = fragment.spans.map((span) => ({
|
|
2507
2500
|
...span,
|
|
@@ -2561,7 +2554,7 @@ var MarkupBuilder = class MarkupBuilder {
|
|
|
2561
2554
|
*/
|
|
2562
2555
|
link(content, url) {
|
|
2563
2556
|
const resolvedUrl = url ?? (typeof content === "string" ? content : void 0);
|
|
2564
|
-
if (resolvedUrl === void 0) throw new
|
|
2557
|
+
if (resolvedUrl === void 0) throw new require_validate.ItdConfigError("Для форматированного текста ссылки нужно явно передать URL");
|
|
2565
2558
|
return this.span(content, {
|
|
2566
2559
|
type: require_url.SpanType.Link,
|
|
2567
2560
|
url: resolvedUrl
|
|
@@ -2707,19 +2700,19 @@ const MAX_OPTION_LENGTH = 100;
|
|
|
2707
2700
|
*/
|
|
2708
2701
|
function validatePoll(input) {
|
|
2709
2702
|
const question = input?.question;
|
|
2710
|
-
if (typeof question !== "string" || question.trim() === "") throw new
|
|
2711
|
-
if (question.trim().length > MAX_QUESTION_LENGTH) throw new
|
|
2703
|
+
if (typeof question !== "string" || question.trim() === "") throw new require_validate.ItdConfigError("Опрос требует непустого вопроса");
|
|
2704
|
+
if (question.trim().length > MAX_QUESTION_LENGTH) throw new require_validate.ItdConfigError(`Вопрос длиннее ${MAX_QUESTION_LENGTH} символов (передано ${question.trim().length})`);
|
|
2712
2705
|
const texts = (Array.isArray(input.options) ? input.options : []).map((option, index) => {
|
|
2713
2706
|
const text = typeof option?.text === "string" ? option.text.trim() : "";
|
|
2714
|
-
if (text === "") throw new
|
|
2715
|
-
if (text.length > MAX_OPTION_LENGTH) throw new
|
|
2707
|
+
if (text === "") throw new require_validate.ItdConfigError(`Вариант ответа №${index + 1} пуст — у каждого должен быть текст`);
|
|
2708
|
+
if (text.length > MAX_OPTION_LENGTH) throw new require_validate.ItdConfigError(`Вариант ответа №${index + 1} длиннее ${MAX_OPTION_LENGTH} символов (передано ${text.length})`);
|
|
2716
2709
|
return text;
|
|
2717
2710
|
});
|
|
2718
|
-
if (texts.length < MIN_OPTIONS) throw new
|
|
2719
|
-
if (texts.length > MAX_OPTIONS) throw new
|
|
2711
|
+
if (texts.length < MIN_OPTIONS) throw new require_validate.ItdConfigError(`Опрос требует минимум ${MIN_OPTIONS} варианта, передано: ${texts.length}`);
|
|
2712
|
+
if (texts.length > MAX_OPTIONS) throw new require_validate.ItdConfigError(`Опрос допускает не больше ${MAX_OPTIONS} вариантов, передано: ${texts.length}`);
|
|
2720
2713
|
const seen = /* @__PURE__ */ new Set();
|
|
2721
2714
|
for (const text of texts) {
|
|
2722
|
-
if (seen.has(text)) throw new
|
|
2715
|
+
if (seen.has(text)) throw new require_validate.ItdConfigError(`Вариант «${text}» повторяется — варианты должны различаться`);
|
|
2723
2716
|
seen.add(text);
|
|
2724
2717
|
}
|
|
2725
2718
|
return {
|
|
@@ -2831,10 +2824,10 @@ function validatePost(input) {
|
|
|
2831
2824
|
const hasContent = content.trim() !== "";
|
|
2832
2825
|
const hasAttachments = attachmentIds.length > 0 || files.length > 0;
|
|
2833
2826
|
const hasPoll = Boolean(input?.poll);
|
|
2834
|
-
if (!hasContent && !hasAttachments && !hasPoll) throw new
|
|
2827
|
+
if (!hasContent && !hasAttachments && !hasPoll) throw new require_validate.ItdConfigError("Пост пуст: нужен текст, вложение или опрос");
|
|
2835
2828
|
const wallRecipientId = input.wallRecipientId;
|
|
2836
2829
|
if (wallRecipientId !== void 0 && wallRecipientId !== null) {
|
|
2837
|
-
if (!UUID_PATTERN.test(wallRecipientId)) throw new
|
|
2830
|
+
if (!UUID_PATTERN.test(wallRecipientId)) throw new require_validate.ItdConfigError(`wallRecipientId должен быть UUID, а не именем пользователя (получено: «${wallRecipientId}»). Идентификатор можно взять из профиля: (await itd.users.get(username)).id`);
|
|
2838
2831
|
}
|
|
2839
2832
|
const { poll: inputPoll, ...data } = input;
|
|
2840
2833
|
return {
|
|
@@ -3041,17 +3034,17 @@ function resolvePost(input) {
|
|
|
3041
3034
|
return isBuilder(resolved) ? validatePost(resolved.build()) : validatePost(resolved);
|
|
3042
3035
|
}
|
|
3043
3036
|
function validatePostUpdate(input) {
|
|
3044
|
-
if (!input || typeof input !== "object") throw new
|
|
3037
|
+
if (!input || typeof input !== "object") throw new require_validate.ItdConfigError("Для обновления поста нужен объект с явно заданным content");
|
|
3045
3038
|
const unsupported = [
|
|
3046
3039
|
"wallRecipientId",
|
|
3047
3040
|
"attachmentIds",
|
|
3048
3041
|
"files",
|
|
3049
3042
|
"poll"
|
|
3050
3043
|
].filter((key) => input[key] !== void 0);
|
|
3051
|
-
if (unsupported.length > 0) throw new
|
|
3052
|
-
if (input.content === void 0) throw new
|
|
3053
|
-
if (typeof input.content !== "string") throw new
|
|
3054
|
-
if (input.spans !== void 0 && !Array.isArray(input.spans)) throw new
|
|
3044
|
+
if (unsupported.length > 0) throw new require_validate.ItdConfigError(`posts.update изменяет только content и spans; не поддерживаются: ${unsupported.join(", ")}`);
|
|
3045
|
+
if (input.content === void 0) throw new require_validate.ItdConfigError("posts.update требует явно заданный content; обновление одних spans могло бы стереть текст поста");
|
|
3046
|
+
if (typeof input.content !== "string") throw new require_validate.ItdConfigError("content обновляемого поста должен быть строкой");
|
|
3047
|
+
if (input.spans !== void 0 && !Array.isArray(input.spans)) throw new require_validate.ItdConfigError("spans обновляемого поста должен быть массивом");
|
|
3055
3048
|
return {
|
|
3056
3049
|
content: input.content,
|
|
3057
3050
|
...input.spans !== void 0 ? { spans: validateSpans(input.content, input.spans) } : {}
|
|
@@ -3420,9 +3413,9 @@ const TARGET_TYPES = new Set(Object.values(require_url.ReportTargetType));
|
|
|
3420
3413
|
* @throws {ItdConfigError} если объект жалобы или причина заданы неверно
|
|
3421
3414
|
*/
|
|
3422
3415
|
function validateReport(input) {
|
|
3423
|
-
if (!input?.targetId || typeof input.targetId !== "string") throw new
|
|
3424
|
-
if (!TARGET_TYPES.has(input.targetType)) throw new
|
|
3425
|
-
if (!REASONS.has(input.reason)) throw new
|
|
3416
|
+
if (!input?.targetId || typeof input.targetId !== "string") throw new require_validate.ItdConfigError("Жалоба требует идентификатор объекта (targetId)");
|
|
3417
|
+
if (!TARGET_TYPES.has(input.targetType)) throw new require_validate.ItdConfigError(`targetType должен быть одним из ${[...TARGET_TYPES].join(", ")}, получено: ${String(input.targetType)}`);
|
|
3418
|
+
if (!REASONS.has(input.reason)) throw new require_validate.ItdConfigError(`Неизвестная причина жалобы «${String(input.reason)}». Допустимые: ${[...REASONS].join(", ")}`);
|
|
3426
3419
|
return input;
|
|
3427
3420
|
}
|
|
3428
3421
|
/**
|
|
@@ -3595,7 +3588,7 @@ var ShopProductsResource = class extends BaseResource {
|
|
|
3595
3588
|
...options
|
|
3596
3589
|
});
|
|
3597
3590
|
} catch (error) {
|
|
3598
|
-
if (
|
|
3591
|
+
if (require_validate.isItdApiError(error) && error.status === 404) return null;
|
|
3599
3592
|
throw error;
|
|
3600
3593
|
}
|
|
3601
3594
|
}
|
|
@@ -3814,20 +3807,20 @@ var SubscriptionResource = class extends BaseResource {
|
|
|
3814
3807
|
const DEFAULT_BATCH_SIZE = 50;
|
|
3815
3808
|
const SYSTEM_CLOCK = Object.freeze({ now: () => Date.now() });
|
|
3816
3809
|
function assertNonEmpty(value, name) {
|
|
3817
|
-
if (value.trim().length === 0) throw new
|
|
3810
|
+
if (value.trim().length === 0) throw new require_validate.ItdConfigError(`${name} должен быть непустой строкой`);
|
|
3818
3811
|
}
|
|
3819
3812
|
function assertFinite(value, name) {
|
|
3820
|
-
if (!Number.isFinite(value)) throw new
|
|
3813
|
+
if (!Number.isFinite(value)) throw new require_validate.ItdConfigError(`${name} должен быть конечным числом`);
|
|
3821
3814
|
}
|
|
3822
3815
|
function assertNonNegative(value, name) {
|
|
3823
3816
|
assertFinite(value, name);
|
|
3824
|
-
if (value < 0) throw new
|
|
3817
|
+
if (value < 0) throw new require_validate.ItdConfigError(`${name} должен быть неотрицательным числом`);
|
|
3825
3818
|
}
|
|
3826
3819
|
function validateDwell(entry) {
|
|
3827
3820
|
assertNonEmpty(entry.vs, "vs");
|
|
3828
3821
|
assertFinite(entry.enterAt, "enterAt");
|
|
3829
3822
|
assertFinite(entry.exitAt, "exitAt");
|
|
3830
|
-
if (entry.exitAt < entry.enterAt) throw new
|
|
3823
|
+
if (entry.exitAt < entry.enterAt) throw new require_validate.ItdConfigError("exitAt не может быть раньше enterAt");
|
|
3831
3824
|
if (entry.durationMs !== void 0) assertNonNegative(entry.durationMs, "durationMs");
|
|
3832
3825
|
return { ...entry };
|
|
3833
3826
|
}
|
|
@@ -3836,7 +3829,7 @@ function validateInteraction(entry) {
|
|
|
3836
3829
|
assertNonEmpty(entry.postId, "postId");
|
|
3837
3830
|
if (entry.mediaIndex !== void 0) {
|
|
3838
3831
|
assertNonNegative(entry.mediaIndex, "mediaIndex");
|
|
3839
|
-
if (!Number.isInteger(entry.mediaIndex)) throw new
|
|
3832
|
+
if (!Number.isInteger(entry.mediaIndex)) throw new require_validate.ItdConfigError("mediaIndex должен быть целым числом");
|
|
3840
3833
|
}
|
|
3841
3834
|
if (entry.positionMs !== void 0) assertNonNegative(entry.positionMs, "positionMs");
|
|
3842
3835
|
if (entry.durationMs !== void 0) assertNonNegative(entry.durationMs, "durationMs");
|
|
@@ -3876,7 +3869,7 @@ var ViewTrackerImpl = class {
|
|
|
3876
3869
|
try {
|
|
3877
3870
|
const exitAt = this.#clock.now();
|
|
3878
3871
|
assertFinite(exitAt, "clock.now()");
|
|
3879
|
-
if (exitAt < this.enteredAt) throw new
|
|
3872
|
+
if (exitAt < this.enteredAt) throw new require_validate.ItdConfigError("часы не могут вернуться назад во время измерения просмотра");
|
|
3880
3873
|
this.#result = Promise.resolve(this.#submit({
|
|
3881
3874
|
...this.#input,
|
|
3882
3875
|
enterAt: this.enteredAt,
|
|
@@ -3905,7 +3898,7 @@ var TelemetryBatchImpl = class {
|
|
|
3905
3898
|
#disposeCleanup = false;
|
|
3906
3899
|
constructor(sendDwell, sendInteraction, options, requestOptions, onClose) {
|
|
3907
3900
|
const { maxBatchSize = DEFAULT_BATCH_SIZE, clock = SYSTEM_CLOCK, ...telemetryOptions } = options;
|
|
3908
|
-
if (!Number.isInteger(maxBatchSize) || maxBatchSize < 1) throw new
|
|
3901
|
+
if (!Number.isInteger(maxBatchSize) || maxBatchSize < 1) throw new require_validate.ItdConfigError("maxBatchSize должен быть целым числом от 1");
|
|
3909
3902
|
this.#sendDwell = sendDwell;
|
|
3910
3903
|
this.#sendInteraction = sendInteraction;
|
|
3911
3904
|
this.#telemetryOptions = telemetryOptions;
|
|
@@ -4009,7 +4002,7 @@ var TelemetryBatchImpl = class {
|
|
|
4009
4002
|
}
|
|
4010
4003
|
}
|
|
4011
4004
|
#assertOpen() {
|
|
4012
|
-
if (this.#state !== "open") throw new
|
|
4005
|
+
if (this.#state !== "open") throw new require_validate.ItdConfigError("накопитель телеметрии уже закрывается или закрыт");
|
|
4013
4006
|
}
|
|
4014
4007
|
};
|
|
4015
4008
|
const TELEMETRY_DISPOSE_CLOSERS = /* @__PURE__ */ new WeakMap();
|
|
@@ -4562,15 +4555,15 @@ function fromStream(factory, options = {}) {
|
|
|
4562
4555
|
try {
|
|
4563
4556
|
opened = await factory(context);
|
|
4564
4557
|
} catch (error) {
|
|
4565
|
-
if (error instanceof
|
|
4566
|
-
throw new
|
|
4567
|
-
reason:
|
|
4558
|
+
if (error instanceof require_validate.ItdFileError || error instanceof require_validate.ItdConfigError || context.signal?.aborted) throw error;
|
|
4559
|
+
throw new require_validate.ItdFileError("не удалось открыть поток вложения", {
|
|
4560
|
+
reason: require_validate.ItdFileErrorReason.Read,
|
|
4568
4561
|
retryable: true,
|
|
4569
4562
|
cause: require_redact.redactErrorCause(error)
|
|
4570
4563
|
});
|
|
4571
4564
|
}
|
|
4572
4565
|
const content = require_options.isReadableByteStream(opened) ? { stream: opened } : opened;
|
|
4573
|
-
if (!content || !require_options.isReadableByteStream(content.stream)) throw new
|
|
4566
|
+
if (!content || !require_options.isReadableByteStream(content.stream)) throw new require_validate.ItdConfigError("fromStream должен вернуть ReadableStream или { stream }");
|
|
4574
4567
|
const size = options.size ?? content.size;
|
|
4575
4568
|
require_options.optionalBytes(size, "size");
|
|
4576
4569
|
if (resolved.maxBytes !== void 0 && size !== void 0 && size > resolved.maxBytes) {
|
|
@@ -4680,7 +4673,7 @@ var MemoryShopOrderAccessStorage = class {
|
|
|
4680
4673
|
/** Создаёт хранилище доступа к заказам поверх `KeyValueStore`. */
|
|
4681
4674
|
function createShopOrderAccessStorage(store, options = {}) {
|
|
4682
4675
|
const key = options.key ?? "shop-order-access";
|
|
4683
|
-
if (typeof key !== "string" || key.length === 0) throw new
|
|
4676
|
+
if (typeof key !== "string" || key.length === 0) throw new require_validate.ItdConfigError("ключ хранилища доступа к заказам должен быть непустой строкой");
|
|
4684
4677
|
const backend = require_key_value_store.createKeyValueStore(store);
|
|
4685
4678
|
return {
|
|
4686
4679
|
async get() {
|
|
@@ -4702,7 +4695,7 @@ const Operation = Object.freeze({
|
|
|
4702
4695
|
});
|
|
4703
4696
|
function requireEmail(value) {
|
|
4704
4697
|
const email = value.trim();
|
|
4705
|
-
if (!email?.includes("@")) throw new
|
|
4698
|
+
if (!email?.includes("@")) throw new require_validate.ItdConfigError("укажите адрес почты");
|
|
4706
4699
|
return email;
|
|
4707
4700
|
}
|
|
4708
4701
|
/** Создаёт подключаемый модуль для доступа к заказам по коду из письма. */
|
|
@@ -4756,7 +4749,7 @@ function createShopFeature(options = {}) {
|
|
|
4756
4749
|
};
|
|
4757
4750
|
const requireSession = async () => {
|
|
4758
4751
|
const session = await readSession();
|
|
4759
|
-
if (!session) throw new
|
|
4752
|
+
if (!session) throw new require_validate.ItdConfigError("доступ к заказам отсутствует или истёк; запросите новый код");
|
|
4760
4753
|
return session;
|
|
4761
4754
|
};
|
|
4762
4755
|
const withAccess = async (run) => {
|
|
@@ -4764,7 +4757,7 @@ function createShopFeature(options = {}) {
|
|
|
4764
4757
|
try {
|
|
4765
4758
|
return await run(session.token);
|
|
4766
4759
|
} catch (error) {
|
|
4767
|
-
if (
|
|
4760
|
+
if (require_validate.isItdApiError(error) && error.status === 401) await storage.clear();
|
|
4768
4761
|
throw error;
|
|
4769
4762
|
}
|
|
4770
4763
|
};
|
|
@@ -4790,7 +4783,7 @@ function createShopFeature(options = {}) {
|
|
|
4790
4783
|
pendingEmail = normalized;
|
|
4791
4784
|
},
|
|
4792
4785
|
async verifyCode(code, requestOptions = {}) {
|
|
4793
|
-
if (!pendingEmail) throw new
|
|
4786
|
+
if (!pendingEmail) throw new require_validate.ItdConfigError("сначала запросите код для адреса почты");
|
|
4794
4787
|
const verified = await context.request(Operation.VerifyCode, {
|
|
4795
4788
|
path: "/api/v1/shop/orders/lookup/verify",
|
|
4796
4789
|
body: {
|
|
@@ -5258,4 +5251,4 @@ Object.defineProperty(exports, "utcStampToIso", {
|
|
|
5258
5251
|
}
|
|
5259
5252
|
});
|
|
5260
5253
|
|
|
5261
|
-
//# sourceMappingURL=render-
|
|
5254
|
+
//# sourceMappingURL=render-Dnc84B4M.cjs.map
|