itd-api 0.7.1 → 0.7.2
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 -2
- package/dist/index.cjs +81 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -4
- package/dist/index.d.ts +22 -4
- package/dist/index.js +81 -8
- package/dist/index.js.map +1 -1
- package/dist/realtime/index.cjs +3 -3
- package/dist/realtime/index.d.cts +2 -2
- package/dist/realtime/index.d.ts +2 -2
- package/dist/realtime/index.js +3 -3
- package/dist/rest/index.cjs +70 -6
- package/dist/rest/index.cjs.map +1 -1
- package/dist/rest/index.d.cts +11 -3
- package/dist/rest/index.d.ts +11 -3
- package/dist/rest/index.js +70 -6
- package/dist/rest/index.js.map +1 -1
- package/dist/shared/{auth-provider-CTJkKfgy.cjs → auth-provider-CG8oCQ9F.cjs} +2 -2
- package/dist/shared/{auth-provider-CTJkKfgy.cjs.map → auth-provider-CG8oCQ9F.cjs.map} +1 -1
- package/dist/shared/{auth-provider-BfogACAb.js → auth-provider-mYqxsSVa.js} +2 -2
- package/dist/shared/{auth-provider-BfogACAb.js.map → auth-provider-mYqxsSVa.js.map} +1 -1
- package/dist/shared/{render-vtLixIiU.js → render-C6HRPs10.js} +353 -56
- package/dist/shared/render-C6HRPs10.js.map +1 -0
- package/dist/shared/{render-DZrxhC5_.d.ts → render-CgwKdOzu.d.ts} +101 -40
- package/dist/shared/{render-DMp_3Nzk.d.cts → render-DO0F5YSm.d.cts} +101 -40
- package/dist/shared/{render-DyeHJNBw.cjs → render-mcuELiYi.cjs} +369 -54
- package/dist/shared/render-mcuELiYi.cjs.map +1 -0
- package/dist/shared/{url-yjl2c8Ie.cjs → url-BaMCQpYH.cjs} +76 -24
- package/dist/shared/url-BaMCQpYH.cjs.map +1 -0
- package/dist/shared/{url-B6-bXHKt.d.ts → url-DTfZ2toq.d.cts} +6 -8
- package/dist/shared/{url-B6-bXHKt.d.cts → url-DTfZ2toq.d.ts} +6 -8
- package/dist/shared/{url-CYXgxqGx.js → url-IU0xN9wX.js} +65 -25
- package/dist/shared/url-IU0xN9wX.js.map +1 -0
- package/dist/shared/{websocket-CbzB1Leq.js → websocket-BLR8eVJV.js} +2 -2
- package/dist/shared/{websocket-CbzB1Leq.js.map → websocket-BLR8eVJV.js.map} +1 -1
- package/dist/shared/{websocket-sYlynrr0.cjs → websocket-C_eI4H2o.cjs} +2 -2
- package/dist/shared/{websocket-sYlynrr0.cjs.map → websocket-C_eI4H2o.cjs.map} +1 -1
- package/dist/shared/{websocket-D1p32SB2.d.cts → websocket-DF7XIMiX.d.cts} +2 -2
- package/dist/shared/{websocket-BMtihD56.d.ts → websocket-DYKBr8HF.d.ts} +2 -2
- package/package.json +1 -1
- package/dist/shared/render-DyeHJNBw.cjs.map +0 -1
- package/dist/shared/render-vtLixIiU.js.map +0 -1
- package/dist/shared/url-CYXgxqGx.js.map +0 -1
- package/dist/shared/url-yjl2c8Ie.cjs.map +0 -1
|
@@ -1,7 +1,270 @@
|
|
|
1
|
-
import { F as pickArray, I as pickBoolean, L as pickNumber, P as isRecord, R as pickObject,
|
|
2
|
-
import { a as ItdConfigError, l as ItdFileError, s as ItdError, u as ItdFileErrorReason } from "./errors-Bhrd2fJd.js";
|
|
1
|
+
import { F as pickArray, H as RetrySafety, I as pickBoolean, J as DEFAULT_STATUS_BASE_URL, L as pickNumber, P as isRecord, R as pickObject, U as mergeService, Y as STATUS_SERVICE, it as isFile, nt as createDeviceId, p as InteractionType, r as normalizeNotification, rt as isBlob, v as ReportReason, x as SpanType, y as ReportTargetType, z as pickString } from "./url-IU0xN9wX.js";
|
|
2
|
+
import { _ as ItdStateError, a as ItdConfigError, l as ItdFileError, s as ItdError, t as ItdAbortError, u as ItdFileErrorReason } from "./errors-Bhrd2fJd.js";
|
|
3
3
|
import { o as encodePathSegment } from "./cookies-tX2sNwxb.js";
|
|
4
4
|
import { a as fileTooLarge, c as FileTransferMode, i as isReadableByteStream, n as boundedFileStream, o as optionalBytes, r as isBoundedFileStream, s as DEFAULT_URL_FILE_MAX_BYTES, t as resolveFileStreamOptions } from "./options-DtATYdLr.js";
|
|
5
|
+
//#region src/core/feature-catalog.ts
|
|
6
|
+
/**
|
|
7
|
+
* Изменяемый слой над каталогом конкретного API.
|
|
8
|
+
*
|
|
9
|
+
* Базовый каталог остаётся замороженным и общим для всех клиентов, а регистрации feature
|
|
10
|
+
* принадлежат одному facade. Поэтому установка модуля в одном клиенте не меняет остальные.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
var ExtensibleOperationCatalog = class {
|
|
15
|
+
#base;
|
|
16
|
+
#operations = /* @__PURE__ */ new Map();
|
|
17
|
+
#operationOwners = /* @__PURE__ */ new Map();
|
|
18
|
+
#bucketOwners = /* @__PURE__ */ new Map();
|
|
19
|
+
#bucketLimits;
|
|
20
|
+
#bucketOverrides;
|
|
21
|
+
constructor(base) {
|
|
22
|
+
this.#base = base;
|
|
23
|
+
this.#bucketLimits = { ...base.bucketLimits };
|
|
24
|
+
this.#bucketOverrides = { ...base.bucketOverrides };
|
|
25
|
+
}
|
|
26
|
+
get bucketLimits() {
|
|
27
|
+
return this.#bucketLimits;
|
|
28
|
+
}
|
|
29
|
+
get bucketOverrides() {
|
|
30
|
+
return this.#bucketOverrides;
|
|
31
|
+
}
|
|
32
|
+
get defaultBucket() {
|
|
33
|
+
return this.#base.defaultBucket;
|
|
34
|
+
}
|
|
35
|
+
retrySafetyOf(id) {
|
|
36
|
+
return this.#operations.get(id)?.retrySafety ?? this.#base.retrySafetyOf(id);
|
|
37
|
+
}
|
|
38
|
+
methodOf(id) {
|
|
39
|
+
return this.#operations.get(id)?.method ?? this.#base.methodOf(id);
|
|
40
|
+
}
|
|
41
|
+
bucketOf(id) {
|
|
42
|
+
return this.#operations.get(id)?.bucket ?? this.#base.bucketOf(id);
|
|
43
|
+
}
|
|
44
|
+
isKnownBucket(name) {
|
|
45
|
+
return this.#bucketOwners.has(name) || this.#base.isKnownBucket(name);
|
|
46
|
+
}
|
|
47
|
+
/** Регистрирует принадлежащий feature бакет и возвращает откат регистрации. */
|
|
48
|
+
registerBucket(owner, name, definition) {
|
|
49
|
+
if (this.#base.isKnownBucket(name) || this.#bucketOwners.has(name)) throw new ItdConfigError(`feature «${owner}»: бакет «${name}» уже зарегистрирован`);
|
|
50
|
+
this.#bucketOwners.set(name, owner);
|
|
51
|
+
if (definition.limit !== void 0) this.#bucketLimits[name] = definition.limit;
|
|
52
|
+
if (definition.concurrency !== void 0) this.#bucketOverrides[name] = Object.freeze({ concurrency: definition.concurrency });
|
|
53
|
+
return () => {
|
|
54
|
+
if (this.#bucketOwners.get(name) !== owner) return;
|
|
55
|
+
this.#bucketOwners.delete(name);
|
|
56
|
+
delete this.#bucketLimits[name];
|
|
57
|
+
delete this.#bucketOverrides[name];
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Регистрирует операцию и возвращает откат регистрации. */
|
|
61
|
+
registerOperation(owner, id, definition) {
|
|
62
|
+
const ownerPrefix = `${owner}.`;
|
|
63
|
+
if (!id.startsWith(ownerPrefix) || id.length === ownerPrefix.length) throw new ItdConfigError(`feature «${owner}»: операция «${id}» должна находиться в пространстве имён «${owner}»`);
|
|
64
|
+
const occupiedBy = this.#operationOwners.get(id);
|
|
65
|
+
if (occupiedBy !== void 0) throw new ItdConfigError(`feature «${owner}»: операция «${id}» уже зарегистрирована feature «${occupiedBy}»`);
|
|
66
|
+
if (this.#base.methodOf(id) !== void 0) throw new ItdConfigError(`feature «${owner}»: операция «${id}» уже существует в базовом каталоге`);
|
|
67
|
+
this.#operationOwners.set(id, owner);
|
|
68
|
+
this.#operations.set(id, Object.freeze({ ...definition }));
|
|
69
|
+
return () => {
|
|
70
|
+
if (this.#operationOwners.get(id) !== owner) return;
|
|
71
|
+
this.#operationOwners.delete(id);
|
|
72
|
+
this.#operations.delete(id);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/core/features.ts
|
|
78
|
+
function requireName(value, kind) {
|
|
79
|
+
if (typeof value !== "string" || value.trim() === "") throw new ItdConfigError(`${kind} должен иметь непустое имя`);
|
|
80
|
+
return value.trim();
|
|
81
|
+
}
|
|
82
|
+
function requireOperationName(value, featureName) {
|
|
83
|
+
const name = requireName(value, `Операция feature «${featureName}»`);
|
|
84
|
+
if (name.includes(".")) throw new ItdConfigError(`feature «${featureName}»: локальное имя операции «${name}» не должно содержать точку`);
|
|
85
|
+
return name;
|
|
86
|
+
}
|
|
87
|
+
function validateBucket(owner, localName, bucket) {
|
|
88
|
+
if (typeof bucket !== "object" || bucket === null || Array.isArray(bucket)) throw new ItdConfigError(`feature «${owner}»: бакет «${localName}» должен быть объектом`);
|
|
89
|
+
if (bucket.limit !== void 0 && (!Number.isFinite(bucket.limit) || bucket.limit <= 0)) throw new ItdConfigError(`feature «${owner}»: limit бакета «${localName}» должен быть положительным числом`);
|
|
90
|
+
if (bucket.concurrency !== void 0 && (!Number.isInteger(bucket.concurrency) || bucket.concurrency < 1)) throw new ItdConfigError(`feature «${owner}»: concurrency бакета «${localName}» должен быть целым числом от 1`);
|
|
91
|
+
}
|
|
92
|
+
/** Реестр feature одного facade. @internal */
|
|
93
|
+
var FeatureRegistry = class {
|
|
94
|
+
#deps;
|
|
95
|
+
#features = /* @__PURE__ */ new Map();
|
|
96
|
+
#serviceOwners = /* @__PURE__ */ new Map();
|
|
97
|
+
#disposed = false;
|
|
98
|
+
constructor(deps) {
|
|
99
|
+
this.#deps = deps;
|
|
100
|
+
}
|
|
101
|
+
install(feature) {
|
|
102
|
+
this.#deps.assertActive("установить feature");
|
|
103
|
+
if (this.#disposed) throw new ItdStateError("Реестр feature уже освобождён");
|
|
104
|
+
if (typeof feature !== "object" || feature === null) throw new ItdConfigError("feature должен быть объектом");
|
|
105
|
+
const name = requireName(feature.name, "Feature");
|
|
106
|
+
if (this.#features.has(name)) throw new ItdConfigError(`feature «${name}» уже установлен`);
|
|
107
|
+
if (typeof feature.setup !== "function") throw new ItdConfigError(`feature «${name}» должен предоставлять setup()`);
|
|
108
|
+
if (typeof feature.operations !== "object" || feature.operations === null || Array.isArray(feature.operations)) throw new ItdConfigError(`feature «${name}» должен объявлять operations`);
|
|
109
|
+
if (feature.services !== void 0 && !Array.isArray(feature.services)) throw new ItdConfigError(`feature «${name}»: services должен быть массивом`);
|
|
110
|
+
if (feature.buckets !== void 0 && (typeof feature.buckets !== "object" || feature.buckets === null || Array.isArray(feature.buckets))) throw new ItdConfigError(`feature «${name}»: buckets должен быть объектом`);
|
|
111
|
+
const rollback = [];
|
|
112
|
+
const services = /* @__PURE__ */ new Set();
|
|
113
|
+
const bucketNames = /* @__PURE__ */ new Map();
|
|
114
|
+
const operations = /* @__PURE__ */ new Map();
|
|
115
|
+
const controller = new AbortController();
|
|
116
|
+
let committed = false;
|
|
117
|
+
try {
|
|
118
|
+
for (const service of feature.services ?? []) {
|
|
119
|
+
const serviceName = requireName(service?.name, `Сервис feature «${name}»`);
|
|
120
|
+
const owner = this.#serviceOwners.get(serviceName);
|
|
121
|
+
if (owner !== void 0) throw new ItdConfigError(`feature «${name}»: сервис «${serviceName}» уже принадлежит feature «${owner}»`);
|
|
122
|
+
if (services.has(serviceName)) throw new ItdConfigError(`feature «${name}»: сервис «${serviceName}» объявлен повторно`);
|
|
123
|
+
if (!this.#deps.services.has(serviceName)) {
|
|
124
|
+
this.#deps.services.define(service);
|
|
125
|
+
rollback.push(() => void this.#deps.services.delete(serviceName));
|
|
126
|
+
} else {
|
|
127
|
+
const override = this.#deps.serviceOverrides.find((candidate) => candidate.name.trim() === serviceName);
|
|
128
|
+
if (override) {
|
|
129
|
+
const previous = this.#deps.services.replace(mergeService(service, override));
|
|
130
|
+
rollback.push(() => void this.#deps.services.replace(previous));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
this.#serviceOwners.set(serviceName, name);
|
|
134
|
+
rollback.push(() => {
|
|
135
|
+
if (this.#serviceOwners.get(serviceName) === name) this.#serviceOwners.delete(serviceName);
|
|
136
|
+
});
|
|
137
|
+
services.add(serviceName);
|
|
138
|
+
}
|
|
139
|
+
for (const [rawLocalName, bucket] of Object.entries(feature.buckets ?? {})) {
|
|
140
|
+
const localName = requireName(rawLocalName, `Бакет feature «${name}»`);
|
|
141
|
+
validateBucket(name, localName, bucket);
|
|
142
|
+
const globalName = `feature:${name}/${localName}`;
|
|
143
|
+
const unregisterCatalog = this.#deps.catalog.registerBucket(name, globalName, bucket);
|
|
144
|
+
rollback.push(unregisterCatalog);
|
|
145
|
+
const unregisterQueue = this.#deps.registerBucket(globalName, bucket);
|
|
146
|
+
if (unregisterQueue) rollback.push(unregisterQueue);
|
|
147
|
+
bucketNames.set(localName, globalName);
|
|
148
|
+
}
|
|
149
|
+
for (const [rawLocalName, definition] of Object.entries(feature.operations)) {
|
|
150
|
+
const localName = requireOperationName(rawLocalName, name);
|
|
151
|
+
if (typeof definition !== "object" || definition === null) throw new ItdConfigError(`feature «${name}»: операция «${localName}» должна быть объектом`);
|
|
152
|
+
if (![
|
|
153
|
+
"GET",
|
|
154
|
+
"POST",
|
|
155
|
+
"PUT",
|
|
156
|
+
"PATCH",
|
|
157
|
+
"DELETE"
|
|
158
|
+
].includes(definition.method)) throw new ItdConfigError(`feature «${name}»: операция «${localName}» содержит неизвестный HTTP-метод`);
|
|
159
|
+
if (!Object.values(RetrySafety).includes(definition.retrySafety)) throw new ItdConfigError(`feature «${name}»: операция «${localName}» содержит неизвестную retrySafety`);
|
|
160
|
+
if (definition.service !== void 0 && typeof definition.service !== "string") throw new ItdConfigError(`feature «${name}»: service операции «${localName}» должен быть строкой`);
|
|
161
|
+
if (definition.bucket !== void 0 && typeof definition.bucket !== "string") throw new ItdConfigError(`feature «${name}»: bucket операции «${localName}» должен быть строкой`);
|
|
162
|
+
if (definition.service !== void 0 && !services.has(definition.service)) throw new ItdConfigError(`feature «${name}»: операция «${localName}» ссылается на необъявленный сервис «${definition.service}»`);
|
|
163
|
+
const bucket = definition.bucket === void 0 ? this.#deps.catalog.defaultBucket : bucketNames.get(definition.bucket);
|
|
164
|
+
if (bucket === void 0) throw new ItdConfigError(`feature «${name}»: операция «${localName}» ссылается на необъявленный бакет «${definition.bucket}»`);
|
|
165
|
+
const operationId = `${name}.${localName}`;
|
|
166
|
+
const unregister = this.#deps.catalog.registerOperation(name, operationId, {
|
|
167
|
+
method: definition.method,
|
|
168
|
+
retrySafety: definition.retrySafety,
|
|
169
|
+
bucket
|
|
170
|
+
});
|
|
171
|
+
rollback.push(unregister);
|
|
172
|
+
operations.set(localName, {
|
|
173
|
+
operationId,
|
|
174
|
+
method: definition.method,
|
|
175
|
+
service: definition.service
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const context = Object.freeze({
|
|
179
|
+
featureName: name,
|
|
180
|
+
baseUrl: this.#deps.baseUrl,
|
|
181
|
+
signal: controller.signal,
|
|
182
|
+
clock: this.#deps.clock,
|
|
183
|
+
logger: this.#deps.logger,
|
|
184
|
+
request: (operation, options) => {
|
|
185
|
+
try {
|
|
186
|
+
this.#deps.assertActive(`выполнить операцию feature «${name}»`);
|
|
187
|
+
if (!committed || controller.signal.aborted) throw new ItdStateError(`feature «${name}» не активен`);
|
|
188
|
+
const resolved = operations.get(operation);
|
|
189
|
+
if (!resolved) throw new ItdConfigError(`feature «${name}» не объявляет операцию «${operation}»`);
|
|
190
|
+
const scoped = { ...options };
|
|
191
|
+
delete scoped.operationId;
|
|
192
|
+
delete scoped.method;
|
|
193
|
+
delete scoped.service;
|
|
194
|
+
delete scoped.baseUrl;
|
|
195
|
+
delete scoped.retrySafety;
|
|
196
|
+
delete scoped.rateLimitBucket;
|
|
197
|
+
return this.#deps.http.request({
|
|
198
|
+
...scoped,
|
|
199
|
+
operationId: resolved.operationId,
|
|
200
|
+
method: resolved.method,
|
|
201
|
+
...resolved.service === void 0 ? {} : { service: resolved.service }
|
|
202
|
+
});
|
|
203
|
+
} catch (error) {
|
|
204
|
+
return Promise.reject(error);
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
serviceBaseUrl: (serviceName) => {
|
|
208
|
+
if (!services.has(serviceName)) throw new ItdConfigError(`feature «${name}» не объявляет сервис «${serviceName}»`);
|
|
209
|
+
return this.#deps.services.resolveBaseUrl(serviceName);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
const installation = feature.setup(context);
|
|
213
|
+
if (typeof installation !== "object" || installation === null || !("api" in installation)) throw new ItdConfigError(`setup() feature «${name}» должен вернуть объект с полем api`);
|
|
214
|
+
if (installation.close !== void 0 && typeof installation.close !== "function") throw new ItdConfigError(`feature «${name}»: close должен быть функцией`);
|
|
215
|
+
if (installation.dispose !== void 0 && typeof installation.dispose !== "function") throw new ItdConfigError(`feature «${name}»: dispose должен быть функцией`);
|
|
216
|
+
this.#features.set(name, {
|
|
217
|
+
name,
|
|
218
|
+
controller,
|
|
219
|
+
close: installation.close,
|
|
220
|
+
dispose: installation.dispose,
|
|
221
|
+
cleanup: [...rollback]
|
|
222
|
+
});
|
|
223
|
+
committed = true;
|
|
224
|
+
return installation.api;
|
|
225
|
+
} catch (error) {
|
|
226
|
+
controller.abort(new ItdAbortError(`Установка feature «${name}» отменена`));
|
|
227
|
+
for (const undo of rollback.reverse()) undo();
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
names() {
|
|
232
|
+
return [...this.#features.keys()];
|
|
233
|
+
}
|
|
234
|
+
has(name) {
|
|
235
|
+
return this.#features.has(name);
|
|
236
|
+
}
|
|
237
|
+
async close() {
|
|
238
|
+
const errors = [];
|
|
239
|
+
for (const feature of [...this.#features.values()].reverse()) try {
|
|
240
|
+
await feature.close?.();
|
|
241
|
+
} catch (error) {
|
|
242
|
+
errors.push(error);
|
|
243
|
+
}
|
|
244
|
+
if (errors.length > 0) throw new AggregateError(errors, "Не удалось закрыть feature");
|
|
245
|
+
}
|
|
246
|
+
async dispose() {
|
|
247
|
+
if (this.#disposed) return;
|
|
248
|
+
this.#disposed = true;
|
|
249
|
+
const errors = [];
|
|
250
|
+
for (const feature of [...this.#features.values()].reverse()) {
|
|
251
|
+
feature.controller.abort(new ItdAbortError(`Feature «${feature.name}» освобождён`));
|
|
252
|
+
try {
|
|
253
|
+
await feature.dispose?.();
|
|
254
|
+
} catch (error) {
|
|
255
|
+
errors.push(error);
|
|
256
|
+
}
|
|
257
|
+
for (const cleanup of [...feature.cleanup].reverse()) try {
|
|
258
|
+
cleanup();
|
|
259
|
+
} catch (error) {
|
|
260
|
+
errors.push(error);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
this.#features.clear();
|
|
264
|
+
if (errors.length > 0) throw new AggregateError(errors, "Не удалось освободить feature");
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
//#endregion
|
|
5
268
|
//#region src/resources/pagination.ts
|
|
6
269
|
/** Схема пагинации эндпоинта. */
|
|
7
270
|
const PaginationMode = Object.freeze({
|
|
@@ -311,6 +574,84 @@ function withPageState(query, state) {
|
|
|
311
574
|
};
|
|
312
575
|
}
|
|
313
576
|
//#endregion
|
|
577
|
+
//#region src/domain/time.ts
|
|
578
|
+
const NAIVE_STAMP = /^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}(?:\.\d+)?)$/;
|
|
579
|
+
/**
|
|
580
|
+
* Приводит отметку времени без часового пояса к ISO-8601, считая её временем UTC.
|
|
581
|
+
*
|
|
582
|
+
* Строку другого вида возвращает нетронутой.
|
|
583
|
+
*
|
|
584
|
+
* @example
|
|
585
|
+
* ```ts
|
|
586
|
+
* utcStampToIso('2026-07-23 23:14:25'); // '2026-07-23T23:14:25Z'
|
|
587
|
+
* utcStampToIso('2026-07-23T23:14:25Z'); // без изменений
|
|
588
|
+
* ```
|
|
589
|
+
*/
|
|
590
|
+
function utcStampToIso(value) {
|
|
591
|
+
const match = typeof value === "string" ? NAIVE_STAMP.exec(value) : null;
|
|
592
|
+
if (!match) return value;
|
|
593
|
+
const iso = `${match[1]}T${match[2]}Z`;
|
|
594
|
+
return Number.isFinite(Date.parse(iso)) ? iso : value;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Разбирает дату API в объект `Date`.
|
|
598
|
+
*
|
|
599
|
+
* @returns `null`, если строки нет или она не разбирается
|
|
600
|
+
*
|
|
601
|
+
* @example
|
|
602
|
+
* ```ts
|
|
603
|
+
* const created = toDate(post.createdAt);
|
|
604
|
+
* ```
|
|
605
|
+
*/
|
|
606
|
+
function toDate(value) {
|
|
607
|
+
if (!value) return null;
|
|
608
|
+
const date = new Date(value);
|
|
609
|
+
return Number.isFinite(date.getTime()) ? date : null;
|
|
610
|
+
}
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region src/resources/status.ts
|
|
613
|
+
/** Приводит `last_checked` каждого сервиса к ISO. Остальное остаётся как прислал сервер. */
|
|
614
|
+
function normalizeStatus(body) {
|
|
615
|
+
if (!isRecord(body) || !Array.isArray(body.services)) return body;
|
|
616
|
+
return {
|
|
617
|
+
...body,
|
|
618
|
+
services: body.services.map((service) => typeof service?.last_checked === "string" ? {
|
|
619
|
+
...service,
|
|
620
|
+
last_checked: utcStampToIso(service.last_checked)
|
|
621
|
+
} : service)
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
/** API встроенного status feature. @internal */
|
|
625
|
+
var StatusResource = class {
|
|
626
|
+
#context;
|
|
627
|
+
constructor(context) {
|
|
628
|
+
this.#context = context;
|
|
629
|
+
}
|
|
630
|
+
async get(options = {}) {
|
|
631
|
+
return normalizeStatus(await this.#context.request("get", {
|
|
632
|
+
path: "/api/status",
|
|
633
|
+
...options
|
|
634
|
+
}));
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
/** Встроенный модуль страницы состояния платформы. @internal */
|
|
638
|
+
function createStatusFeature() {
|
|
639
|
+
return {
|
|
640
|
+
name: "status",
|
|
641
|
+
services: [{
|
|
642
|
+
name: STATUS_SERVICE,
|
|
643
|
+
baseUrl: DEFAULT_STATUS_BASE_URL,
|
|
644
|
+
auth: false
|
|
645
|
+
}],
|
|
646
|
+
operations: { get: {
|
|
647
|
+
method: "GET",
|
|
648
|
+
retrySafety: RetrySafety.Safe,
|
|
649
|
+
service: STATUS_SERVICE
|
|
650
|
+
} },
|
|
651
|
+
setup: (context) => ({ api: new StatusResource(context) })
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
//#endregion
|
|
314
655
|
//#region src/builders/base.ts
|
|
315
656
|
/** Метка билдера. Через `Symbol.for` — чтобы распознавание переживало смешивание ESM и CJS. */
|
|
316
657
|
const BUILDER = Symbol.for("itd.builder");
|
|
@@ -1240,59 +1581,19 @@ var NotificationsResource = class extends BaseResource {
|
|
|
1240
1581
|
}
|
|
1241
1582
|
};
|
|
1242
1583
|
//#endregion
|
|
1243
|
-
//#region src/domain/time.ts
|
|
1244
|
-
const NAIVE_STAMP = /^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}(?:\.\d+)?)$/;
|
|
1245
|
-
/**
|
|
1246
|
-
* Приводит отметку времени без часового пояса к ISO-8601, считая её временем UTC.
|
|
1247
|
-
*
|
|
1248
|
-
* Строку другого вида возвращает нетронутой.
|
|
1249
|
-
*
|
|
1250
|
-
* @example
|
|
1251
|
-
* ```ts
|
|
1252
|
-
* utcStampToIso('2026-07-23 23:14:25'); // '2026-07-23T23:14:25Z'
|
|
1253
|
-
* utcStampToIso('2026-07-23T23:14:25Z'); // без изменений
|
|
1254
|
-
* ```
|
|
1255
|
-
*/
|
|
1256
|
-
function utcStampToIso(value) {
|
|
1257
|
-
const match = typeof value === "string" ? NAIVE_STAMP.exec(value) : null;
|
|
1258
|
-
if (!match) return value;
|
|
1259
|
-
const iso = `${match[1]}T${match[2]}Z`;
|
|
1260
|
-
return Number.isFinite(Date.parse(iso)) ? iso : value;
|
|
1261
|
-
}
|
|
1262
|
-
/**
|
|
1263
|
-
* Разбирает дату API в объект `Date`.
|
|
1264
|
-
*
|
|
1265
|
-
* @returns `null`, если строки нет или она не разбирается
|
|
1266
|
-
*
|
|
1267
|
-
* @example
|
|
1268
|
-
* ```ts
|
|
1269
|
-
* const created = toDate(post.createdAt);
|
|
1270
|
-
* ```
|
|
1271
|
-
*/
|
|
1272
|
-
function toDate(value) {
|
|
1273
|
-
if (!value) return null;
|
|
1274
|
-
const date = new Date(value);
|
|
1275
|
-
return Number.isFinite(date.getTime()) ? date : null;
|
|
1276
|
-
}
|
|
1277
|
-
//#endregion
|
|
1278
1584
|
//#region src/resources/platform.ts
|
|
1279
|
-
/** Приводит `last_checked` каждого сервиса к ISO. Остальное остаётся как прислал сервер. */
|
|
1280
|
-
function normalizeStatus(body) {
|
|
1281
|
-
if (!isRecord(body) || !Array.isArray(body.services)) return body;
|
|
1282
|
-
return {
|
|
1283
|
-
...body,
|
|
1284
|
-
services: body.services.map((service) => typeof service?.last_checked === "string" ? {
|
|
1285
|
-
...service,
|
|
1286
|
-
last_checked: utcStampToIso(service.last_checked)
|
|
1287
|
-
} : service)
|
|
1288
|
-
};
|
|
1289
|
-
}
|
|
1290
1585
|
/**
|
|
1291
1586
|
* Сведения о платформе: версии приложений, изменения, анонсы, баннер события.
|
|
1292
1587
|
*
|
|
1293
1588
|
* Доступна как `itd.platform`.
|
|
1294
1589
|
*/
|
|
1295
1590
|
var PlatformResource = class extends BaseResource {
|
|
1591
|
+
#status;
|
|
1592
|
+
/** @internal */
|
|
1593
|
+
constructor(http, status) {
|
|
1594
|
+
super(http);
|
|
1595
|
+
this.#status = status;
|
|
1596
|
+
}
|
|
1296
1597
|
/**
|
|
1297
1598
|
* Загружает минимальные и актуальные версии клиентских приложений.
|
|
1298
1599
|
*
|
|
@@ -1351,11 +1652,7 @@ var PlatformResource = class extends BaseResource {
|
|
|
1351
1652
|
* ```
|
|
1352
1653
|
*/
|
|
1353
1654
|
async status(options = {}) {
|
|
1354
|
-
return
|
|
1355
|
-
service: STATUS_SERVICE,
|
|
1356
|
-
path: "/api/status",
|
|
1357
|
-
...options
|
|
1358
|
-
}));
|
|
1655
|
+
return this.#status.get(options);
|
|
1359
1656
|
}
|
|
1360
1657
|
};
|
|
1361
1658
|
//#endregion
|
|
@@ -3684,7 +3981,7 @@ function createResources(deps) {
|
|
|
3684
3981
|
return subscription;
|
|
3685
3982
|
},
|
|
3686
3983
|
get platform() {
|
|
3687
|
-
platform ??= new PlatformResource(deps.http);
|
|
3984
|
+
platform ??= new PlatformResource(deps.http, deps.status);
|
|
3688
3985
|
return platform;
|
|
3689
3986
|
},
|
|
3690
3987
|
get telemetry() {
|
|
@@ -3987,6 +4284,6 @@ function renderSpans(content, spans = [], options = {}) {
|
|
|
3987
4284
|
return result;
|
|
3988
4285
|
}
|
|
3989
4286
|
//#endregion
|
|
3990
|
-
export {
|
|
4287
|
+
export { utcStampToIso as C, mapPage as D, Paginator as E, FeatureRegistry as O, toDate as S, PaginationMode as T, IMAGE_MIME_TYPES as _, fromStream as a, isBuilder as b, report as c, autoSpans as d, markup as f, AUDIO_MIME_TYPES as g, ALLOWED_MIME_TYPES as h, isMyProfile as i, ExtensibleOperationCatalog as k, post as l, parseMarkdown as m, renderSpans as n, fromUrl as o, parseHtml as p, statusDays as r, createResources as s, SpanRenderFormat as t, poll as u, VIDEO_MIME_TYPES as v, BaseResource as w, createStatusFeature as x, comment as y };
|
|
3991
4288
|
|
|
3992
|
-
//# sourceMappingURL=render-
|
|
4289
|
+
//# sourceMappingURL=render-C6HRPs10.js.map
|