@coopenomics/extension-kit 2026.9.22 → 2026.9.24-3

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.mjs CHANGED
@@ -1,5 +1,6 @@
1
- import { createParamDecorator, UnauthorizedException, SetMetadata, applyDecorators, Injectable, Inject, ForbiddenException, HttpException, HttpStatus, BadRequestException } from '@nestjs/common';
1
+ import { HttpException, HttpStatus, BadGatewayException, ServiceUnavailableException, BadRequestException, UnauthorizedException, ForbiddenException, NotFoundException, ConflictException, UnprocessableEntityException, InternalServerErrorException, createParamDecorator, SetMetadata, applyDecorators, Injectable, Inject } from '@nestjs/common';
2
2
  import { GqlExecutionContext, Directive, Field, InputType, registerEnumType, Int, IntersectionType, OmitType, ObjectType } from '@nestjs/graphql';
3
+ import { te, t } from '@coopenomics/i18n/server';
3
4
  import { Reflector } from '@nestjs/core';
4
5
  import { AuthGuard } from '@nestjs/passport';
5
6
  import { timingSafeEqual } from 'node:crypto';
@@ -10,11 +11,155 @@ import { GraphQLJSON } from 'graphql-type-json';
10
11
  import { Classes } from '@coopenomics/sdk';
11
12
  import { Name } from '@wharfkit/antelope';
12
13
  import moment from 'moment';
14
+ import crypto, { createHash } from 'crypto';
13
15
  import moment$1 from 'moment-timezone';
14
16
  import 'moment/locale/ru';
15
- import crypto from 'crypto';
16
17
  import { _Common } from 'cooptypes';
17
18
 
19
+ const DOMAIN_ERROR = Symbol.for("@coopenomics/extension-kit/DomainError");
20
+ function domainErrorMessage(code, params = {}) {
21
+ const key = `errors.${code}`;
22
+ if (!te(key) && typeof params.message === "string")
23
+ return params.message;
24
+ return t(key, params);
25
+ }
26
+ function body(code, params, status) {
27
+ return { statusCode: status, code, params, message: domainErrorMessage(code, params) };
28
+ }
29
+ function brand(target, code, params) {
30
+ target.code = code;
31
+ target.params = params;
32
+ target[DOMAIN_ERROR] = true;
33
+ target.name = "DomainError";
34
+ }
35
+ class DomainError extends HttpException {
36
+ static [Symbol.hasInstance](value) {
37
+ return !!value && value[DOMAIN_ERROR] === true;
38
+ }
39
+ constructor(code, params = {}, status = HttpStatus.BAD_REQUEST) {
40
+ super(body(code, params, status), status);
41
+ brand(this, code, params);
42
+ }
43
+ /** Ключ сообщения в словаре. */
44
+ get messageKey() {
45
+ return `errors.${this.code}`;
46
+ }
47
+ static badRequest(code, params = {}) {
48
+ return new DomainBadRequest(code, params);
49
+ }
50
+ static unauthorized(code, params = {}) {
51
+ return new DomainUnauthorized(code, params);
52
+ }
53
+ static forbidden(code, params = {}) {
54
+ return new DomainForbidden(code, params);
55
+ }
56
+ static notFound(code, params = {}) {
57
+ return new DomainNotFound(code, params);
58
+ }
59
+ static conflict(code, params = {}) {
60
+ return new DomainConflict(code, params);
61
+ }
62
+ static unprocessable(code, params = {}) {
63
+ return new DomainUnprocessable(code, params);
64
+ }
65
+ static tooManyRequests(code, params = {}) {
66
+ return new DomainError(code, params, HttpStatus.TOO_MANY_REQUESTS);
67
+ }
68
+ static internal(code, params = {}) {
69
+ return new DomainInternal(code, params);
70
+ }
71
+ static badGateway(code, params = {}) {
72
+ return new DomainBadGateway(code, params);
73
+ }
74
+ static serviceUnavailable(code, params = {}) {
75
+ return new DomainServiceUnavailable(code, params);
76
+ }
77
+ }
78
+ class DomainBadGateway extends BadGatewayException {
79
+ constructor(code, params) {
80
+ super(body(code, params, HttpStatus.BAD_GATEWAY));
81
+ brand(this, code, params);
82
+ }
83
+ get messageKey() {
84
+ return `errors.${this.code}`;
85
+ }
86
+ }
87
+ class DomainServiceUnavailable extends ServiceUnavailableException {
88
+ constructor(code, params) {
89
+ super(body(code, params, HttpStatus.SERVICE_UNAVAILABLE));
90
+ brand(this, code, params);
91
+ }
92
+ get messageKey() {
93
+ return `errors.${this.code}`;
94
+ }
95
+ }
96
+ class DomainBadRequest extends BadRequestException {
97
+ constructor(code, params) {
98
+ super(body(code, params, HttpStatus.BAD_REQUEST));
99
+ brand(this, code, params);
100
+ }
101
+ get messageKey() {
102
+ return `errors.${this.code}`;
103
+ }
104
+ }
105
+ class DomainUnauthorized extends UnauthorizedException {
106
+ constructor(code, params) {
107
+ super(body(code, params, HttpStatus.UNAUTHORIZED));
108
+ brand(this, code, params);
109
+ }
110
+ get messageKey() {
111
+ return `errors.${this.code}`;
112
+ }
113
+ }
114
+ class DomainForbidden extends ForbiddenException {
115
+ constructor(code, params) {
116
+ super(body(code, params, HttpStatus.FORBIDDEN));
117
+ brand(this, code, params);
118
+ }
119
+ get messageKey() {
120
+ return `errors.${this.code}`;
121
+ }
122
+ }
123
+ class DomainNotFound extends NotFoundException {
124
+ constructor(code, params) {
125
+ super(body(code, params, HttpStatus.NOT_FOUND));
126
+ brand(this, code, params);
127
+ }
128
+ get messageKey() {
129
+ return `errors.${this.code}`;
130
+ }
131
+ }
132
+ class DomainConflict extends ConflictException {
133
+ constructor(code, params) {
134
+ super(body(code, params, HttpStatus.CONFLICT));
135
+ brand(this, code, params);
136
+ }
137
+ get messageKey() {
138
+ return `errors.${this.code}`;
139
+ }
140
+ }
141
+ class DomainUnprocessable extends UnprocessableEntityException {
142
+ constructor(code, params) {
143
+ super(body(code, params, HttpStatus.UNPROCESSABLE_ENTITY));
144
+ brand(this, code, params);
145
+ }
146
+ get messageKey() {
147
+ return `errors.${this.code}`;
148
+ }
149
+ }
150
+ class DomainInternal extends InternalServerErrorException {
151
+ constructor(code, params) {
152
+ super(body(code, params, HttpStatus.INTERNAL_SERVER_ERROR));
153
+ brand(this, code, params);
154
+ }
155
+ get messageKey() {
156
+ return `errors.${this.code}`;
157
+ }
158
+ }
159
+ function validationMessage(key, params = {}) {
160
+ return (args) => t(key, { property: args.property, value: String(args.value ?? ""), ...params });
161
+ }
162
+
18
163
  const ROLES_DENY_SELF_KEY = "roles_deny_self";
19
164
  const ROLES_ANY_STATUS_KEY = "roles_any_status";
20
165
  function AuthRoles(roles, options = {}) {
@@ -31,7 +176,7 @@ const CurrentUser = createParamDecorator((data, context) => {
31
176
  const ctx = GqlExecutionContext.create(context);
32
177
  const request = ctx.getContext().req;
33
178
  if (!request?.user) {
34
- throw new UnauthorizedException("\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u043D\u0435 \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u043E\u0432\u0430\u043D");
179
+ throw DomainError.unauthorized("KIT_USER_NOT_AUTHORIZED");
35
180
  }
36
181
  return request?.user;
37
182
  });
@@ -114,7 +259,7 @@ let OptionalGqlJwtAuthGuard = class extends AuthGuard("jwt") {
114
259
  if (err instanceof Error) {
115
260
  throw err;
116
261
  }
117
- throw new UnauthorizedException("\u0421\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0430, \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u0430\u044F \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u0430\u0446\u0438\u044F");
262
+ throw DomainError.unauthorized("KIT_SESSION_ENDED");
118
263
  }
119
264
  };
120
265
  OptionalGqlJwtAuthGuard = __decorateClass$g([
@@ -164,11 +309,11 @@ let RolesGuard = class {
164
309
  if (allowedRoles.includes(user.role)) {
165
310
  const anyStatus = this.reflector.get(ROLES_ANY_STATUS_KEY, context.getHandler()) === true;
166
311
  if (user.role === PARTICIPANT_ROLE && !anyStatus && user.status !== ACTIVE_USER_STATUS) {
167
- throw new ForbiddenException("\u0414\u043E\u0441\u0442\u0443\u043F \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u043F\u0430\u0439\u0449\u0438\u043A\u043E\u0432 \u043A\u043E\u043E\u043F\u0435\u0440\u0430\u0442\u0438\u0432\u0430");
312
+ throw DomainError.forbidden("KIT_MEMBERS_ONLY");
168
313
  }
169
314
  return true;
170
315
  }
171
- throw new UnauthorizedException(`\u041D\u0435\u0434\u043E\u0441\u0442\u0430\u0442\u043E\u0447\u043D\u043E \u043F\u0440\u0430\u0432 \u0434\u043E\u0441\u0442\u0443\u043F\u0430`);
316
+ throw DomainError.unauthorized("KIT_INSUFFICIENT_RIGHTS");
172
317
  }
173
318
  };
174
319
  RolesGuard = __decorateClass$g([
@@ -184,12 +329,10 @@ let ActiveUserStatusGuard = class {
184
329
  }
185
330
  const user = request.user;
186
331
  if (!user?.status) {
187
- throw new ForbiddenException("\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C");
332
+ throw DomainError.forbidden("KIT_AUTHORIZED_USER_REQUIRED");
188
333
  }
189
334
  if (user.status !== ACTIVE_USER_STATUS) {
190
- throw new ForbiddenException(
191
- "\u0414\u043E\u0441\u0442\u0443\u043F \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u043F\u0430\u0439\u0449\u0438\u043A\u043E\u0432 \u0432 \u0441\u0442\u0430\u0442\u0443\u0441\u0435 \xABactive\xBB"
192
- );
335
+ throw DomainError.forbidden("KIT_ACTIVE_MEMBERS_ONLY");
193
336
  }
194
337
  return true;
195
338
  }
@@ -254,6 +397,7 @@ const BucketRegistry = {
254
397
  const existing = REGISTERED.get(cls);
255
398
  if (existing && existing.name !== spec.name) {
256
399
  throw new Error(
400
+ // i18n-ignore: ошибка регистрации бакета при старте, до пайщика не доходит
257
401
  `BucketRegistry: \u043A\u043B\u0430\u0441\u0441 ${cls.name} \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D \u043F\u043E\u0434 \u0431\u0430\u043A\u0435\u0442\u043E\u043C '${existing.name}', \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u0430\u044F \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044F \u043F\u043E\u0434 '${spec.name}' \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D\u0430`
258
402
  );
259
403
  }
@@ -290,6 +434,7 @@ function bucketProvidersFor(fileStoragePortToken, consumers) {
290
434
  const spec = BucketRegistry.get(cls);
291
435
  if (!spec) {
292
436
  throw new Error(
437
+ // i18n-ignore: ошибка объявления хранилища при старте, до пайщика не доходит
293
438
  `bucketProvidersFor: \u043A\u043B\u0430\u0441\u0441 ${cls.name} \u043D\u0435 \u043F\u043E\u043C\u0435\u0447\u0435\u043D @UseBucket \u2014 \u043E\u0431\u044A\u044F\u0432\u0438\u0442\u0435 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u0440\u044F\u0434\u043E\u043C \u0441 \u0441\u0435\u0440\u0432\u0438\u0441\u043E\u043C`
294
439
  );
295
440
  }
@@ -647,7 +792,7 @@ __decorateClass$a([
647
792
  Field(() => String, { description: "\u0421\u0440\u043E\u043A \u0438\u0441\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u044F (\xAB\u0432 \u0441\u0440\u043E\u043A \u0434\u043E\xBB), \u0444\u043E\u0440\u043C\u0430\u0442 DD.MM.YYYY" }),
648
793
  IsString(),
649
794
  Matches(/^\d{2}\.\d{2}\.\d{4}$/, {
650
- message: "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0441\u0440\u043E\u043A \u0438\u0441\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u044F \u0440\u0430\u0441\u0445\u043E\u0434\u0430 \u0432 \u0444\u043E\u0440\u043C\u0430\u0442\u0435 \u0414\u0414.\u041C\u041C.\u0413\u0413\u0413\u0413"
795
+ message: validationMessage("kit.expenseStatement.dueDateFormat")
651
796
  })
652
797
  ], ExpenseProposalHeaderInputDTO.prototype, "deadline", 2);
653
798
  __decorateClass$a([
@@ -874,6 +1019,7 @@ let entities;
874
1019
  function registerExtensionEntities(list) {
875
1020
  if (entities) {
876
1021
  throw new Error(
1022
+ // i18n-ignore: ошибка сборки приложения при старте, до пайщика не доходит
877
1023
  "\u0421\u043E\u0441\u0442\u0430\u0432 \u0442\u0430\u0431\u043B\u0438\u0446 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u0439 \u0443\u0436\u0435 \u043E\u0431\u044A\u044F\u0432\u043B\u0435\u043D: registerExtensionEntities() \u0432\u044B\u0437\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u043E\u0434\u0438\u043D \u0440\u0430\u0437 \u043F\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435"
878
1024
  );
879
1025
  }
@@ -886,6 +1032,23 @@ function resetExtensionEntities() {
886
1032
  entities = void 0;
887
1033
  }
888
1034
 
1035
+ let migrations;
1036
+ function registerExtensionDatabaseMigrations(list) {
1037
+ if (migrations) {
1038
+ throw new Error(
1039
+ // i18n-ignore: ошибка сборки composition root для разработчика, до пайщика не доходит
1040
+ "\u041C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0442\u0430\u0431\u043B\u0438\u0446 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u0439 \u0443\u0436\u0435 \u043E\u0431\u044A\u044F\u0432\u043B\u0435\u043D\u044B: registerExtensionDatabaseMigrations() \u0432\u044B\u0437\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u043E\u0434\u0438\u043D \u0440\u0430\u0437 \u043F\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435"
1041
+ );
1042
+ }
1043
+ migrations = [...list];
1044
+ }
1045
+ function extensionDatabaseMigrations() {
1046
+ return migrations ?? [];
1047
+ }
1048
+ function resetExtensionDatabaseMigrations() {
1049
+ migrations = void 0;
1050
+ }
1051
+
889
1052
  var __defProp$7 = Object.defineProperty;
890
1053
  var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor;
891
1054
  var __decorateClass$7 = (decorators, target, key, kind) => {
@@ -897,6 +1060,7 @@ var __decorateClass$7 = (decorators, target, key, kind) => {
897
1060
  __defProp$7(target, key, result);
898
1061
  return result;
899
1062
  };
1063
+ const SORT_FIELD_PATTERN = /^([A-Za-z_][A-Za-z0-9_]*(:(asc|desc))?)?$/i;
900
1064
  let PaginationInputDTO = class {
901
1065
  };
902
1066
  __decorateClass$7([
@@ -906,7 +1070,9 @@ __decorateClass$7([
906
1070
  Field(() => Int, { description: "\u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435", defaultValue: 10 })
907
1071
  ], PaginationInputDTO.prototype, "limit", 2);
908
1072
  __decorateClass$7([
909
- Field(() => String, { nullable: true, description: '\u041A\u043B\u044E\u0447 \u0441\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u043A\u0438 (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440, "name")' })
1073
+ Field(() => String, { nullable: true, description: '\u041A\u043B\u044E\u0447 \u0441\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u043A\u0438 (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440, "name")' }),
1074
+ IsOptional(),
1075
+ Matches(SORT_FIELD_PATTERN, { message: validationMessage("kit.pagination.invalidSortField") })
910
1076
  ], PaginationInputDTO.prototype, "sortBy", 2);
911
1077
  __decorateClass$7([
912
1078
  Field(() => String, {
@@ -980,13 +1146,16 @@ class PaginationUtils {
980
1146
  static validatePaginationOptions(options) {
981
1147
  const { page = 1, limit = 10, sortBy, sortOrder = "ASC" } = options;
982
1148
  if (page < 1) {
983
- throw new Error("\u041D\u043E\u043C\u0435\u0440 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0431\u043E\u043B\u044C\u0448\u0435 0");
1149
+ throw DomainError.internal("KIT_PAGE_NUMBER_INVALID");
984
1150
  }
985
1151
  if (limit < 1 || limit > 1e3) {
986
- throw new Error("\u041A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043E\u0442 1 \u0434\u043E 1000");
1152
+ throw DomainError.internal("KIT_PAGE_LIMIT_INVALID");
987
1153
  }
988
1154
  if (sortOrder !== "ASC" && sortOrder !== "DESC") {
989
- throw new Error("\u041D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u0441\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u043A\u0438 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C ASC \u0438\u043B\u0438 DESC");
1155
+ throw DomainError.internal("KIT_SORT_ORDER_INVALID");
1156
+ }
1157
+ if (sortBy !== void 0 && sortBy !== null && !SORT_FIELD_PATTERN.test(sortBy)) {
1158
+ throw DomainError.internal("KIT_SORT_FIELD_INVALID");
990
1159
  }
991
1160
  return {
992
1161
  page,
@@ -1116,6 +1285,7 @@ function configurePlatformSettings(value) {
1116
1285
  function platformSettings() {
1117
1286
  if (!settings) {
1118
1287
  throw new Error(
1288
+ // i18n-ignore: ошибка сборки приложения при старте, до пайщика не доходит
1119
1289
  "\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u043A\u043E\u043D\u0442\u0443\u0440\u0430 \u043D\u0435 \u0437\u0430\u0434\u0430\u043D\u044B: composition root \u043E\u0431\u044F\u0437\u0430\u043D \u0432\u044B\u0437\u0432\u0430\u0442\u044C configurePlatformSettings() \u043F\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435"
1120
1290
  );
1121
1291
  }
@@ -1333,19 +1503,6 @@ DocumentAggregateDTO = __decorateClass$1([
1333
1503
  ObjectType("DocumentAggregate")
1334
1504
  ], DocumentAggregateDTO);
1335
1505
 
1336
- class HttpApiError extends HttpException {
1337
- constructor(statusCode, message, isOperational = true, stack, subcode) {
1338
- super(message, statusCode);
1339
- this.isOperational = isOperational;
1340
- this.subcode = subcode;
1341
- if (stack) {
1342
- this.stack = stack;
1343
- } else {
1344
- Error.captureStackTrace(this, this.constructor);
1345
- }
1346
- }
1347
- }
1348
-
1349
1506
  class CurrencyValidationUtil {
1350
1507
  /**
1351
1508
  * Проверяет, что сумма содержит правильный символ валюты
@@ -1365,10 +1522,10 @@ class CurrencyValidationUtil {
1365
1522
  * @param fieldName Название поля для сообщения об ошибке (по умолчанию "сумма")
1366
1523
  * @throws Error если символ валюты неправильный
1367
1524
  */
1368
- static validateCurrencySymbol(amount, fieldName = "\u0441\u0443\u043C\u043C\u0430") {
1525
+ static validateCurrencySymbol(amount, fieldName = t("kit.currency.defaultFieldName")) {
1369
1526
  if (!this.hasValidCurrencySymbol(amount)) {
1370
1527
  const expectedSymbol = platformSettings().blockchain.rootGovernSymbol;
1371
- throw new Error(`\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0441\u0438\u043C\u0432\u043E\u043B \u0432\u0430\u043B\u044E\u0442\u044B \u0432 ${fieldName}. \u041E\u0436\u0438\u0434\u0430\u043B\u0441\u044F: ${expectedSymbol}`);
1528
+ throw DomainError.internal("KIT_CURRENCY_SYMBOL_INVALID", { fieldName, expectedSymbol });
1372
1529
  }
1373
1530
  }
1374
1531
  /**
@@ -1399,12 +1556,13 @@ class CurrencyValidationUtil {
1399
1556
  /**
1400
1557
  * Форматирует сумму с правильным символом валюты
1401
1558
  * @param value Числовое значение
1402
- * @param precision Количество знаков после запятой (по умолчанию из конфига)
1559
+ * @param precision Количество знаков после запятой (по умолчанию из настроек платформы)
1403
1560
  * @returns string - отформатированная сумма
1404
1561
  */
1405
1562
  static formatAmount(value, precision) {
1406
- const actualPrecision = precision ?? config.blockchain.root_govern_precision;
1407
- const symbol = platformSettings().blockchain.rootGovernSymbol;
1563
+ const { rootGovernPrecision, rootGovernSymbol } = platformSettings().blockchain;
1564
+ const actualPrecision = precision ?? rootGovernPrecision;
1565
+ const symbol = rootGovernSymbol;
1408
1566
  return `${value.toFixed(actualPrecision)} ${symbol}`;
1409
1567
  }
1410
1568
  }
@@ -1412,18 +1570,12 @@ class CurrencyValidationUtil {
1412
1570
  async function verifySignedDocumentAgainstStoredDraft(loadGeneratedByDocHash, signed, metaVerifications) {
1413
1571
  const generated = await loadGeneratedByDocHash(signed.doc_hash);
1414
1572
  if (!generated) {
1415
- throw new HttpApiError(
1416
- HttpStatus.BAD_REQUEST,
1417
- `\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0441 \u0445\u0435\u0448\u0435\u043C ${signed.doc_hash} \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D. \u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u0441\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0439\u0442\u0435 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442.`
1418
- );
1573
+ throw DomainError.badRequest("KIT_GENERATED_DOCUMENT_NOT_FOUND", { docHash: signed.doc_hash });
1419
1574
  }
1420
1575
  const comparison = await Classes.Document.compareDocuments(signed, generated);
1421
1576
  if (!comparison.isValid) {
1422
- const differences = Object.entries(comparison.differences).map(([field, values]) => `${field}: \u043E\u0436\u0438\u0434\u0430\u043B\u043E\u0441\u044C "${values.expected}", \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E "${values.actual}"`).join("; ");
1423
- throw new HttpApiError(
1424
- HttpStatus.BAD_REQUEST,
1425
- `\u0421\u0432\u0435\u0440\u043A\u0430 \u043F\u043E\u0434\u043F\u0438\u0441\u0430\u043D\u043D\u043E\u0433\u043E \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430 \u0441 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u043E\u043C \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0430: ${differences}. \u0412\u043E\u0437\u043C\u043E\u0436\u043D\u0430 \u043F\u043E\u0434\u043C\u0435\u043D\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430.`
1426
- );
1577
+ const differences = Object.entries(comparison.differences).map(([field, values]) => t("kit.documentVerification.fieldDifference", { field, expected: values.expected, actual: values.actual })).join("; ");
1578
+ throw DomainError.badRequest("KIT_DOCUMENT_DRAFT_MISMATCH", { differences });
1427
1579
  }
1428
1580
  if (!metaVerifications || metaVerifications.length === 0) {
1429
1581
  return;
@@ -1433,10 +1585,7 @@ async function verifySignedDocumentAgainstStoredDraft(loadGeneratedByDocHash, si
1433
1585
  const raw = meta[field];
1434
1586
  const actualStr = typeof raw === "string" ? raw.trim() : raw !== void 0 && raw !== null ? String(raw).trim() : "";
1435
1587
  if (!actualStr) {
1436
- throw new HttpApiError(
1437
- HttpStatus.BAD_REQUEST,
1438
- `\u0412 \u043C\u0435\u0442\u0430\u0434\u0430\u043D\u043D\u044B\u0445 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u043F\u043E\u043B\u0435 \xAB${field}\xBB, \u0442\u0440\u0435\u0431\u0443\u0435\u043C\u043E\u0435 \u0434\u043B\u044F \u0441\u0432\u0435\u0440\u043A\u0438.`
1439
- );
1588
+ throw DomainError.badRequest("KIT_DOCUMENT_META_FIELD_MISSING", { field });
1440
1589
  }
1441
1590
  const expectedTrimmed = expected.trim();
1442
1591
  switch (mode) {
@@ -1444,39 +1593,27 @@ async function verifySignedDocumentAgainstStoredDraft(loadGeneratedByDocHash, si
1444
1593
  const parsedExpected = CurrencyValidationUtil.extractAmountValue(expectedTrimmed);
1445
1594
  const parsedActual = CurrencyValidationUtil.extractAmountValue(actualStr);
1446
1595
  if (Number.isNaN(parsedExpected) || Number.isNaN(parsedActual)) {
1447
- throw new HttpApiError(
1448
- HttpStatus.BAD_REQUEST,
1449
- `\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 \u0441\u0443\u043C\u043C\u044B \u0432 \u043F\u043E\u043B\u0435 \xAB${field}\xBB \u0438\u043B\u0438 \u0432 \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u043E\u043C \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0438.`
1450
- );
1596
+ throw DomainError.badRequest("KIT_DOCUMENT_AMOUNT_FORMAT_INVALID", { field });
1451
1597
  }
1452
1598
  if (CurrencyValidationUtil.formatAmount(parsedExpected) !== CurrencyValidationUtil.formatAmount(parsedActual)) {
1453
- throw new HttpApiError(
1454
- HttpStatus.BAD_REQUEST,
1455
- `\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043F\u043E\u043B\u044F \xAB${field}\xBB \u0432 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0435 (${actualStr}) \u043D\u0435 \u0441\u043E\u0432\u043F\u0430\u0434\u0430\u0435\u0442 \u0441 \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u044B\u043C (${expectedTrimmed}). \u0412\u043E\u0437\u043C\u043E\u0436\u043D\u0430 \u043F\u043E\u0434\u043C\u0435\u043D\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430.`
1456
- );
1599
+ throw DomainError.badRequest("KIT_DOCUMENT_FIELD_MISMATCH", { field, actual: actualStr, expected: expectedTrimmed });
1457
1600
  }
1458
1601
  break;
1459
1602
  }
1460
1603
  case "string_trim": {
1461
1604
  if (actualStr !== expectedTrimmed) {
1462
- throw new HttpApiError(
1463
- HttpStatus.BAD_REQUEST,
1464
- `\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043F\u043E\u043B\u044F \xAB${field}\xBB \u0432 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0435 (${actualStr}) \u043D\u0435 \u0441\u043E\u0432\u043F\u0430\u0434\u0430\u0435\u0442 \u0441 \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u044B\u043C (${expectedTrimmed}). \u0412\u043E\u0437\u043C\u043E\u0436\u043D\u0430 \u043F\u043E\u0434\u043C\u0435\u043D\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430.`
1465
- );
1605
+ throw DomainError.badRequest("KIT_DOCUMENT_FIELD_MISMATCH", { field, actual: actualStr, expected: expectedTrimmed });
1466
1606
  }
1467
1607
  break;
1468
1608
  }
1469
1609
  case "hex_case_insensitive": {
1470
1610
  if (actualStr.toLowerCase() !== expectedTrimmed.toLowerCase()) {
1471
- throw new HttpApiError(
1472
- HttpStatus.BAD_REQUEST,
1473
- `\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043F\u043E\u043B\u044F \xAB${field}\xBB \u0432 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0435 (${actualStr}) \u043D\u0435 \u0441\u043E\u0432\u043F\u0430\u0434\u0430\u0435\u0442 \u0441 \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u044B\u043C (${expectedTrimmed}). \u0412\u043E\u0437\u043C\u043E\u0436\u043D\u0430 \u043F\u043E\u0434\u043C\u0435\u043D\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430.`
1474
- );
1611
+ throw DomainError.badRequest("KIT_DOCUMENT_FIELD_MISMATCH", { field, actual: actualStr, expected: expectedTrimmed });
1475
1612
  }
1476
1613
  break;
1477
1614
  }
1478
1615
  default:
1479
- throw new HttpApiError(HttpStatus.INTERNAL_SERVER_ERROR, `\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C \u0441\u0440\u0430\u0432\u043D\u0435\u043D\u0438\u044F meta: ${String(mode)}`);
1616
+ throw DomainError.internal("KIT_DOCUMENT_COMPARE_MODE_UNKNOWN", { mode: String(mode) });
1480
1617
  }
1481
1618
  }
1482
1619
  }
@@ -1533,12 +1670,12 @@ let DomainToBlockchainUtils = class {
1533
1670
  formatQuantityWithPrecision(quantity) {
1534
1671
  const parts = quantity.split(" ");
1535
1672
  if (parts.length !== 2) {
1536
- throw new Error(`\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 quantity: ${quantity}. \u041E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F "\u0447\u0438\u0441\u043B\u043E \u0441\u0438\u043C\u0432\u043E\u043B"`);
1673
+ throw DomainError.internal("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1537
1674
  }
1538
1675
  const [amount, symbol] = parts;
1539
1676
  const numericAmount = parseFloat(amount);
1540
1677
  if (isNaN(numericAmount)) {
1541
- throw new Error(`\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E\u0435 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0432 quantity: ${amount}`);
1678
+ throw DomainError.internal("KIT_QUANTITY_AMOUNT_INVALID", { amount });
1542
1679
  }
1543
1680
  const { rootSymbol, rootPrecision, rootGovernSymbol, rootGovernPrecision } = platformSettings().blockchain;
1544
1681
  let precision;
@@ -1547,7 +1684,7 @@ let DomainToBlockchainUtils = class {
1547
1684
  } else if (symbol === rootGovernSymbol) {
1548
1685
  precision = rootGovernPrecision;
1549
1686
  } else {
1550
- throw new Error(`\u041D\u0435\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0439 \u0441\u0438\u043C\u0432\u043E\u043B: ${symbol}. \u041F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044E\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E: ${rootSymbol}, ${rootGovernSymbol}`);
1687
+ throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1551
1688
  }
1552
1689
  return `${numericAmount.toFixed(precision)} ${symbol}`;
1553
1690
  }
@@ -1555,10 +1692,10 @@ let DomainToBlockchainUtils = class {
1555
1692
  formatNumericStringToAssetString(numericString, precision, symbol) {
1556
1693
  const numericValue = parseFloat(numericString);
1557
1694
  if (isNaN(numericValue)) {
1558
- throw new Error(`\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E\u0435 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435: ${numericString}`);
1695
+ throw DomainError.internal("KIT_NUMBER_INVALID", { value: numericString });
1559
1696
  }
1560
1697
  if (numericValue < 0) {
1561
- throw new Error(`\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043D\u0435 \u043C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C \u043E\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u043C: ${numericString}`);
1698
+ throw DomainError.internal("KIT_NUMBER_NEGATIVE", { value: numericString });
1562
1699
  }
1563
1700
  return `${numericValue.toFixed(precision)} ${symbol}`;
1564
1701
  }
@@ -1589,6 +1726,19 @@ DomainToBlockchainUtils = __decorateClass([
1589
1726
  Injectable()
1590
1727
  ], DomainToBlockchainUtils);
1591
1728
 
1729
+ const DIGEST_RE = /^[0-9a-f]{64}$/;
1730
+ function chainTextDigest(text) {
1731
+ if (!text)
1732
+ return "";
1733
+ return createHash("sha256").update(text, "utf8").digest("hex");
1734
+ }
1735
+ function isChainTextDigest(value) {
1736
+ return typeof value === "string" && DIGEST_RE.test(value);
1737
+ }
1738
+ function resolveChainText(chainValue, current) {
1739
+ return isChainTextDigest(chainValue) ? current ?? "" : chainValue;
1740
+ }
1741
+
1592
1742
  class AssetUtils {
1593
1743
  /**
1594
1744
  * Парсит строку ассета и возвращает числовое значение и символ
@@ -1675,17 +1825,17 @@ class AmountFormatterUtils {
1675
1825
  */
1676
1826
  static formatAmount(amountStr) {
1677
1827
  if (!amountStr || typeof amountStr !== "string") {
1678
- throw new Error(`\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 \u0441\u0443\u043C\u043C\u044B: ${amountStr}. \u041E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F "\u0447\u0438\u0441\u043B\u043E" \u0438\u043B\u0438 "\u0447\u0438\u0441\u043B\u043E \u0432\u0430\u043B\u044E\u0442\u0430"`);
1828
+ throw DomainError.internal("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1679
1829
  }
1680
1830
  const parts = amountStr.trim().split(/\s+/);
1681
1831
  if (parts.length < 1 || !parts[0]) {
1682
- throw new Error(`\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 \u0441\u0443\u043C\u043C\u044B: ${amountStr}. \u041E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F "\u0447\u0438\u0441\u043B\u043E" \u0438\u043B\u0438 "\u0447\u0438\u0441\u043B\u043E \u0432\u0430\u043B\u044E\u0442\u0430"`);
1832
+ throw DomainError.internal("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1683
1833
  }
1684
1834
  const amountPart = parts[0].replace(",", ".");
1685
1835
  const currency = parts.length >= 2 ? parts.slice(1).join(" ") : "";
1686
1836
  const amount = parseFloat(amountPart);
1687
1837
  if (isNaN(amount)) {
1688
- throw new Error(`\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E\u0435 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0432 \u0441\u0443\u043C\u043C\u0435: ${parts[0]}`);
1838
+ throw DomainError.internal("KIT_AMOUNT_NUMBER_INVALID", { value: parts[0] });
1689
1839
  }
1690
1840
  const formattedAmount = amount.toLocaleString("ru-RU", {
1691
1841
  minimumFractionDigits: 2,
@@ -1730,7 +1880,7 @@ function checkPaymentAmount(incomeAmount, expectedAmount, tolerancePercentage) {
1730
1880
  if (incomeAmount < expectedAmount - tolerance) {
1731
1881
  return {
1732
1882
  status: "error",
1733
- message: `\u041D\u0435\u0434\u043E\u0441\u0442\u0430\u0442\u043E\u0447\u043D\u043E \u0441\u0440\u0435\u0434\u0441\u0442\u0432, \u043F\u043E\u0441\u0442\u0443\u043F\u0438\u043B\u043E: ${incomeAmount}, \u043E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F: ${expectedAmount}`
1883
+ message: t("kit.payments.insufficientFunds", { income: incomeAmount, expected: expectedAmount })
1734
1884
  };
1735
1885
  }
1736
1886
  return {
@@ -1761,7 +1911,7 @@ class QuantityUtils {
1761
1911
  } else if (symbol === rootGovernSymbol) {
1762
1912
  return rootGovernPrecision;
1763
1913
  } else {
1764
- throw new Error(`\u041D\u0435\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0439 \u0441\u0438\u043C\u0432\u043E\u043B: ${symbol}. \u041F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044E\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E: ${rootSymbol}, ${rootGovernSymbol}`);
1914
+ throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1765
1915
  }
1766
1916
  }
1767
1917
  /**
@@ -1771,7 +1921,7 @@ class QuantityUtils {
1771
1921
  static validateSymbol(symbol) {
1772
1922
  if (!this.isSupportedSymbol(symbol)) {
1773
1923
  const { rootSymbol, rootGovernSymbol } = platformSettings().blockchain;
1774
- throw new Error(`\u041D\u0435\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0439 \u0441\u0438\u043C\u0432\u043E\u043B: ${symbol}. \u041F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044E\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E: ${rootSymbol}, ${rootGovernSymbol}`);
1924
+ throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1775
1925
  }
1776
1926
  }
1777
1927
  /**
@@ -1783,7 +1933,7 @@ class QuantityUtils {
1783
1933
  static formatQuantityForBlockchain(amount, symbol) {
1784
1934
  this.validateSymbol(symbol);
1785
1935
  if (isNaN(amount) || amount < 0) {
1786
- throw new Error(`\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E\u0435 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435: ${amount}`);
1936
+ throw DomainError.internal("KIT_NUMBER_INVALID", { value: amount });
1787
1937
  }
1788
1938
  const precision = this.getPrecisionForSymbol(symbol);
1789
1939
  const formattedAmount = amount.toFixed(precision);
@@ -1798,7 +1948,7 @@ class QuantityUtils {
1798
1948
  static combineQuantityAndSymbol(amount, symbol) {
1799
1949
  this.validateSymbol(symbol);
1800
1950
  if (isNaN(amount) || amount < 0) {
1801
- throw new Error(`\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E\u0435 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435: ${amount}`);
1951
+ throw DomainError.internal("KIT_NUMBER_INVALID", { value: amount });
1802
1952
  }
1803
1953
  return `${amount} ${symbol}`;
1804
1954
  }
@@ -1810,12 +1960,12 @@ class QuantityUtils {
1810
1960
  static parseQuantityString(quantity) {
1811
1961
  const parts = quantity.split(" ");
1812
1962
  if (parts.length !== 2) {
1813
- throw new Error(`\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 quantity: ${quantity}. \u041E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F "\u0447\u0438\u0441\u043B\u043E \u0441\u0438\u043C\u0432\u043E\u043B"`);
1963
+ throw DomainError.internal("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1814
1964
  }
1815
1965
  const [amountStr, symbol] = parts;
1816
1966
  const amount = parseFloat(amountStr);
1817
1967
  if (isNaN(amount)) {
1818
- throw new Error(`\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E\u0435 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0432 quantity: ${amountStr}`);
1968
+ throw DomainError.internal("KIT_QUANTITY_AMOUNT_INVALID", { amount: amountStr });
1819
1969
  }
1820
1970
  this.validateSymbol(symbol);
1821
1971
  return { amount, symbol };
@@ -1878,14 +2028,29 @@ class DateUtils {
1878
2028
  }
1879
2029
  }
1880
2030
 
1881
- async function waitAfterTransactBeforeChainTableRead() {
1882
- const ms = platformSettings().blockchain.postTransactChainReadDelayMs;
1883
- if (typeof ms !== "number" || !Number.isFinite(ms) || ms <= 0) {
1884
- return;
1885
- }
1886
- await new Promise((resolve) => {
1887
- setTimeout(resolve, ms);
2031
+ function waitForEvent(source, event, match, timeoutMs) {
2032
+ let settle;
2033
+ const promise = new Promise((resolve) => {
2034
+ settle = resolve;
1888
2035
  });
2036
+ let done = false;
2037
+ let timer;
2038
+ const listener = (payload) => {
2039
+ if (match(payload))
2040
+ finish(payload);
2041
+ };
2042
+ function finish(value) {
2043
+ if (done)
2044
+ return;
2045
+ done = true;
2046
+ if (timer)
2047
+ clearTimeout(timer);
2048
+ source.off(event, listener);
2049
+ settle(value);
2050
+ }
2051
+ source.on(event, listener);
2052
+ timer = setTimeout(() => finish(null), timeoutMs);
2053
+ return { promise, cancel: () => finish(null) };
1889
2054
  }
1890
2055
 
1891
2056
  function getAppliedBlockNum(transactResult) {
@@ -1912,11 +2077,52 @@ function sanitizeUserText(value) {
1912
2077
  return _Common.Text.sanitizeEditorMarkdown(value);
1913
2078
  }
1914
2079
 
2080
+ function resolveSortColumn(repository, sortBy, fallback) {
2081
+ if (!sortBy)
2082
+ return fallback;
2083
+ const isKnownColumn = repository.metadata.columns.some(
2084
+ (column) => column.propertyName === sortBy || column.databaseName === sortBy
2085
+ );
2086
+ return isKnownColumn ? sortBy : fallback;
2087
+ }
2088
+
2089
+ class HttpApiError extends HttpException {
2090
+ constructor(statusCode, message, isOperational = true, stack, subcode) {
2091
+ super(message, statusCode);
2092
+ this.isOperational = isOperational;
2093
+ this.subcode = subcode;
2094
+ if (stack) {
2095
+ this.stack = stack;
2096
+ } else {
2097
+ Error.captureStackTrace(this, this.constructor);
2098
+ }
2099
+ }
2100
+ }
2101
+
2102
+ const ASSERT_PREFIX = /assertion failure with message: (.+?)(?:\n|$)/;
2103
+ const CODED = /^([A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+):\s*(.*)$/s;
2104
+ function parseChainAssert(message) {
2105
+ const assert = message.match(ASSERT_PREFIX);
2106
+ const body = (assert ? assert[1] : message).trim();
2107
+ const coded = body.match(CODED);
2108
+ return coded ? { code: coded[1], text: coded[2].trim() } : { text: body };
2109
+ }
2110
+ function chainErrorCode(error) {
2111
+ if (error instanceof DomainError) {
2112
+ if (error.code !== "CHAIN_ASSERT" && error.code !== "CHAIN_ERROR")
2113
+ return error.code;
2114
+ return typeof error.params.message === "string" ? parseChainAssert(error.params.message).code : void 0;
2115
+ }
2116
+ const raw = error?.message ?? String(error);
2117
+ return parseChainAssert(raw).code;
2118
+ }
1915
2119
  function rethrowChainError(error) {
1916
2120
  const raw = error?.message ?? String(error);
1917
- const match = raw.match(/assertion failure with message: (.+?)(?:\n|$)/);
1918
- const clean = match ? match[1].trim() : raw;
1919
- throw new BadRequestException(clean);
2121
+ const match = raw.match(ASSERT_PREFIX);
2122
+ if (!match)
2123
+ throw DomainError.badRequest("CHAIN_ERROR", { message: raw });
2124
+ const { code, text } = parseChainAssert(raw);
2125
+ throw DomainError.badRequest(code ?? "CHAIN_ASSERT", { message: text });
1920
2126
  }
1921
2127
 
1922
- export { ActiveUserStatusGuard, AmountFormatterUtils, AssetUtils, AuthRoles, BaseExtensionModule, BucketRegistry, CandidateFilterInputDTO, CandidateOutputDTO, CandidateStatus, CurrencyValidationUtil, CurrentUser, DEFAULT_DOCUMENT_VERSION, DateUtils, DocumentAggregateDTO, DomainToBlockchainUtils, EMPTY_HASH, EXTENSION_APP_TERMINATE_EVENT, EXTENSION_REPOSITORY, EXTENSION_SECRET_SET, EXTENSION_SECRET_UNSET, ExpenseProposalStatementGenerateDocumentInputDTO, ExpenseProposalStatementSignedDocumentInputDTO, ExpenseProposalStatementSignedMetaDocumentInputDTO, ExtensionAvailability, ExtensionConfigSuppliedBy, ExtensionDomainEntity, GenerateDocumentInputDTO, GenerateDocumentOptionsInputDTO, GenerateMetaDocumentInputDTO, GeneratedDocumentDTO, GqlJwtAuthGuard, HttpApiError, HttpJwtAuthGuard, IPNProvider, InjectBucket, LOG_EXTENSION_REPOSITORY, LangType, LogExtensionDomainEntity, MetaDocumentInputDTO, ONBOARDING_EXPIRY_DAYS, ONBOARDING_EXPIRY_MS, OptionalCurrentUser, OptionalGqlJwtAuthGuard, PaginationInputDTO, PaginationResult, PaginationUtils, PaymentProvider, PollingProvider, QuantityUtils, ROLES_ANY_STATUS_KEY, ROLES_DENY_SELF_KEY, RolesGuard, SignatureInfoDTO, SignatureInfoInputDTO, SignedDigitalDocumentDTO, SignedDigitalDocumentInputDTO, TransactionDTO, UseBucket, bucketProvidersFor, bucketTokenFor, buildPaginationResult, checkPaymentAmount, checkPaymentSymbol, computeOnboardingExpiresAt, configureExtensionAuth, configurePlatformSettings, createPaginationResult, extensionEntities, generateHashFromString, generateRandomHash, generateUniqueHash, getAmountPlusFee, getAppliedBlockNum, hasServerSecret, isExtensionAvailable, mergeSecretConfig, paginationInputToOffset, platformSettings, providerSuppliedPaths, redactSecretConfig, registerExtensionEntities, resetExtensionEntities, rethrowChainError, sanitizeUserText, verifySignedDocumentAgainstStoredDraft, waitAfterTransactBeforeChainTableRead };
2128
+ export { ActiveUserStatusGuard, AmountFormatterUtils, AssetUtils, AuthRoles, BaseExtensionModule, BucketRegistry, CandidateFilterInputDTO, CandidateOutputDTO, CandidateStatus, CurrencyValidationUtil, CurrentUser, DEFAULT_DOCUMENT_VERSION, DateUtils, DocumentAggregateDTO, DomainError, DomainToBlockchainUtils, EMPTY_HASH, EXTENSION_APP_TERMINATE_EVENT, EXTENSION_REPOSITORY, EXTENSION_SECRET_SET, EXTENSION_SECRET_UNSET, ExpenseProposalStatementGenerateDocumentInputDTO, ExpenseProposalStatementSignedDocumentInputDTO, ExpenseProposalStatementSignedMetaDocumentInputDTO, ExtensionAvailability, ExtensionConfigSuppliedBy, ExtensionDomainEntity, GenerateDocumentInputDTO, GenerateDocumentOptionsInputDTO, GenerateMetaDocumentInputDTO, GeneratedDocumentDTO, GqlJwtAuthGuard, HttpApiError, HttpJwtAuthGuard, IPNProvider, InjectBucket, LOG_EXTENSION_REPOSITORY, LangType, LogExtensionDomainEntity, MetaDocumentInputDTO, ONBOARDING_EXPIRY_DAYS, ONBOARDING_EXPIRY_MS, OptionalCurrentUser, OptionalGqlJwtAuthGuard, PaginationInputDTO, PaginationResult, PaginationUtils, PaymentProvider, PollingProvider, QuantityUtils, ROLES_ANY_STATUS_KEY, ROLES_DENY_SELF_KEY, RolesGuard, SORT_FIELD_PATTERN, SignatureInfoDTO, SignatureInfoInputDTO, SignedDigitalDocumentDTO, SignedDigitalDocumentInputDTO, TransactionDTO, UseBucket, bucketProvidersFor, bucketTokenFor, buildPaginationResult, chainErrorCode, chainTextDigest, checkPaymentAmount, checkPaymentSymbol, computeOnboardingExpiresAt, configureExtensionAuth, configurePlatformSettings, createPaginationResult, domainErrorMessage, extensionDatabaseMigrations, extensionEntities, generateHashFromString, generateRandomHash, generateUniqueHash, getAmountPlusFee, getAppliedBlockNum, hasServerSecret, isChainTextDigest, isExtensionAvailable, mergeSecretConfig, paginationInputToOffset, parseChainAssert, platformSettings, providerSuppliedPaths, redactSecretConfig, registerExtensionDatabaseMigrations, registerExtensionEntities, resetExtensionDatabaseMigrations, resetExtensionEntities, resolveChainText, resolveSortColumn, rethrowChainError, sanitizeUserText, validationMessage, verifySignedDocumentAgainstStoredDraft, waitForEvent };