@torrow/sdk-client 0.7.0 → 0.9.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/dist/index.js CHANGED
@@ -11,103 +11,167 @@ async function buildAuthHeaders(config) {
11
11
  return headers;
12
12
  }
13
13
 
14
- // ../../src/http/TorrowHttpError.ts
15
- function pickApiErrorName(body) {
16
- if (body === null || typeof body !== "object") {
17
- return void 0;
18
- }
19
- const o = body;
20
- if (typeof o.error === "string" && o.error.length > 0) {
21
- return o.error;
22
- }
23
- if (typeof o.name === "string" && o.name.length > 0) {
24
- return o.name;
14
+ // ../../src/http/TorrowSdkError.ts
15
+ var TorrowSdkErrorCategory = /* @__PURE__ */ ((TorrowSdkErrorCategory2) => {
16
+ TorrowSdkErrorCategory2["InvalidInput"] = "InvalidInput";
17
+ TorrowSdkErrorCategory2["InvalidState"] = "InvalidState";
18
+ TorrowSdkErrorCategory2["AccessDenied"] = "AccessDenied";
19
+ TorrowSdkErrorCategory2["InsufficientData"] = "InsufficientData";
20
+ TorrowSdkErrorCategory2["InvalidResponse"] = "InvalidResponse";
21
+ TorrowSdkErrorCategory2["VersionConflict"] = "VersionConflict";
22
+ TorrowSdkErrorCategory2["AuthenticationRequired"] = "AuthenticationRequired";
23
+ TorrowSdkErrorCategory2["UnsupportedOperation"] = "UnsupportedOperation";
24
+ TorrowSdkErrorCategory2["RateLimited"] = "RateLimited";
25
+ TorrowSdkErrorCategory2["HttpFailure"] = "HttpFailure";
26
+ TorrowSdkErrorCategory2["TransportFailure"] = "TransportFailure";
27
+ TorrowSdkErrorCategory2["StorageFailure"] = "StorageFailure";
28
+ TorrowSdkErrorCategory2["Cancelled"] = "Cancelled";
29
+ TorrowSdkErrorCategory2["Unknown"] = "Unknown";
30
+ return TorrowSdkErrorCategory2;
31
+ })(TorrowSdkErrorCategory || {});
32
+ var TorrowSdkRequestState = /* @__PURE__ */ ((TorrowSdkRequestState2) => {
33
+ TorrowSdkRequestState2["NotStarted"] = "NotStarted";
34
+ TorrowSdkRequestState2["Started"] = "Started";
35
+ return TorrowSdkRequestState2;
36
+ })(TorrowSdkRequestState || {});
37
+ var TorrowSdkEffectArea = /* @__PURE__ */ ((TorrowSdkEffectArea2) => {
38
+ TorrowSdkEffectArea2["Remote"] = "Remote";
39
+ TorrowSdkEffectArea2["SessionMemory"] = "SessionMemory";
40
+ TorrowSdkEffectArea2["Storage"] = "Storage";
41
+ return TorrowSdkEffectArea2;
42
+ })(TorrowSdkEffectArea || {});
43
+ var TorrowSdkEffectState = /* @__PURE__ */ ((TorrowSdkEffectState2) => {
44
+ TorrowSdkEffectState2["NotApplied"] = "NotApplied";
45
+ TorrowSdkEffectState2["Applied"] = "Applied";
46
+ TorrowSdkEffectState2["Unknown"] = "Unknown";
47
+ return TorrowSdkEffectState2;
48
+ })(TorrowSdkEffectState || {});
49
+ var TorrowSdkErrorDomain = /* @__PURE__ */ ((TorrowSdkErrorDomain2) => {
50
+ TorrowSdkErrorDomain2["BehaviorSettings"] = "BehaviorSettings";
51
+ TorrowSdkErrorDomain2["Otp"] = "Otp";
52
+ TorrowSdkErrorDomain2["AuthSession"] = "AuthSession";
53
+ return TorrowSdkErrorDomain2;
54
+ })(TorrowSdkErrorDomain || {});
55
+ var OtpErrorReason = /* @__PURE__ */ ((OtpErrorReason2) => {
56
+ OtpErrorReason2["InvalidChallenge"] = "InvalidChallenge";
57
+ OtpErrorReason2["UnsupportedMethod"] = "UnsupportedMethod";
58
+ OtpErrorReason2["AuthHeaderMissing"] = "AuthHeaderMissing";
59
+ OtpErrorReason2["CodeRequestLimitExceeded"] = "CodeRequestLimitExceeded";
60
+ return OtpErrorReason2;
61
+ })(OtpErrorReason || {});
62
+ var AuthSessionErrorReason = /* @__PURE__ */ ((AuthSessionErrorReason2) => {
63
+ AuthSessionErrorReason2["SessionNotReady"] = "SessionNotReady";
64
+ AuthSessionErrorReason2["CallbackFailed"] = "CallbackFailed";
65
+ return AuthSessionErrorReason2;
66
+ })(AuthSessionErrorReason || {});
67
+ var BehaviorSettingsEffectStep = /* @__PURE__ */ ((BehaviorSettingsEffectStep2) => {
68
+ BehaviorSettingsEffectStep2["Write"] = "Write";
69
+ return BehaviorSettingsEffectStep2;
70
+ })(BehaviorSettingsEffectStep || {});
71
+ var AuthSessionEffectStep = /* @__PURE__ */ ((AuthSessionEffectStep2) => {
72
+ AuthSessionEffectStep2["CreateAuthSession"] = "CreateAuthSession";
73
+ AuthSessionEffectStep2["CloseSession"] = "CloseSession";
74
+ AuthSessionEffectStep2["IssueTokens"] = "IssueTokens";
75
+ AuthSessionEffectStep2["RefreshTokens"] = "RefreshTokens";
76
+ AuthSessionEffectStep2["RevokeTokens"] = "RevokeTokens";
77
+ AuthSessionEffectStep2["SendCode"] = "SendCode";
78
+ AuthSessionEffectStep2["SetTokens"] = "SetTokens";
79
+ AuthSessionEffectStep2["ClearTokens"] = "ClearTokens";
80
+ AuthSessionEffectStep2["SaveTokens"] = "SaveTokens";
81
+ AuthSessionEffectStep2["ClearStorage"] = "ClearStorage";
82
+ return AuthSessionEffectStep2;
83
+ })(AuthSessionEffectStep || {});
84
+ var TorrowSdkError = class _TorrowSdkError extends Error {
85
+ /** Общая причина отказа. */
86
+ category;
87
+ /** Безопасные метаданные ответа. */
88
+ http;
89
+ /** Неизменяемое предметное уточнение. */
90
+ detail;
91
+ /** Начало fetch; не доставка. */
92
+ request;
93
+ /** Имя составной операции без пользовательских значений. */
94
+ operation;
95
+ /** Стадия отказа без аргументов. */
96
+ failureAt;
97
+ /** Неизменяемые сведения об описанных изменениях. */
98
+ effects;
99
+ #sensitive;
100
+ constructor(init) {
101
+ super(`Torrow SDK: ${init.category}`);
102
+ this.name = "TorrowSdkError";
103
+ this.category = init.category;
104
+ this.http = init.http && Object.freeze({ ...init.http });
105
+ this.detail = init.detail && Object.freeze({ ...init.detail });
106
+ this.request = init.request;
107
+ this.operation = init.operation;
108
+ this.failureAt = init.failureAt;
109
+ this.effects = init.effects && Object.freeze(init.effects.map((effect) => Object.freeze({ ...effect })));
110
+ this.#sensitive = Object.freeze({ ...init.sensitive, cleanup: init.sensitive?.cleanup && Object.freeze({ ...init.sensitive.cleanup }) });
111
+ Object.freeze(this);
112
+ }
113
+ /** Безопасный снимок для обычной сериализации и диагностики. */
114
+ toJSON() {
115
+ return {
116
+ category: this.category,
117
+ http: this.http,
118
+ detail: this.detail,
119
+ request: this.request,
120
+ operation: this.operation,
121
+ failureAt: this.failureAt,
122
+ effects: this.effects
123
+ };
25
124
  }
26
- if (typeof o.title === "string" && o.title.length > 0) {
27
- return o.title;
125
+ /** Явный доступ к секретной диагностике; вызывающий код отвечает за её защиту. */
126
+ getSensitiveDetails() {
127
+ return this.#sensitive;
28
128
  }
29
- if (typeof o.message === "string" && o.message.length > 0) {
30
- return o.message;
129
+ /** Новый снимок для составной операции; исходная ошибка не изменяется. */
130
+ withContext(context) {
131
+ return new _TorrowSdkError({ ...this.toJSON(), ...context, sensitive: this.#sensitive });
31
132
  }
32
- return void 0;
133
+ };
134
+
135
+ // ../../src/http/transport-errors.ts
136
+ var KnownApiErrorName = /* @__PURE__ */ ((KnownApiErrorName2) => {
137
+ KnownApiErrorName2["ResourceUnavailable"] = "REQUESTED_RESOURCE_IS_NOT_AVAILABLE";
138
+ KnownApiErrorName2["QuantityUnavailable"] = "REQUESTED_RESOURCE_QUANTITY_IS_NOT_AVAILABLE";
139
+ KnownApiErrorName2["ServiceUnavailableAtTime"] = "SERVICE_IS_NOT_AVAILABLE_AT_TIME";
140
+ KnownApiErrorName2["WrongVersion"] = "OBJECT_HAS_WRONG_VERSION";
141
+ KnownApiErrorName2["CodeRequestLimit"] = "CODE_REQUEST_LIMIT_EXCEEDED";
142
+ KnownApiErrorName2["AccessDenied"] = "ACCESS_DENIED";
143
+ KnownApiErrorName2["UserNoRights"] = "USER_HAVE_NO_RIGHTS_TO_ELEMENTS";
144
+ KnownApiErrorName2["AuthHeaderMissing"] = "AUTH_HEADER_MISSING";
145
+ KnownApiErrorName2["WrongSessionCode"] = "WRONG_SESSION_ACCESS_CODE";
146
+ KnownApiErrorName2["AttemptsExceeded"] = "EXCEEDED_ATTEMPTS_COUNT";
147
+ return KnownApiErrorName2;
148
+ })(KnownApiErrorName || {});
149
+ function createHttpError(init) {
150
+ const body = record(init.body);
151
+ const code = body?.code;
152
+ const apiCode = typeof code === "string" ? code : typeof code === "number" && Number.isFinite(code) ? String(code) : void 0;
153
+ const apiName = [body?.error, body?.name].find((value) => typeof value === "string" && Object.values(KnownApiErrorName).includes(value));
154
+ const nested = record(body?.torrowError) ?? record(body?.torrowErrorClient) ?? record(body?.innerError);
155
+ const trace = body?.traceIdentifier ?? nested?.traceIdentifier;
156
+ const http = {
157
+ status: init.status,
158
+ apiCode,
159
+ apiErrorName: typeof apiName === "string" ? apiName : void 0,
160
+ requestId: init.requestId,
161
+ traceIdentifier: init.traceIdentifier || (typeof trace === "string" ? trace : void 0)
162
+ };
163
+ const category = init.status === 401 ? "AuthenticationRequired" /* AuthenticationRequired */ : init.status === 403 ? "AccessDenied" /* AccessDenied */ : init.status === 429 ? "RateLimited" /* RateLimited */ : "HttpFailure" /* HttpFailure */;
164
+ return new TorrowSdkError({ category, http, request: "Started" /* Started */, sensitive: init });
33
165
  }
34
- function pickApiCode(body) {
35
- if (body === null || typeof body !== "object") {
36
- return void 0;
37
- }
38
- const c = body.code;
39
- if (typeof c === "string") {
40
- return c;
41
- }
42
- return typeof c === "number" && Number.isFinite(c) ? String(c) : void 0;
166
+ function record(value) {
167
+ return value !== null && typeof value === "object" ? value : void 0;
43
168
  }
44
- function pickTraceIdentifier(body) {
45
- if (body === null || typeof body !== "object") {
46
- return void 0;
47
- }
48
- const o = body;
49
- if (typeof o.traceIdentifier === "string" && o.traceIdentifier.length > 0) {
50
- return o.traceIdentifier;
51
- }
52
- for (const key of ["torrowError", "torrowErrorClient", "innerError"]) {
53
- const nested = o[key];
54
- if (nested !== null && typeof nested === "object") {
55
- const t = nested.traceIdentifier;
56
- if (typeof t === "string" && t.length > 0) {
57
- return t;
58
- }
59
- }
60
- }
61
- return void 0;
169
+ function normalizeTransportError(cause, request, signal, url, parsing = false) {
170
+ if (cause instanceof TorrowSdkError) return cause;
171
+ const cancelled = signal?.aborted || record(cause)?.name === "AbortError";
172
+ const category = cancelled ? "Cancelled" /* Cancelled */ : parsing ? "InvalidResponse" /* InvalidResponse */ : request === "Started" /* Started */ && cause instanceof TypeError ? "TransportFailure" /* TransportFailure */ : "Unknown" /* Unknown */;
173
+ return new TorrowSdkError({ category, request, sensitive: { cause, url } });
62
174
  }
63
- var TorrowHttpError = class _TorrowHttpError extends Error {
64
- status;
65
- body;
66
- apiErrorName;
67
- /** Код ошибки API строкой: числовой `TorrowErrorClient.code` приводится к строке (`"311293"`). */
68
- code;
69
- requestId;
70
- /**
71
- * Идентификатор трассировки шлюза (`TorrowErrorClient.traceIdentifier`
72
- * или заголовок `trace-identifier`) — для саппорта/логов.
73
- */
74
- traceIdentifier;
75
- url;
76
- constructor(init) {
77
- const apiName = pickApiErrorName(init.body);
78
- const code = pickApiCode(init.body);
79
- const traceIdentifier = (typeof init.traceIdentifier === "string" && init.traceIdentifier.length > 0 ? init.traceIdentifier : void 0) ?? pickTraceIdentifier(init.body);
80
- const message = _TorrowHttpError.buildMessage(
81
- init.status,
82
- init.url,
83
- apiName,
84
- code,
85
- traceIdentifier
86
- );
87
- super(message, { cause: init.cause });
88
- this.name = "TorrowHttpError";
89
- this.status = init.status;
90
- this.body = init.body;
91
- this.url = init.url;
92
- this.requestId = init.requestId;
93
- this.traceIdentifier = traceIdentifier;
94
- this.apiErrorName = apiName;
95
- this.code = code;
96
- }
97
- static buildMessage(status, url, apiErrorName, code, traceIdentifier) {
98
- const parts = [`HTTP ${status}`, url];
99
- if (code) {
100
- parts.push(`code=${code}`);
101
- }
102
- if (apiErrorName) {
103
- parts.push(apiErrorName);
104
- }
105
- if (traceIdentifier) {
106
- parts.push(`trace-identifier=${traceIdentifier}`);
107
- }
108
- return parts.join(" \u2014 ");
109
- }
110
- };
111
175
 
112
176
  // ../../src/util/json.ts
113
177
  var ISO_DATE_REGEX = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)(Z|([+-])(\d{2}):(\d{2}))$/;
@@ -679,46 +743,59 @@ var HttpClient = class {
679
743
  this.config = config;
680
744
  }
681
745
  config;
682
- async requestJson(init) {
683
- const { fetch: fetchFn = globalThis.fetch } = this.config;
684
- const url = this.buildUrl(init.path, init.query);
685
- const authHeaders = await buildAuthHeaders(this.config);
686
- const headers = {
687
- Accept: JSON_MIME,
688
- ...authHeaders,
689
- ...this.config.defaultHeaders,
690
- ...init.headers
691
- };
692
- if (this.config.userAgent) {
693
- headers["User-Agent"] = this.config.userAgent;
694
- }
695
- const needsBody = init.body !== void 0 && init.method !== "GET" && init.method !== "DELETE";
696
- if (needsBody) {
697
- headers["Content-Type"] = JSON_MIME;
698
- }
699
- const res = await fetchFn(url, {
700
- method: init.method,
701
- headers,
702
- body: needsBody ? stringifyJsonBody(init.body) : void 0,
703
- signal: init.signal
704
- });
705
- const requestId = res.headers.get("x-request-id") ?? void 0;
706
- const traceIdentifier = res.headers.get("trace-identifier") ?? void 0;
707
- const text = await res.text();
708
- if (!res.ok) {
709
- const errBody = tryParseErrorBody(text, res.headers.get("content-type"));
710
- throw new TorrowHttpError({
711
- status: res.status,
712
- body: errBody,
713
- url,
714
- requestId,
715
- traceIdentifier
716
- });
717
- }
718
- if (text === "") {
719
- return void 0;
746
+ /** Выполняет запрос; сетевые и подготовительные отказы возвращает как TorrowSdkError. */
747
+ async requestJson(init, onDispatch) {
748
+ let request = "NotStarted" /* NotStarted */;
749
+ let url;
750
+ let parsing = false;
751
+ try {
752
+ const { fetch: fetchFn = globalThis.fetch } = this.config;
753
+ url = this.buildUrl(init.path, init.query);
754
+ const authHeaders = await buildAuthHeaders(this.config);
755
+ const headers = {
756
+ Accept: JSON_MIME,
757
+ ...authHeaders,
758
+ ...this.config.defaultHeaders,
759
+ ...init.headers
760
+ };
761
+ if (this.config.userAgent) {
762
+ headers["User-Agent"] = this.config.userAgent;
763
+ }
764
+ const needsBody = init.body !== void 0 && init.method !== "GET" && init.method !== "DELETE";
765
+ if (needsBody) {
766
+ headers["Content-Type"] = JSON_MIME;
767
+ }
768
+ const requestInit = {
769
+ method: init.method,
770
+ headers,
771
+ body: needsBody ? stringifyJsonBody(init.body) : void 0,
772
+ signal: init.signal
773
+ };
774
+ init.signal?.throwIfAborted();
775
+ onDispatch?.();
776
+ request = "Started" /* Started */;
777
+ const res = await fetchFn(url, requestInit);
778
+ const requestId = res.headers.get("x-request-id") ?? void 0;
779
+ const traceIdentifier = res.headers.get("trace-identifier") ?? void 0;
780
+ const text = await res.text();
781
+ if (!res.ok) {
782
+ const errBody = tryParseErrorBody(text, res.headers.get("content-type"));
783
+ throw createHttpError({
784
+ status: res.status,
785
+ body: errBody,
786
+ url,
787
+ requestId,
788
+ traceIdentifier
789
+ });
790
+ }
791
+ if (text === "") {
792
+ return void 0;
793
+ }
794
+ parsing = true;
795
+ return parseOperationResponse(text, init.method, init.path, res.status);
796
+ } catch (cause) {
797
+ throw normalizeTransportError(cause, request, init.signal, url, parsing);
720
798
  }
721
- return parseOperationResponse(text, init.method, init.path, res.status);
722
799
  }
723
800
  buildUrl(path, query) {
724
801
  const origin = this.config.baseUrl ?? DEFAULT_TORROW_API_BASE_URL;
@@ -1210,7 +1287,8 @@ var TorrowItemCoreFacade = class {
1210
1287
  }
1211
1288
  /**
1212
1289
  * `PUT /api/v1/{domain}/{torrowId}/participation/requestaccess` — запрос доступа (не-владелец).
1213
- * Body: **`profileId`**. State → **`Requested`**; владельцу announcement **`RequestAccessToItem`**.
1290
+ * Body: **`profileId`**. Новый участник → **`Requested`**; некоторые существующие состояния → **`Accepted`**.
1291
+ * Проверять возвращённый participantState; после Accepted заново прочитать элемент и проверить полноту содержимого.
1214
1292
  * Владелец своего item вызвать не может. Дальше: {@link grantAccess} / {@link declineAccess} / {@link revokeAccessRequest}.
1215
1293
  */
1216
1294
  requestAccess(torrowId, profileId, options) {
@@ -3075,6 +3153,28 @@ var RelationsFacade = class {
3075
3153
  }
3076
3154
  };
3077
3155
 
3156
+ // ../../src/resources/common/behavior-settings-errors.ts
3157
+ function behaviorSettingsError(category) {
3158
+ return new TorrowSdkError({ category, detail: { domain: "BehaviorSettings" /* BehaviorSettings */ } });
3159
+ }
3160
+ function contextualizeBehaviorError(cause, operation, failureAt, state) {
3161
+ const error = cause instanceof TorrowSdkError ? cause : new TorrowSdkError({
3162
+ category: "Unknown" /* Unknown */,
3163
+ sensitive: { cause }
3164
+ });
3165
+ return error.withContext({
3166
+ operation,
3167
+ failureAt,
3168
+ detail: error.detail ?? { domain: "BehaviorSettings" /* BehaviorSettings */ },
3169
+ effects: state === void 0 ? void 0 : [{ area: "Remote" /* Remote */, step: "Write" /* Write */, state }]
3170
+ });
3171
+ }
3172
+ function isBehaviorCasRefusal(cause) {
3173
+ if (!(cause instanceof TorrowSdkError) || cause.request !== "Started" /* Started */ || cause.http?.status !== 500) return false;
3174
+ const body = cause.getSensitiveDetails().body;
3175
+ return body !== null && typeof body === "object" && "name" in body && body.name === "OBJECT_HAS_WRONG_VERSION" /* VersionConflict */ && "code" in body && (body.code === 208213 /* VersionConflict */ || body.code === String(208213 /* VersionConflict */));
3176
+ }
3177
+
3078
3178
  // ../../src/resources/common/implicit-tag-params.ts
3079
3179
  var SERVICE_IMPLICIT_TAG_SUFFIX = {
3080
3180
  orderNameLegacy: ":ordername",
@@ -3092,7 +3192,6 @@ var SERVICE_IMPLICIT_TAG_SUFFIX = {
3092
3192
  autoStartTelegramNotificationBot: "AutoStartTelegramNotificationBot",
3093
3193
  autoStartTelegramNotificationBotTypo: "AutoStartTelegramNotifcationBot",
3094
3194
  autoStartConnectNotifications: "AutoStartConnectNotifications",
3095
- hideMessengerConnectUiLegacy: "HideTelegramNotificationBot",
3096
3195
  hideMessengerConnectUi: "HideMessengerNotifications",
3097
3196
  /**
3098
3197
  * Резерв под серверную договоренность. Формат в `tags`: `NNN:payment_redirect_timeout_sec`.
@@ -3100,6 +3199,7 @@ var SERVICE_IMPLICIT_TAG_SUFFIX = {
3100
3199
  */
3101
3200
  paymentRedirectTimeoutSec: ":payment_redirect_timeout_sec"
3102
3201
  };
3202
+ var OBSOLETE_MESSENGER_CONNECT_UI_TAG = "HideTelegramNotificationBot";
3103
3203
  var SERVICE_IMPLICIT_TAG_REGISTRY = {
3104
3204
  orderName: {
3105
3205
  field: "orderName",
@@ -3145,13 +3245,8 @@ var SERVICE_IMPLICIT_TAG_REGISTRY = {
3145
3245
  },
3146
3246
  hideMessengerConnectUi: {
3147
3247
  field: "hideMessengerConnectUi",
3148
- kind: "containsFlag",
3149
- tokens: [SERVICE_IMPLICIT_TAG_SUFFIX.hideMessengerConnectUi, SERVICE_IMPLICIT_TAG_SUFFIX.hideMessengerConnectUiLegacy]
3150
- },
3151
- hideTelegramNotificationBot: {
3152
- field: "hideTelegramNotificationBot",
3153
- kind: "containsFlag",
3154
- tokens: [SERVICE_IMPLICIT_TAG_SUFFIX.hideMessengerConnectUiLegacy]
3248
+ kind: "exactFlag",
3249
+ tokens: [SERVICE_IMPLICIT_TAG_SUFFIX.hideMessengerConnectUi]
3155
3250
  },
3156
3251
  autoStartNotificationDelaySeconds: {
3157
3252
  field: "autoStartNotificationDelaySeconds",
@@ -3184,8 +3279,8 @@ function splitBySuffix(tag, suffix, caseInsensitive = false) {
3184
3279
  }
3185
3280
  function firstPrefixBySuffix(tags, suffix, options) {
3186
3281
  for (const tag of tags) {
3187
- const prefix = splitBySuffix(tag, suffix, options?.caseInsensitive ?? false);
3188
- if (prefix !== void 0) return prefix;
3282
+ const prefix2 = splitBySuffix(tag, suffix, options?.caseInsensitive ?? false);
3283
+ if (prefix2 !== void 0) return prefix2;
3189
3284
  }
3190
3285
  return void 0;
3191
3286
  }
@@ -3239,8 +3334,8 @@ function parseServiceImplicitTagParams(tags) {
3239
3334
  if (value !== void 0) mutable[config.field] = value;
3240
3335
  }
3241
3336
  }
3242
- if (config.kind === "containsFlag") {
3243
- const hasAny = safeTags.some((tag) => config.tokens.some((token) => tag.includes(token)));
3337
+ if (config.kind === "exactFlag") {
3338
+ const hasAny = safeTags.some((tag) => config.tokens.some((token) => tag === token));
3244
3339
  if (hasAny) mutable[config.field] = true;
3245
3340
  }
3246
3341
  }
@@ -3315,9 +3410,6 @@ function buildServiceImplicitTags(params, options) {
3315
3410
  if (params.hideMessengerConnectUi) {
3316
3411
  tags.push(SERVICE_IMPLICIT_TAG_REGISTRY.hideMessengerConnectUi.tokens[0]);
3317
3412
  }
3318
- if (params.hideTelegramNotificationBot) {
3319
- tags.push(SERVICE_IMPLICIT_TAG_REGISTRY.hideTelegramNotificationBot.tokens[0]);
3320
- }
3321
3413
  return tags;
3322
3414
  }
3323
3415
  function isImplicitServiceTag(tag) {
@@ -3328,8 +3420,8 @@ function isImplicitServiceTag(tag) {
3328
3420
  );
3329
3421
  if (matched) return true;
3330
3422
  }
3331
- if (config.kind === "containsFlag") {
3332
- if (config.tokens.some((token) => tag.includes(token))) return true;
3423
+ if (config.kind === "exactFlag") {
3424
+ if (config.tokens.some((token) => tag === token)) return true;
3333
3425
  }
3334
3426
  if (config.kind === "autoStart") {
3335
3427
  const isAutoStart = config.suffixes.some((suffix) => tag.match(new RegExp(`^\\d+:${suffix}$`)) !== null);
@@ -3345,15 +3437,17 @@ function isImplicitServiceTag(tag) {
3345
3437
  }
3346
3438
  function mergeServiceImplicitTags(baseTags, params, options) {
3347
3439
  const safeBase = baseTags ?? [];
3348
- const preservedNonImplicit = safeBase.filter((tag) => !isImplicitServiceTag(tag));
3440
+ const preservedNonImplicit = safeBase.filter(
3441
+ (tag) => tag !== OBSOLETE_MESSENGER_CONNECT_UI_TAG && !isImplicitServiceTag(tag)
3442
+ );
3349
3443
  const nextImplicit = buildServiceImplicitTags(params, options);
3350
3444
  return [...preservedNonImplicit, ...nextImplicit];
3351
3445
  }
3352
3446
  function parseResourceBehaviorSettings(tags) {
3353
- const parsed = parseServiceImplicitTagParams(tags);
3447
+ const marginTimeSec = toPositiveInt(firstPrefixBySuffix(tags ?? [], SERVICE_IMPLICIT_TAG_SUFFIX.marginTime));
3354
3448
  const lockId = toPositiveInt(firstPrefixBySuffix(tags ?? [], SERVICE_IMPLICIT_TAG_SUFFIX.lockId));
3355
3449
  const result = {};
3356
- if (parsed.marginTimeSec !== void 0) result.marginTimeSec = parsed.marginTimeSec;
3450
+ if (marginTimeSec !== void 0) result.marginTimeSec = marginTimeSec;
3357
3451
  if (lockId !== void 0) result.lockId = lockId;
3358
3452
  return result;
3359
3453
  }
@@ -3488,39 +3582,77 @@ var ResourcesFacade = class extends TorrowItemFacade {
3488
3582
  return this.getBehaviorSettingsFromResource(resource);
3489
3583
  }
3490
3584
  async updateBehaviorSettings(resourceId, settingsPatch, options) {
3491
- const resource = await this.getById(resourceId, {
3492
- includeDeleted: options?.includeDeleted,
3493
- isNeedItem: options?.isNeedItem,
3494
- isNeedParentResource: options?.isNeedParentResource,
3495
- includeChildTypes: options?.includeChildTypes,
3496
- securityToken: options?.securityToken,
3497
- signal: options?.signal
3498
- });
3499
- const mergeParameters = options?.mergeParameters ?? true;
3500
- const nextSettings = mergeParameters ? {
3501
- ...this.getBehaviorSettingsFromResource(resource),
3502
- ...settingsPatch
3503
- } : { ...settingsPatch };
3504
- const nextTags = mergeResourceBehaviorTags(resource.tags, nextSettings);
3505
- await this.updateTags(resourceId, nextTags, {
3506
- versionToCheck: options?.checkVersion ? resource.meta?.version ?? void 0 : void 0,
3507
- isNeedAnnouncement: options?.isNeedAnnouncement,
3508
- signal: options?.signal
3509
- });
3510
- return this.getById(resourceId, {
3511
- includeDeleted: options?.includeDeleted,
3512
- isNeedItem: options?.isNeedItem,
3513
- isNeedParentResource: options?.isNeedParentResource,
3514
- includeChildTypes: options?.includeChildTypes,
3515
- securityToken: options?.securityToken,
3516
- signal: options?.signal
3517
- });
3585
+ return this.writeBehaviorSettings(resourceId, settingsPatch, options, "ResourcesFacade.updateBehaviorSettings" /* ResourceUpdate */);
3586
+ }
3587
+ /** Контекст локален вызову; алгоритм и число запросов двух публичных операций совпадают. */
3588
+ async writeBehaviorSettings(resourceId, settingsPatch, options, operation) {
3589
+ let failureAt = "ReadCurrent" /* ReadCurrent */;
3590
+ let effect = "NotApplied" /* NotApplied */;
3591
+ try {
3592
+ const resource = await this.getById(resourceId, {
3593
+ includeDeleted: options?.includeDeleted,
3594
+ isNeedItem: options?.isNeedItem,
3595
+ isNeedParentResource: options?.isNeedParentResource,
3596
+ includeChildTypes: options?.includeChildTypes,
3597
+ securityToken: options?.securityToken,
3598
+ signal: options?.signal
3599
+ });
3600
+ failureAt = "PrepareWrite" /* PrepareWrite */;
3601
+ const mergeParameters = options?.mergeParameters ?? true;
3602
+ const nextSettings = mergeParameters ? {
3603
+ ...this.getBehaviorSettingsFromResource(resource),
3604
+ ...settingsPatch
3605
+ } : { ...settingsPatch };
3606
+ const nextTags = mergeResourceBehaviorTags(resource.tags, nextSettings);
3607
+ failureAt = "WriteSettings" /* WriteSettings */;
3608
+ let dispatched = false;
3609
+ let acknowledgement;
3610
+ try {
3611
+ acknowledgement = await this.http.requestJson({
3612
+ method: "PUT",
3613
+ path: `${BASE7}/${encodePathSegment(resourceId)}/tags`,
3614
+ body: nextTags,
3615
+ query: {
3616
+ versionToCheck: options?.checkVersion ? resource.meta?.version ?? void 0 : void 0,
3617
+ isNeedAnnouncement: options?.isNeedAnnouncement
3618
+ },
3619
+ signal: options?.signal
3620
+ }, () => {
3621
+ dispatched = true;
3622
+ });
3623
+ } catch (cause) {
3624
+ effect = dispatched ? "Unknown" /* Unknown */ : "NotApplied" /* NotApplied */;
3625
+ const error = contextualizeBehaviorError(cause, operation, failureAt, effect).withContext({
3626
+ request: dispatched ? "Started" /* Started */ : "NotStarted" /* NotStarted */
3627
+ });
3628
+ if (dispatched && isBehaviorCasRefusal(error)) {
3629
+ effect = "NotApplied" /* NotApplied */;
3630
+ throw error.withContext({
3631
+ category: "VersionConflict" /* VersionConflict */,
3632
+ detail: { domain: "BehaviorSettings" /* BehaviorSettings */ }
3633
+ });
3634
+ }
3635
+ throw error;
3636
+ }
3637
+ effect = acknowledgement?.id === resourceId ? "Applied" /* Applied */ : "Unknown" /* Unknown */;
3638
+ failureAt = "ReadBack" /* ReadBack */;
3639
+ return await this.getById(resourceId, {
3640
+ includeDeleted: options?.includeDeleted,
3641
+ isNeedItem: options?.isNeedItem,
3642
+ isNeedParentResource: options?.isNeedParentResource,
3643
+ includeChildTypes: options?.includeChildTypes,
3644
+ securityToken: options?.securityToken,
3645
+ signal: options?.signal
3646
+ });
3647
+ } catch (cause) {
3648
+ throw contextualizeBehaviorError(cause, operation, failureAt, effect);
3649
+ }
3518
3650
  }
3519
3651
  setBehaviorSettings(resourceId, settings, options) {
3520
- return this.updateBehaviorSettings(resourceId, settings, {
3652
+ return this.writeBehaviorSettings(resourceId, settings, {
3521
3653
  ...options,
3522
3654
  mergeParameters: false
3523
- });
3655
+ }, "ResourcesFacade.setBehaviorSettings" /* ResourceSet */);
3524
3656
  }
3525
3657
  /** `GET /api/v1/resources/personallist` */
3526
3658
  listPersonal(options) {
@@ -3591,6 +3723,173 @@ var ResourcesFacade = class extends TorrowItemFacade {
3591
3723
  }
3592
3724
  };
3593
3725
 
3726
+ // ../../src/resources/services/behavior-settings.ts
3727
+ var ServiceBehaviorSettingsIssueReason = /* @__PURE__ */ ((ServiceBehaviorSettingsIssueReason2) => {
3728
+ ServiceBehaviorSettingsIssueReason2["InvalidValue"] = "InvalidValue";
3729
+ ServiceBehaviorSettingsIssueReason2["AmbiguousValue"] = "AmbiguousValue";
3730
+ return ServiceBehaviorSettingsIssueReason2;
3731
+ })(ServiceBehaviorSettingsIssueReason || {});
3732
+ var configs = Object.values(SERVICE_IMPLICIT_TAG_REGISTRY);
3733
+ function isBehaviorVersion(value) {
3734
+ return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 2147483647;
3735
+ }
3736
+ function record2(value) {
3737
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3738
+ }
3739
+ function validateBehaviorResponse(value, serviceId) {
3740
+ if (!record2(value) || value.id !== serviceId) throw behaviorSettingsError("InvalidResponse" /* InvalidResponse */);
3741
+ const personal = value.personalInfo;
3742
+ if (record2(personal) && (personal.itemContentType === "NoRights" /* NoRights */ || personal.itemContentType === "SubscriptionIsNotValid" /* SubscriptionIsNotValid */)) {
3743
+ throw behaviorSettingsError("AccessDenied" /* AccessDenied */);
3744
+ }
3745
+ if (!record2(personal) || personal.itemContentType !== "Full" /* Full */ || value.tags == null || !record2(value.meta) || !isBehaviorVersion(value.meta.version)) {
3746
+ throw behaviorSettingsError("InsufficientData" /* InsufficientData */);
3747
+ }
3748
+ if (!Array.isArray(value.tags) || !value.tags.every((tag) => typeof tag === "string")) {
3749
+ throw behaviorSettingsError("InvalidResponse" /* InvalidResponse */);
3750
+ }
3751
+ const operations = personal.operationList;
3752
+ if (operations != null && (!Array.isArray(operations) || !operations.every((operation) => typeof operation === "string" || typeof operation === "number" && Number.isSafeInteger(operation)))) {
3753
+ throw behaviorSettingsError("InvalidResponse" /* InvalidResponse */);
3754
+ }
3755
+ return { tags: [...new Set(value.tags)], version: value.meta.version, operations };
3756
+ }
3757
+ function assertBehaviorPermission(operations) {
3758
+ if (!Array.isArray(operations)) throw behaviorSettingsError("InsufficientData" /* InsufficientData */);
3759
+ if (!operations.includes("SetItemTags" /* SetItemTags */)) throw behaviorSettingsError("AccessDenied" /* AccessDenied */);
3760
+ }
3761
+ function suffixPrefix(tag, suffix, ignoreCase = false) {
3762
+ const matches = ignoreCase ? tag.toLowerCase().endsWith(suffix.toLowerCase()) : tag.endsWith(suffix);
3763
+ return matches ? tag.slice(0, -suffix.length) : void 0;
3764
+ }
3765
+ function positive(raw) {
3766
+ const value = Number(raw);
3767
+ return /^\d+$/.test(raw) && Number.isSafeInteger(value) && value > 0 ? value : void 0;
3768
+ }
3769
+ function prefix(tag, config) {
3770
+ if (config.kind === "string" /* String */ || config.kind === "positiveInt" /* PositiveInt */) {
3771
+ for (const suffix of config.suffixes) {
3772
+ const value = suffixPrefix(tag, suffix, config.caseInsensitive);
3773
+ if (value !== void 0) return value;
3774
+ }
3775
+ } else if (config.kind === "exactFlag" /* ExactFlag */) {
3776
+ if (config.tokens.includes(tag) || tag === "HideTelegramNotificationBot" /* LegacyHide */) return "true";
3777
+ } else if (config.kind === "autoStart" /* AutoStart */) {
3778
+ for (const suffix of config.suffixes) {
3779
+ const value = suffixPrefix(tag, `:${suffix}`);
3780
+ if (value !== void 0) return value;
3781
+ }
3782
+ } else if (config.kind === "agreement" /* Agreement */) {
3783
+ for (const suffix of [config.textSuffix, config.linkSuffix]) {
3784
+ const position = tag.lastIndexOf(suffix);
3785
+ if (position >= 0 && /^\d+$/.test(tag.slice(position + suffix.length))) return tag;
3786
+ }
3787
+ }
3788
+ return void 0;
3789
+ }
3790
+ function readBehaviorSettings(tags) {
3791
+ const settings = { hideMessengerConnectUi: false };
3792
+ const issues = [];
3793
+ const target = settings;
3794
+ for (const config of configs) {
3795
+ const matching = [...new Set(tags)].filter((tag) => prefix(tag, config) !== void 0);
3796
+ if (!matching.length) continue;
3797
+ let invalid = false;
3798
+ let ambiguous = false;
3799
+ let result;
3800
+ if (config.kind === "agreement" /* Agreement */) {
3801
+ const entries = /* @__PURE__ */ new Map();
3802
+ for (const tag of matching) {
3803
+ for (const [attribute, suffix] of [["text", config.textSuffix], ["link", config.linkSuffix]]) {
3804
+ const position = tag.lastIndexOf(suffix);
3805
+ if (position < 0 || !/^\d+$/.test(tag.slice(position + suffix.length))) continue;
3806
+ const index = positive(tag.slice(position + suffix.length));
3807
+ let value = tag.slice(0, position).trim();
3808
+ if (attribute === "link") value = value.replace(/^(https?)\/\//, "$1://");
3809
+ if (!index || !value) {
3810
+ invalid = true;
3811
+ continue;
3812
+ }
3813
+ const entry = entries.get(index) ?? { index };
3814
+ if (entry[attribute] !== void 0 && entry[attribute] !== value) ambiguous = true;
3815
+ entry[attribute] = value;
3816
+ entries.set(index, entry);
3817
+ }
3818
+ }
3819
+ result = [...entries.values()].sort((a, b) => a.index - b.index);
3820
+ } else {
3821
+ const values = /* @__PURE__ */ new Set();
3822
+ for (const tag of matching) {
3823
+ const raw = prefix(tag, config);
3824
+ const value = config.kind === "exactFlag" /* ExactFlag */ ? true : config.kind === "string" /* String */ ? raw.trim() || void 0 : positive(raw);
3825
+ if (value === void 0) invalid = true;
3826
+ else values.add(value);
3827
+ }
3828
+ ambiguous = values.size > 1;
3829
+ result = [...values][0];
3830
+ }
3831
+ if (invalid || ambiguous) {
3832
+ issues.push({ field: config.field, reason: invalid ? "InvalidValue" /* InvalidValue */ : "AmbiguousValue" /* AmbiguousValue */ });
3833
+ } else target[config.field] = result;
3834
+ }
3835
+ return { settings, issues };
3836
+ }
3837
+ function normalizeBehaviorInput(input) {
3838
+ function fail() {
3839
+ throw behaviorSettingsError("InvalidInput" /* InvalidInput */);
3840
+ }
3841
+ if (!record2(input) || Object.keys(input).some((key) => !Object.hasOwn(SERVICE_IMPLICIT_TAG_REGISTRY, key))) fail();
3842
+ const output = {};
3843
+ for (const config of configs) {
3844
+ if (!Object.hasOwn(input, config.field)) continue;
3845
+ const value = input[config.field];
3846
+ if (config.kind === "string" /* String */) {
3847
+ if (typeof value !== "string" || !value.trim()) fail();
3848
+ output[config.field] = value.trim();
3849
+ } else if (config.kind === "positiveInt" /* PositiveInt */ || config.kind === "autoStart" /* AutoStart */) {
3850
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) fail();
3851
+ output[config.field] = value;
3852
+ } else if (config.kind === "exactFlag" /* ExactFlag */) {
3853
+ if (typeof value !== "boolean") fail();
3854
+ output[config.field] = value;
3855
+ } else {
3856
+ if (!Array.isArray(value)) fail();
3857
+ const seen = /* @__PURE__ */ new Set();
3858
+ output[config.field] = value.map((entry) => {
3859
+ if (!record2(entry) || Object.keys(entry).some((key) => !["index", "text", "link"].includes(key)) || typeof entry.index !== "number" || !Number.isSafeInteger(entry.index) || entry.index <= 0 || seen.has(entry.index)) fail();
3860
+ seen.add(entry.index);
3861
+ const normalized = { index: entry.index };
3862
+ for (const attribute of ["text", "link"]) {
3863
+ if (Object.hasOwn(entry, attribute)) {
3864
+ if (typeof entry[attribute] !== "string" || !entry[attribute].trim()) fail();
3865
+ normalized[attribute] = entry[attribute].trim();
3866
+ }
3867
+ }
3868
+ if (!normalized.text && !normalized.link) fail();
3869
+ return normalized;
3870
+ }).sort((a, b) => a.index - b.index);
3871
+ }
3872
+ }
3873
+ return output;
3874
+ }
3875
+ function mergeBehaviorSettings(tags, settings, mergeParameters) {
3876
+ const selected = configs.filter((config) => !mergeParameters || Object.hasOwn(settings, config.field));
3877
+ const output = tags.filter((tag) => !selected.some((config) => prefix(tag, config) !== void 0));
3878
+ for (const config of selected) {
3879
+ const value = settings[config.field];
3880
+ if (value === void 0) continue;
3881
+ if (config.kind === "string" /* String */ || config.kind === "positiveInt" /* PositiveInt */) output.push(`${value}${config.suffixes[0]}`);
3882
+ else if (config.kind === "exactFlag" /* ExactFlag */) {
3883
+ if (value) output.push(config.tokens[0]);
3884
+ } else if (config.kind === "autoStart" /* AutoStart */) output.push(`${value}:${config.suffixes[0]}`);
3885
+ else if (config.kind === "agreement" /* Agreement */) for (const entry of settings.agreementItems ?? []) {
3886
+ if (entry.text) output.push(`${entry.text}${config.textSuffix}${entry.index}`);
3887
+ if (entry.link) output.push(`${entry.link.replace(/^(https?):\/\//, "$1//")}${config.linkSuffix}${entry.index}`);
3888
+ }
3889
+ }
3890
+ return [...new Set(output)];
3891
+ }
3892
+
3594
3893
  // ../../src/resources/services/service-requests.ts
3595
3894
  var BASE8 = "/api/v1/services";
3596
3895
  function toSdkServiceItem(service) {
@@ -3707,62 +4006,118 @@ var ServicesFacade = class extends TorrowItemFacade {
3707
4006
  getById(torrowId, options) {
3708
4007
  return getService(this.http, torrowId, options);
3709
4008
  }
3710
- /** Внутренний helper: получить типизированные настройки поведения услуги из `service.tags`. */
3711
- getBehaviorSettingsFromService(service) {
3712
- const settings = "behaviorSettings" in service && service.behaviorSettings ? service.behaviorSettings : parseServiceImplicitTagParams(service.tags);
3713
- return settings;
4009
+ /** Читает сырой OpenAPI DTO до проекции, чтобы malformed tags не маскировались парсером. */
4010
+ async readBehaviorState(serviceId, options) {
4011
+ if (typeof serviceId !== "string" || !serviceId.trim()) {
4012
+ throw behaviorSettingsError("InvalidInput" /* InvalidInput */);
4013
+ }
4014
+ const value = await this.http.requestJson({
4015
+ method: "GET",
4016
+ path: `${BASE9}/${encodePathSegment(serviceId)}`,
4017
+ query: {
4018
+ profileId: options?.profileId,
4019
+ versionToCheck: options?.versionToCheck,
4020
+ includeDeleted: options?.includeDeleted,
4021
+ includeChildTypes: options?.includeChildTypes,
4022
+ securityToken: options?.securityToken
4023
+ },
4024
+ signal: options?.signal
4025
+ });
4026
+ return validateBehaviorResponse(value, serviceId);
3714
4027
  }
3715
- /** `GET + parse`: получить сервис и извлечь `ServiceBehaviorSettings`. */
4028
+ /** Один GET: версия всей услуги, предметные настройки и диагностика неполного снимка. */
3716
4029
  async getBehaviorSettings(serviceId, options) {
3717
- const service = await this.getById(serviceId, options);
3718
- return this.getBehaviorSettingsFromService(service);
4030
+ try {
4031
+ const state = await this.readBehaviorState(serviceId, options);
4032
+ return { serviceId, version: state.version, ...readBehaviorSettings(state.tags) };
4033
+ } catch (cause) {
4034
+ throw contextualizeBehaviorError(cause, "ServicesFacade.getBehaviorSettings" /* ServiceGet */, "ReadCurrent" /* ReadCurrent */);
4035
+ }
3719
4036
  }
3720
4037
  /**
3721
- * Обновляет настройки поведения услуги через типизированную структуру.
3722
- * `mergeParameters=true` по умолчанию: patch поверх текущих settings.
3723
- *
3724
- * Персист только через **`PUT .../services/{id}/tags`**: поле `tags` в теле `PUT /services/{id}`
3725
- * сервер игнорирует (`tags` в `TorrowItemKeysExcludedFromItemWriteBody`).
4038
+ * GET → локальный patch → PUT /tags с необязательной проверкой версии; автоматического readback нет.
4039
+ * mergeParameters=true сохраняет пропущенные поля, false заменяет все настройки.
4040
+ * Порядок и одинаковые дубли сервер не сохраняет; no-op определяется множеством строк.
3726
4041
  */
3727
- async updateBehaviorSettings(serviceId, settingsPatch, options) {
3728
- const service = await this.getById(serviceId, {
3729
- profileId: options?.profileId,
3730
- includeDeleted: options?.includeDeleted,
3731
- includeChildTypes: options?.includeChildTypes,
3732
- securityToken: options?.securityToken,
3733
- signal: options?.signal
3734
- });
3735
- const mergeParameters = options?.mergeParameters ?? true;
3736
- const nextSettings = mergeParameters ? {
3737
- ...this.getBehaviorSettingsFromService(service),
3738
- ...settingsPatch
3739
- } : { ...settingsPatch };
3740
- const nextTags = mergeServiceImplicitTags(service.tags, nextSettings);
3741
- await this.updateTags(serviceId, nextTags, {
3742
- versionToCheck: options?.checkVersion ? service.meta?.version ?? void 0 : void 0,
3743
- isNeedAnnouncement: options?.isNeedAnnouncement,
3744
- signal: options?.signal
3745
- });
3746
- return this.getById(serviceId, {
3747
- profileId: options?.profileId,
3748
- includeDeleted: options?.includeDeleted,
3749
- includeChildTypes: options?.includeChildTypes,
3750
- securityToken: options?.securityToken,
3751
- signal: options?.signal
3752
- });
4042
+ async updateBehaviorSettings(serviceId, settingsPatch, options = {}) {
4043
+ return this.writeBehaviorSettings(serviceId, settingsPatch, options, "ServicesFacade.updateBehaviorSettings" /* ServiceUpdate */);
3753
4044
  }
3754
- /** Полная установка `ServiceBehaviorSettings` (без merge c текущими параметрами). */
3755
- setBehaviorSettings(serviceId, settings, options) {
3756
- return this.updateBehaviorSettings(serviceId, settings, {
3757
- ...options,
3758
- mergeParameters: false
3759
- });
4045
+ /** Общий алгоритм двух публичных операций; контекст и эффект принадлежат только текущему вызову. */
4046
+ async writeBehaviorSettings(serviceId, settingsPatch, options, operation) {
4047
+ let failureAt = "ValidateInput" /* ValidateInput */;
4048
+ let effect = "NotApplied" /* NotApplied */;
4049
+ try {
4050
+ if (!options || options.versionToCheck !== void 0 && !isBehaviorVersion(options.versionToCheck) || Object.hasOwn(options, "checkVersion") && options.checkVersion !== true || Object.hasOwn(options, "mergeParameters") && typeof options.mergeParameters !== "boolean" || options.isNeedAnnouncement != null && typeof options.isNeedAnnouncement !== "boolean" || Object.keys(options).some((key) => !["versionToCheck", "checkVersion", "mergeParameters", "profileId", "securityToken", "isNeedAnnouncement", "signal"].includes(key))) {
4051
+ throw behaviorSettingsError("InvalidInput" /* InvalidInput */);
4052
+ }
4053
+ const normalized = normalizeBehaviorInput(settingsPatch);
4054
+ options = { ...options };
4055
+ failureAt = "ReadCurrent" /* ReadCurrent */;
4056
+ const state = await this.readBehaviorState(serviceId, {
4057
+ profileId: options.profileId,
4058
+ securityToken: options.securityToken,
4059
+ signal: options.signal
4060
+ });
4061
+ failureAt = "PrepareWrite" /* PrepareWrite */;
4062
+ assertBehaviorPermission(state.operations);
4063
+ if (options.versionToCheck !== void 0 && state.version !== options.versionToCheck) {
4064
+ throw behaviorSettingsError("VersionConflict" /* VersionConflict */);
4065
+ }
4066
+ const tags = mergeBehaviorSettings(state.tags, normalized, options.mergeParameters ?? true);
4067
+ const projected = readBehaviorSettings(tags);
4068
+ const unchanged = tags.length === state.tags.length && tags.every((tag) => state.tags.includes(tag));
4069
+ if (unchanged) return { serviceId, version: state.version, ...projected, changed: false };
4070
+ failureAt = "WriteSettings" /* WriteSettings */;
4071
+ let result;
4072
+ let dispatched = false;
4073
+ try {
4074
+ result = await this.http.requestJson({
4075
+ method: "PUT",
4076
+ path: `${BASE9}/${encodePathSegment(serviceId)}/tags`,
4077
+ query: { versionToCheck: options.versionToCheck ?? (options.checkVersion ? state.version : void 0), isNeedAnnouncement: options.isNeedAnnouncement ?? false },
4078
+ body: tags,
4079
+ signal: options.signal
4080
+ }, () => {
4081
+ dispatched = true;
4082
+ });
4083
+ } catch (cause) {
4084
+ effect = dispatched ? "Unknown" /* Unknown */ : "NotApplied" /* NotApplied */;
4085
+ const error = contextualizeBehaviorError(cause, operation, failureAt, effect).withContext({
4086
+ request: dispatched ? "Started" /* Started */ : "NotStarted" /* NotStarted */
4087
+ });
4088
+ if (dispatched && isBehaviorCasRefusal(error)) {
4089
+ effect = "NotApplied" /* NotApplied */;
4090
+ throw error.withContext({
4091
+ category: "VersionConflict" /* VersionConflict */,
4092
+ detail: { domain: "BehaviorSettings" /* BehaviorSettings */ }
4093
+ });
4094
+ }
4095
+ throw error;
4096
+ }
4097
+ effect = "Unknown" /* Unknown */;
4098
+ failureAt = "ValidateWriteResponse" /* ValidateWriteResponse */;
4099
+ if (!result || result.id !== serviceId || !isBehaviorVersion(result.meta?.version)) {
4100
+ throw new TorrowSdkError({
4101
+ category: "InvalidResponse" /* InvalidResponse */,
4102
+ request: "Started" /* Started */,
4103
+ detail: { domain: "BehaviorSettings" /* BehaviorSettings */ },
4104
+ sensitive: { body: result }
4105
+ });
4106
+ }
4107
+ return { serviceId, version: result.meta.version, ...projected, changed: true };
4108
+ } catch (cause) {
4109
+ throw contextualizeBehaviorError(cause, operation, failureAt, effect);
4110
+ }
4111
+ }
4112
+ /** Полная замена: пропущенные поля очищаются, посторонние данные сохраняются. */
4113
+ setBehaviorSettings(serviceId, settings, options = {}) {
4114
+ return this.writeBehaviorSettings(serviceId, settings, { ...options, mergeParameters: false }, "ServicesFacade.setBehaviorSettings" /* ServiceSet */);
3760
4115
  }
3761
4116
  /**
3762
4117
  * Запись на услугу — `POST /api/v1/services/{serviceId}/order`.
3763
4118
  * Минимум в теле: непустой смысл в `checkInProperties` (слот, участник, контакты — по правилам услуги и OpenAPI).
3764
4119
  * Успех: `OrderResp` (часто интересен `order` как кейс и `consumerSecurityToken` для гостевого доступа).
3765
- * Типичные отказы: нет слота, услуга недоступна по времени, подписка/баланс — разбирай тело `TorrowHttpError` (`name` / `code`); эталон UX — `torrow-apps` `CreateOrderTask`.
4120
+ * Типичные отказы: нет слота, услуга недоступна по времени, подписка/баланс — разбирай тело `TorrowSdkError` (`name` / `code`); эталон UX — `torrow-apps` `CreateOrderTask`.
3766
4121
  */
3767
4122
  createOrder(serviceId, body, options) {
3768
4123
  return createOrder(this.http, serviceId, body, options);
@@ -5589,21 +5944,30 @@ var ShortUrlFacade = class {
5589
5944
  * Возвращает заголовок `Location` без следования редиректу.
5590
5945
  */
5591
5946
  async getRedirectLocation(shortUrl, options) {
5592
- const fetchFn = this.config.fetch ?? globalThis.fetch;
5593
- const origin = this.config.baseUrl ?? DEFAULT_TORROW_API_BASE_URL;
5594
- const url = joinBaseUrlAndPath(origin, `${BASE27}/${encodePathSegment(shortUrl)}`);
5595
- const authHeaders = await buildAuthHeaders(this.config);
5596
- const res = await fetchFn(url, {
5597
- method: "GET",
5598
- redirect: "manual",
5599
- headers: {
5600
- Accept: "*/*",
5601
- ...authHeaders,
5602
- ...this.config.defaultHeaders
5603
- },
5604
- signal: options?.signal
5605
- });
5606
- return res.headers.get("Location");
5947
+ let request = "NotStarted" /* NotStarted */;
5948
+ let url;
5949
+ try {
5950
+ const fetchFn = this.config.fetch ?? globalThis.fetch;
5951
+ const origin = this.config.baseUrl ?? DEFAULT_TORROW_API_BASE_URL;
5952
+ url = joinBaseUrlAndPath(origin, `${BASE27}/${encodePathSegment(shortUrl)}`);
5953
+ const authHeaders = await buildAuthHeaders(this.config);
5954
+ const init = {
5955
+ method: "GET",
5956
+ redirect: "manual",
5957
+ headers: {
5958
+ Accept: "*/*",
5959
+ ...authHeaders,
5960
+ ...this.config.defaultHeaders
5961
+ },
5962
+ signal: options?.signal
5963
+ };
5964
+ options?.signal?.throwIfAborted();
5965
+ request = "Started" /* Started */;
5966
+ const res = await fetchFn(url, init);
5967
+ return res.headers.get("Location");
5968
+ } catch (cause) {
5969
+ throw normalizeTransportError(cause, request, options?.signal, url);
5970
+ }
5607
5971
  }
5608
5972
  };
5609
5973
 
@@ -6755,8 +7119,8 @@ var ServiceOrderConflictErrorNameValues = {
6755
7119
  ServiceUnavailableAtTime: "SERVICE_IS_NOT_AVAILABLE_AT_TIME"
6756
7120
  };
6757
7121
  function getServiceOrderConflictReason(error) {
6758
- if (!(error instanceof TorrowHttpError)) return void 0;
6759
- switch (error.apiErrorName) {
7122
+ if (!(error instanceof TorrowSdkError)) return void 0;
7123
+ switch (error.http?.apiErrorName) {
6760
7124
  case ServiceOrderConflictErrorNameValues.ResourceUnavailable:
6761
7125
  return "resource-unavailable";
6762
7126
  case ServiceOrderConflictErrorNameValues.QuantityUnavailable:
@@ -6960,6 +7324,9 @@ export {
6960
7324
  AuthFacade,
6961
7325
  AuthScopeValueList,
6962
7326
  AuthScopeValues,
7327
+ AuthSessionEffectStep,
7328
+ AuthSessionErrorReason,
7329
+ BehaviorSettingsEffectStep,
6963
7330
  BlockReasonsFacade,
6964
7331
  CalendarsFacade,
6965
7332
  CaseStateValues,
@@ -6995,6 +7362,7 @@ export {
6995
7362
  LoyaltySystemsFacade,
6996
7363
  NoteTypeValues,
6997
7364
  NotesFacade,
7365
+ OtpErrorReason,
6998
7366
  ParticipationFieldRequirementTypeValues,
6999
7367
  ParticipationFieldTypeValues,
7000
7368
  PinnedViewsFacade,
@@ -7008,6 +7376,7 @@ export {
7008
7376
  SearchFacade,
7009
7377
  SendCodeAccountTypeValueList,
7010
7378
  SendCodeAccountTypeValues,
7379
+ ServiceBehaviorSettingsIssueReason,
7011
7380
  ServiceBuilder,
7012
7381
  ServiceCheckInWizardStepParameterName,
7013
7382
  ServiceCheckInWizardStepType,
@@ -7027,11 +7396,16 @@ export {
7027
7396
  TokenStatusValueList,
7028
7397
  TokenStatusValues,
7029
7398
  TorrowClient,
7030
- TorrowHttpError,
7031
7399
  TorrowIdError,
7032
7400
  TorrowItemTypeValueList,
7033
7401
  TorrowItemTypeValues,
7034
7402
  TorrowObjectTypeCode,
7403
+ TorrowSdkEffectArea,
7404
+ TorrowSdkEffectState,
7405
+ TorrowSdkError,
7406
+ TorrowSdkErrorCategory,
7407
+ TorrowSdkErrorDomain,
7408
+ TorrowSdkRequestState,
7035
7409
  UsageConditionTypeValues,
7036
7410
  UsageTypeValues,
7037
7411
  UserAccessLevelValues,