@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.cjs CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  const common = require('@nestjs/common');
4
4
  const graphql = require('@nestjs/graphql');
5
+ const server = require('@coopenomics/i18n/server');
5
6
  const core = require('@nestjs/core');
6
7
  const passport = require('@nestjs/passport');
7
8
  const node_crypto = require('node:crypto');
@@ -12,16 +13,160 @@ const graphqlTypeJson = require('graphql-type-json');
12
13
  const sdk = require('@coopenomics/sdk');
13
14
  const antelope = require('@wharfkit/antelope');
14
15
  const moment = require('moment');
16
+ const crypto = require('crypto');
15
17
  const moment$1 = require('moment-timezone');
16
18
  require('moment/locale/ru');
17
- const crypto = require('crypto');
18
19
  const cooptypes = require('cooptypes');
19
20
 
20
21
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
21
22
 
22
23
  const moment__default = /*#__PURE__*/_interopDefaultCompat(moment);
23
- const moment__default$1 = /*#__PURE__*/_interopDefaultCompat(moment$1);
24
24
  const crypto__default = /*#__PURE__*/_interopDefaultCompat(crypto);
25
+ const moment__default$1 = /*#__PURE__*/_interopDefaultCompat(moment$1);
26
+
27
+ const DOMAIN_ERROR = Symbol.for("@coopenomics/extension-kit/DomainError");
28
+ function domainErrorMessage(code, params = {}) {
29
+ const key = `errors.${code}`;
30
+ if (!server.te(key) && typeof params.message === "string")
31
+ return params.message;
32
+ return server.t(key, params);
33
+ }
34
+ function body(code, params, status) {
35
+ return { statusCode: status, code, params, message: domainErrorMessage(code, params) };
36
+ }
37
+ function brand(target, code, params) {
38
+ target.code = code;
39
+ target.params = params;
40
+ target[DOMAIN_ERROR] = true;
41
+ target.name = "DomainError";
42
+ }
43
+ class DomainError extends common.HttpException {
44
+ static [Symbol.hasInstance](value) {
45
+ return !!value && value[DOMAIN_ERROR] === true;
46
+ }
47
+ constructor(code, params = {}, status = common.HttpStatus.BAD_REQUEST) {
48
+ super(body(code, params, status), status);
49
+ brand(this, code, params);
50
+ }
51
+ /** Ключ сообщения в словаре. */
52
+ get messageKey() {
53
+ return `errors.${this.code}`;
54
+ }
55
+ static badRequest(code, params = {}) {
56
+ return new DomainBadRequest(code, params);
57
+ }
58
+ static unauthorized(code, params = {}) {
59
+ return new DomainUnauthorized(code, params);
60
+ }
61
+ static forbidden(code, params = {}) {
62
+ return new DomainForbidden(code, params);
63
+ }
64
+ static notFound(code, params = {}) {
65
+ return new DomainNotFound(code, params);
66
+ }
67
+ static conflict(code, params = {}) {
68
+ return new DomainConflict(code, params);
69
+ }
70
+ static unprocessable(code, params = {}) {
71
+ return new DomainUnprocessable(code, params);
72
+ }
73
+ static tooManyRequests(code, params = {}) {
74
+ return new DomainError(code, params, common.HttpStatus.TOO_MANY_REQUESTS);
75
+ }
76
+ static internal(code, params = {}) {
77
+ return new DomainInternal(code, params);
78
+ }
79
+ static badGateway(code, params = {}) {
80
+ return new DomainBadGateway(code, params);
81
+ }
82
+ static serviceUnavailable(code, params = {}) {
83
+ return new DomainServiceUnavailable(code, params);
84
+ }
85
+ }
86
+ class DomainBadGateway extends common.BadGatewayException {
87
+ constructor(code, params) {
88
+ super(body(code, params, common.HttpStatus.BAD_GATEWAY));
89
+ brand(this, code, params);
90
+ }
91
+ get messageKey() {
92
+ return `errors.${this.code}`;
93
+ }
94
+ }
95
+ class DomainServiceUnavailable extends common.ServiceUnavailableException {
96
+ constructor(code, params) {
97
+ super(body(code, params, common.HttpStatus.SERVICE_UNAVAILABLE));
98
+ brand(this, code, params);
99
+ }
100
+ get messageKey() {
101
+ return `errors.${this.code}`;
102
+ }
103
+ }
104
+ class DomainBadRequest extends common.BadRequestException {
105
+ constructor(code, params) {
106
+ super(body(code, params, common.HttpStatus.BAD_REQUEST));
107
+ brand(this, code, params);
108
+ }
109
+ get messageKey() {
110
+ return `errors.${this.code}`;
111
+ }
112
+ }
113
+ class DomainUnauthorized extends common.UnauthorizedException {
114
+ constructor(code, params) {
115
+ super(body(code, params, common.HttpStatus.UNAUTHORIZED));
116
+ brand(this, code, params);
117
+ }
118
+ get messageKey() {
119
+ return `errors.${this.code}`;
120
+ }
121
+ }
122
+ class DomainForbidden extends common.ForbiddenException {
123
+ constructor(code, params) {
124
+ super(body(code, params, common.HttpStatus.FORBIDDEN));
125
+ brand(this, code, params);
126
+ }
127
+ get messageKey() {
128
+ return `errors.${this.code}`;
129
+ }
130
+ }
131
+ class DomainNotFound extends common.NotFoundException {
132
+ constructor(code, params) {
133
+ super(body(code, params, common.HttpStatus.NOT_FOUND));
134
+ brand(this, code, params);
135
+ }
136
+ get messageKey() {
137
+ return `errors.${this.code}`;
138
+ }
139
+ }
140
+ class DomainConflict extends common.ConflictException {
141
+ constructor(code, params) {
142
+ super(body(code, params, common.HttpStatus.CONFLICT));
143
+ brand(this, code, params);
144
+ }
145
+ get messageKey() {
146
+ return `errors.${this.code}`;
147
+ }
148
+ }
149
+ class DomainUnprocessable extends common.UnprocessableEntityException {
150
+ constructor(code, params) {
151
+ super(body(code, params, common.HttpStatus.UNPROCESSABLE_ENTITY));
152
+ brand(this, code, params);
153
+ }
154
+ get messageKey() {
155
+ return `errors.${this.code}`;
156
+ }
157
+ }
158
+ class DomainInternal extends common.InternalServerErrorException {
159
+ constructor(code, params) {
160
+ super(body(code, params, common.HttpStatus.INTERNAL_SERVER_ERROR));
161
+ brand(this, code, params);
162
+ }
163
+ get messageKey() {
164
+ return `errors.${this.code}`;
165
+ }
166
+ }
167
+ function validationMessage(key, params = {}) {
168
+ return (args) => server.t(key, { property: args.property, value: String(args.value ?? ""), ...params });
169
+ }
25
170
 
26
171
  const ROLES_DENY_SELF_KEY = "roles_deny_self";
27
172
  const ROLES_ANY_STATUS_KEY = "roles_any_status";
@@ -39,7 +184,7 @@ const CurrentUser = common.createParamDecorator((data, context) => {
39
184
  const ctx = graphql.GqlExecutionContext.create(context);
40
185
  const request = ctx.getContext().req;
41
186
  if (!request?.user) {
42
- throw new common.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");
187
+ throw DomainError.unauthorized("KIT_USER_NOT_AUTHORIZED");
43
188
  }
44
189
  return request?.user;
45
190
  });
@@ -122,7 +267,7 @@ exports.OptionalGqlJwtAuthGuard = class OptionalGqlJwtAuthGuard extends passport
122
267
  if (err instanceof Error) {
123
268
  throw err;
124
269
  }
125
- throw new common.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");
270
+ throw DomainError.unauthorized("KIT_SESSION_ENDED");
126
271
  }
127
272
  };
128
273
  exports.OptionalGqlJwtAuthGuard = __decorateClass$g([
@@ -172,11 +317,11 @@ exports.RolesGuard = class RolesGuard {
172
317
  if (allowedRoles.includes(user.role)) {
173
318
  const anyStatus = this.reflector.get(ROLES_ANY_STATUS_KEY, context.getHandler()) === true;
174
319
  if (user.role === PARTICIPANT_ROLE && !anyStatus && user.status !== ACTIVE_USER_STATUS) {
175
- throw new common.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");
320
+ throw DomainError.forbidden("KIT_MEMBERS_ONLY");
176
321
  }
177
322
  return true;
178
323
  }
179
- throw new common.UnauthorizedException(`\u041D\u0435\u0434\u043E\u0441\u0442\u0430\u0442\u043E\u0447\u043D\u043E \u043F\u0440\u0430\u0432 \u0434\u043E\u0441\u0442\u0443\u043F\u0430`);
324
+ throw DomainError.unauthorized("KIT_INSUFFICIENT_RIGHTS");
180
325
  }
181
326
  };
182
327
  exports.RolesGuard = __decorateClass$g([
@@ -192,12 +337,10 @@ exports.ActiveUserStatusGuard = class ActiveUserStatusGuard {
192
337
  }
193
338
  const user = request.user;
194
339
  if (!user?.status) {
195
- throw new common.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");
340
+ throw DomainError.forbidden("KIT_AUTHORIZED_USER_REQUIRED");
196
341
  }
197
342
  if (user.status !== ACTIVE_USER_STATUS) {
198
- throw new common.ForbiddenException(
199
- "\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"
200
- );
343
+ throw DomainError.forbidden("KIT_ACTIVE_MEMBERS_ONLY");
201
344
  }
202
345
  return true;
203
346
  }
@@ -262,6 +405,7 @@ const BucketRegistry = {
262
405
  const existing = REGISTERED.get(cls);
263
406
  if (existing && existing.name !== spec.name) {
264
407
  throw new Error(
408
+ // i18n-ignore: ошибка регистрации бакета при старте, до пайщика не доходит
265
409
  `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`
266
410
  );
267
411
  }
@@ -298,6 +442,7 @@ function bucketProvidersFor(fileStoragePortToken, consumers) {
298
442
  const spec = BucketRegistry.get(cls);
299
443
  if (!spec) {
300
444
  throw new Error(
445
+ // i18n-ignore: ошибка объявления хранилища при старте, до пайщика не доходит
301
446
  `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`
302
447
  );
303
448
  }
@@ -655,7 +800,7 @@ __decorateClass$a([
655
800
  graphql.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" }),
656
801
  classValidator.IsString(),
657
802
  classValidator.Matches(/^\d{2}\.\d{2}\.\d{4}$/, {
658
- 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"
803
+ message: validationMessage("kit.expenseStatement.dueDateFormat")
659
804
  })
660
805
  ], ExpenseProposalHeaderInputDTO.prototype, "deadline", 2);
661
806
  __decorateClass$a([
@@ -882,6 +1027,7 @@ let entities;
882
1027
  function registerExtensionEntities(list) {
883
1028
  if (entities) {
884
1029
  throw new Error(
1030
+ // i18n-ignore: ошибка сборки приложения при старте, до пайщика не доходит
885
1031
  "\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"
886
1032
  );
887
1033
  }
@@ -894,6 +1040,23 @@ function resetExtensionEntities() {
894
1040
  entities = void 0;
895
1041
  }
896
1042
 
1043
+ let migrations;
1044
+ function registerExtensionDatabaseMigrations(list) {
1045
+ if (migrations) {
1046
+ throw new Error(
1047
+ // i18n-ignore: ошибка сборки composition root для разработчика, до пайщика не доходит
1048
+ "\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"
1049
+ );
1050
+ }
1051
+ migrations = [...list];
1052
+ }
1053
+ function extensionDatabaseMigrations() {
1054
+ return migrations ?? [];
1055
+ }
1056
+ function resetExtensionDatabaseMigrations() {
1057
+ migrations = void 0;
1058
+ }
1059
+
897
1060
  var __defProp$7 = Object.defineProperty;
898
1061
  var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor;
899
1062
  var __decorateClass$7 = (decorators, target, key, kind) => {
@@ -905,6 +1068,7 @@ var __decorateClass$7 = (decorators, target, key, kind) => {
905
1068
  __defProp$7(target, key, result);
906
1069
  return result;
907
1070
  };
1071
+ const SORT_FIELD_PATTERN = /^([A-Za-z_][A-Za-z0-9_]*(:(asc|desc))?)?$/i;
908
1072
  exports.PaginationInputDTO = class PaginationInputDTO {
909
1073
  };
910
1074
  __decorateClass$7([
@@ -914,7 +1078,9 @@ __decorateClass$7([
914
1078
  graphql.Field(() => graphql.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 })
915
1079
  ], exports.PaginationInputDTO.prototype, "limit", 2);
916
1080
  __decorateClass$7([
917
- graphql.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")' })
1081
+ graphql.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")' }),
1082
+ classValidator.IsOptional(),
1083
+ classValidator.Matches(SORT_FIELD_PATTERN, { message: validationMessage("kit.pagination.invalidSortField") })
918
1084
  ], exports.PaginationInputDTO.prototype, "sortBy", 2);
919
1085
  __decorateClass$7([
920
1086
  graphql.Field(() => String, {
@@ -988,13 +1154,16 @@ class PaginationUtils {
988
1154
  static validatePaginationOptions(options) {
989
1155
  const { page = 1, limit = 10, sortBy, sortOrder = "ASC" } = options;
990
1156
  if (page < 1) {
991
- 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");
1157
+ throw DomainError.internal("KIT_PAGE_NUMBER_INVALID");
992
1158
  }
993
1159
  if (limit < 1 || limit > 1e3) {
994
- 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");
1160
+ throw DomainError.internal("KIT_PAGE_LIMIT_INVALID");
995
1161
  }
996
1162
  if (sortOrder !== "ASC" && sortOrder !== "DESC") {
997
- 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");
1163
+ throw DomainError.internal("KIT_SORT_ORDER_INVALID");
1164
+ }
1165
+ if (sortBy !== void 0 && sortBy !== null && !SORT_FIELD_PATTERN.test(sortBy)) {
1166
+ throw DomainError.internal("KIT_SORT_FIELD_INVALID");
998
1167
  }
999
1168
  return {
1000
1169
  page,
@@ -1124,6 +1293,7 @@ function configurePlatformSettings(value) {
1124
1293
  function platformSettings() {
1125
1294
  if (!settings) {
1126
1295
  throw new Error(
1296
+ // i18n-ignore: ошибка сборки приложения при старте, до пайщика не доходит
1127
1297
  "\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"
1128
1298
  );
1129
1299
  }
@@ -1341,19 +1511,6 @@ exports.DocumentAggregateDTO = __decorateClass$1([
1341
1511
  graphql.ObjectType("DocumentAggregate")
1342
1512
  ], exports.DocumentAggregateDTO);
1343
1513
 
1344
- class HttpApiError extends common.HttpException {
1345
- constructor(statusCode, message, isOperational = true, stack, subcode) {
1346
- super(message, statusCode);
1347
- this.isOperational = isOperational;
1348
- this.subcode = subcode;
1349
- if (stack) {
1350
- this.stack = stack;
1351
- } else {
1352
- Error.captureStackTrace(this, this.constructor);
1353
- }
1354
- }
1355
- }
1356
-
1357
1514
  class CurrencyValidationUtil {
1358
1515
  /**
1359
1516
  * Проверяет, что сумма содержит правильный символ валюты
@@ -1373,10 +1530,10 @@ class CurrencyValidationUtil {
1373
1530
  * @param fieldName Название поля для сообщения об ошибке (по умолчанию "сумма")
1374
1531
  * @throws Error если символ валюты неправильный
1375
1532
  */
1376
- static validateCurrencySymbol(amount, fieldName = "\u0441\u0443\u043C\u043C\u0430") {
1533
+ static validateCurrencySymbol(amount, fieldName = server.t("kit.currency.defaultFieldName")) {
1377
1534
  if (!this.hasValidCurrencySymbol(amount)) {
1378
1535
  const expectedSymbol = platformSettings().blockchain.rootGovernSymbol;
1379
- 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}`);
1536
+ throw DomainError.internal("KIT_CURRENCY_SYMBOL_INVALID", { fieldName, expectedSymbol });
1380
1537
  }
1381
1538
  }
1382
1539
  /**
@@ -1407,12 +1564,13 @@ class CurrencyValidationUtil {
1407
1564
  /**
1408
1565
  * Форматирует сумму с правильным символом валюты
1409
1566
  * @param value Числовое значение
1410
- * @param precision Количество знаков после запятой (по умолчанию из конфига)
1567
+ * @param precision Количество знаков после запятой (по умолчанию из настроек платформы)
1411
1568
  * @returns string - отформатированная сумма
1412
1569
  */
1413
1570
  static formatAmount(value, precision) {
1414
- const actualPrecision = precision ?? config.blockchain.root_govern_precision;
1415
- const symbol = platformSettings().blockchain.rootGovernSymbol;
1571
+ const { rootGovernPrecision, rootGovernSymbol } = platformSettings().blockchain;
1572
+ const actualPrecision = precision ?? rootGovernPrecision;
1573
+ const symbol = rootGovernSymbol;
1416
1574
  return `${value.toFixed(actualPrecision)} ${symbol}`;
1417
1575
  }
1418
1576
  }
@@ -1420,18 +1578,12 @@ class CurrencyValidationUtil {
1420
1578
  async function verifySignedDocumentAgainstStoredDraft(loadGeneratedByDocHash, signed, metaVerifications) {
1421
1579
  const generated = await loadGeneratedByDocHash(signed.doc_hash);
1422
1580
  if (!generated) {
1423
- throw new HttpApiError(
1424
- common.HttpStatus.BAD_REQUEST,
1425
- `\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.`
1426
- );
1581
+ throw DomainError.badRequest("KIT_GENERATED_DOCUMENT_NOT_FOUND", { docHash: signed.doc_hash });
1427
1582
  }
1428
1583
  const comparison = await sdk.Classes.Document.compareDocuments(signed, generated);
1429
1584
  if (!comparison.isValid) {
1430
- 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("; ");
1431
- throw new HttpApiError(
1432
- common.HttpStatus.BAD_REQUEST,
1433
- `\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.`
1434
- );
1585
+ const differences = Object.entries(comparison.differences).map(([field, values]) => server.t("kit.documentVerification.fieldDifference", { field, expected: values.expected, actual: values.actual })).join("; ");
1586
+ throw DomainError.badRequest("KIT_DOCUMENT_DRAFT_MISMATCH", { differences });
1435
1587
  }
1436
1588
  if (!metaVerifications || metaVerifications.length === 0) {
1437
1589
  return;
@@ -1441,10 +1593,7 @@ async function verifySignedDocumentAgainstStoredDraft(loadGeneratedByDocHash, si
1441
1593
  const raw = meta[field];
1442
1594
  const actualStr = typeof raw === "string" ? raw.trim() : raw !== void 0 && raw !== null ? String(raw).trim() : "";
1443
1595
  if (!actualStr) {
1444
- throw new HttpApiError(
1445
- common.HttpStatus.BAD_REQUEST,
1446
- `\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.`
1447
- );
1596
+ throw DomainError.badRequest("KIT_DOCUMENT_META_FIELD_MISSING", { field });
1448
1597
  }
1449
1598
  const expectedTrimmed = expected.trim();
1450
1599
  switch (mode) {
@@ -1452,39 +1601,27 @@ async function verifySignedDocumentAgainstStoredDraft(loadGeneratedByDocHash, si
1452
1601
  const parsedExpected = CurrencyValidationUtil.extractAmountValue(expectedTrimmed);
1453
1602
  const parsedActual = CurrencyValidationUtil.extractAmountValue(actualStr);
1454
1603
  if (Number.isNaN(parsedExpected) || Number.isNaN(parsedActual)) {
1455
- throw new HttpApiError(
1456
- common.HttpStatus.BAD_REQUEST,
1457
- `\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.`
1458
- );
1604
+ throw DomainError.badRequest("KIT_DOCUMENT_AMOUNT_FORMAT_INVALID", { field });
1459
1605
  }
1460
1606
  if (CurrencyValidationUtil.formatAmount(parsedExpected) !== CurrencyValidationUtil.formatAmount(parsedActual)) {
1461
- throw new HttpApiError(
1462
- common.HttpStatus.BAD_REQUEST,
1463
- `\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.`
1464
- );
1607
+ throw DomainError.badRequest("KIT_DOCUMENT_FIELD_MISMATCH", { field, actual: actualStr, expected: expectedTrimmed });
1465
1608
  }
1466
1609
  break;
1467
1610
  }
1468
1611
  case "string_trim": {
1469
1612
  if (actualStr !== expectedTrimmed) {
1470
- throw new HttpApiError(
1471
- common.HttpStatus.BAD_REQUEST,
1472
- `\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.`
1473
- );
1613
+ throw DomainError.badRequest("KIT_DOCUMENT_FIELD_MISMATCH", { field, actual: actualStr, expected: expectedTrimmed });
1474
1614
  }
1475
1615
  break;
1476
1616
  }
1477
1617
  case "hex_case_insensitive": {
1478
1618
  if (actualStr.toLowerCase() !== expectedTrimmed.toLowerCase()) {
1479
- throw new HttpApiError(
1480
- common.HttpStatus.BAD_REQUEST,
1481
- `\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.`
1482
- );
1619
+ throw DomainError.badRequest("KIT_DOCUMENT_FIELD_MISMATCH", { field, actual: actualStr, expected: expectedTrimmed });
1483
1620
  }
1484
1621
  break;
1485
1622
  }
1486
1623
  default:
1487
- throw new HttpApiError(common.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)}`);
1624
+ throw DomainError.internal("KIT_DOCUMENT_COMPARE_MODE_UNKNOWN", { mode: String(mode) });
1488
1625
  }
1489
1626
  }
1490
1627
  }
@@ -1541,12 +1678,12 @@ exports.DomainToBlockchainUtils = class DomainToBlockchainUtils {
1541
1678
  formatQuantityWithPrecision(quantity) {
1542
1679
  const parts = quantity.split(" ");
1543
1680
  if (parts.length !== 2) {
1544
- 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"`);
1681
+ throw DomainError.internal("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1545
1682
  }
1546
1683
  const [amount, symbol] = parts;
1547
1684
  const numericAmount = parseFloat(amount);
1548
1685
  if (isNaN(numericAmount)) {
1549
- 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}`);
1686
+ throw DomainError.internal("KIT_QUANTITY_AMOUNT_INVALID", { amount });
1550
1687
  }
1551
1688
  const { rootSymbol, rootPrecision, rootGovernSymbol, rootGovernPrecision } = platformSettings().blockchain;
1552
1689
  let precision;
@@ -1555,7 +1692,7 @@ exports.DomainToBlockchainUtils = class DomainToBlockchainUtils {
1555
1692
  } else if (symbol === rootGovernSymbol) {
1556
1693
  precision = rootGovernPrecision;
1557
1694
  } else {
1558
- 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}`);
1695
+ throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1559
1696
  }
1560
1697
  return `${numericAmount.toFixed(precision)} ${symbol}`;
1561
1698
  }
@@ -1563,10 +1700,10 @@ exports.DomainToBlockchainUtils = class DomainToBlockchainUtils {
1563
1700
  formatNumericStringToAssetString(numericString, precision, symbol) {
1564
1701
  const numericValue = parseFloat(numericString);
1565
1702
  if (isNaN(numericValue)) {
1566
- 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}`);
1703
+ throw DomainError.internal("KIT_NUMBER_INVALID", { value: numericString });
1567
1704
  }
1568
1705
  if (numericValue < 0) {
1569
- 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}`);
1706
+ throw DomainError.internal("KIT_NUMBER_NEGATIVE", { value: numericString });
1570
1707
  }
1571
1708
  return `${numericValue.toFixed(precision)} ${symbol}`;
1572
1709
  }
@@ -1597,6 +1734,19 @@ exports.DomainToBlockchainUtils = __decorateClass([
1597
1734
  common.Injectable()
1598
1735
  ], exports.DomainToBlockchainUtils);
1599
1736
 
1737
+ const DIGEST_RE = /^[0-9a-f]{64}$/;
1738
+ function chainTextDigest(text) {
1739
+ if (!text)
1740
+ return "";
1741
+ return crypto.createHash("sha256").update(text, "utf8").digest("hex");
1742
+ }
1743
+ function isChainTextDigest(value) {
1744
+ return typeof value === "string" && DIGEST_RE.test(value);
1745
+ }
1746
+ function resolveChainText(chainValue, current) {
1747
+ return isChainTextDigest(chainValue) ? current ?? "" : chainValue;
1748
+ }
1749
+
1600
1750
  class AssetUtils {
1601
1751
  /**
1602
1752
  * Парсит строку ассета и возвращает числовое значение и символ
@@ -1683,17 +1833,17 @@ class AmountFormatterUtils {
1683
1833
  */
1684
1834
  static formatAmount(amountStr) {
1685
1835
  if (!amountStr || typeof amountStr !== "string") {
1686
- 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"`);
1836
+ throw DomainError.internal("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1687
1837
  }
1688
1838
  const parts = amountStr.trim().split(/\s+/);
1689
1839
  if (parts.length < 1 || !parts[0]) {
1690
- 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"`);
1840
+ throw DomainError.internal("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1691
1841
  }
1692
1842
  const amountPart = parts[0].replace(",", ".");
1693
1843
  const currency = parts.length >= 2 ? parts.slice(1).join(" ") : "";
1694
1844
  const amount = parseFloat(amountPart);
1695
1845
  if (isNaN(amount)) {
1696
- 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]}`);
1846
+ throw DomainError.internal("KIT_AMOUNT_NUMBER_INVALID", { value: parts[0] });
1697
1847
  }
1698
1848
  const formattedAmount = amount.toLocaleString("ru-RU", {
1699
1849
  minimumFractionDigits: 2,
@@ -1738,7 +1888,7 @@ function checkPaymentAmount(incomeAmount, expectedAmount, tolerancePercentage) {
1738
1888
  if (incomeAmount < expectedAmount - tolerance) {
1739
1889
  return {
1740
1890
  status: "error",
1741
- 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}`
1891
+ message: server.t("kit.payments.insufficientFunds", { income: incomeAmount, expected: expectedAmount })
1742
1892
  };
1743
1893
  }
1744
1894
  return {
@@ -1769,7 +1919,7 @@ class QuantityUtils {
1769
1919
  } else if (symbol === rootGovernSymbol) {
1770
1920
  return rootGovernPrecision;
1771
1921
  } else {
1772
- 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}`);
1922
+ throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1773
1923
  }
1774
1924
  }
1775
1925
  /**
@@ -1779,7 +1929,7 @@ class QuantityUtils {
1779
1929
  static validateSymbol(symbol) {
1780
1930
  if (!this.isSupportedSymbol(symbol)) {
1781
1931
  const { rootSymbol, rootGovernSymbol } = platformSettings().blockchain;
1782
- 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}`);
1932
+ throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1783
1933
  }
1784
1934
  }
1785
1935
  /**
@@ -1791,7 +1941,7 @@ class QuantityUtils {
1791
1941
  static formatQuantityForBlockchain(amount, symbol) {
1792
1942
  this.validateSymbol(symbol);
1793
1943
  if (isNaN(amount) || amount < 0) {
1794
- 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}`);
1944
+ throw DomainError.internal("KIT_NUMBER_INVALID", { value: amount });
1795
1945
  }
1796
1946
  const precision = this.getPrecisionForSymbol(symbol);
1797
1947
  const formattedAmount = amount.toFixed(precision);
@@ -1806,7 +1956,7 @@ class QuantityUtils {
1806
1956
  static combineQuantityAndSymbol(amount, symbol) {
1807
1957
  this.validateSymbol(symbol);
1808
1958
  if (isNaN(amount) || amount < 0) {
1809
- 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}`);
1959
+ throw DomainError.internal("KIT_NUMBER_INVALID", { value: amount });
1810
1960
  }
1811
1961
  return `${amount} ${symbol}`;
1812
1962
  }
@@ -1818,12 +1968,12 @@ class QuantityUtils {
1818
1968
  static parseQuantityString(quantity) {
1819
1969
  const parts = quantity.split(" ");
1820
1970
  if (parts.length !== 2) {
1821
- 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"`);
1971
+ throw DomainError.internal("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1822
1972
  }
1823
1973
  const [amountStr, symbol] = parts;
1824
1974
  const amount = parseFloat(amountStr);
1825
1975
  if (isNaN(amount)) {
1826
- 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}`);
1976
+ throw DomainError.internal("KIT_QUANTITY_AMOUNT_INVALID", { amount: amountStr });
1827
1977
  }
1828
1978
  this.validateSymbol(symbol);
1829
1979
  return { amount, symbol };
@@ -1886,14 +2036,29 @@ class DateUtils {
1886
2036
  }
1887
2037
  }
1888
2038
 
1889
- async function waitAfterTransactBeforeChainTableRead() {
1890
- const ms = platformSettings().blockchain.postTransactChainReadDelayMs;
1891
- if (typeof ms !== "number" || !Number.isFinite(ms) || ms <= 0) {
1892
- return;
1893
- }
1894
- await new Promise((resolve) => {
1895
- setTimeout(resolve, ms);
2039
+ function waitForEvent(source, event, match, timeoutMs) {
2040
+ let settle;
2041
+ const promise = new Promise((resolve) => {
2042
+ settle = resolve;
1896
2043
  });
2044
+ let done = false;
2045
+ let timer;
2046
+ const listener = (payload) => {
2047
+ if (match(payload))
2048
+ finish(payload);
2049
+ };
2050
+ function finish(value) {
2051
+ if (done)
2052
+ return;
2053
+ done = true;
2054
+ if (timer)
2055
+ clearTimeout(timer);
2056
+ source.off(event, listener);
2057
+ settle(value);
2058
+ }
2059
+ source.on(event, listener);
2060
+ timer = setTimeout(() => finish(null), timeoutMs);
2061
+ return { promise, cancel: () => finish(null) };
1897
2062
  }
1898
2063
 
1899
2064
  function getAppliedBlockNum(transactResult) {
@@ -1920,11 +2085,52 @@ function sanitizeUserText(value) {
1920
2085
  return cooptypes._Common.Text.sanitizeEditorMarkdown(value);
1921
2086
  }
1922
2087
 
2088
+ function resolveSortColumn(repository, sortBy, fallback) {
2089
+ if (!sortBy)
2090
+ return fallback;
2091
+ const isKnownColumn = repository.metadata.columns.some(
2092
+ (column) => column.propertyName === sortBy || column.databaseName === sortBy
2093
+ );
2094
+ return isKnownColumn ? sortBy : fallback;
2095
+ }
2096
+
2097
+ class HttpApiError extends common.HttpException {
2098
+ constructor(statusCode, message, isOperational = true, stack, subcode) {
2099
+ super(message, statusCode);
2100
+ this.isOperational = isOperational;
2101
+ this.subcode = subcode;
2102
+ if (stack) {
2103
+ this.stack = stack;
2104
+ } else {
2105
+ Error.captureStackTrace(this, this.constructor);
2106
+ }
2107
+ }
2108
+ }
2109
+
2110
+ const ASSERT_PREFIX = /assertion failure with message: (.+?)(?:\n|$)/;
2111
+ const CODED = /^([A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+):\s*(.*)$/s;
2112
+ function parseChainAssert(message) {
2113
+ const assert = message.match(ASSERT_PREFIX);
2114
+ const body = (assert ? assert[1] : message).trim();
2115
+ const coded = body.match(CODED);
2116
+ return coded ? { code: coded[1], text: coded[2].trim() } : { text: body };
2117
+ }
2118
+ function chainErrorCode(error) {
2119
+ if (error instanceof DomainError) {
2120
+ if (error.code !== "CHAIN_ASSERT" && error.code !== "CHAIN_ERROR")
2121
+ return error.code;
2122
+ return typeof error.params.message === "string" ? parseChainAssert(error.params.message).code : void 0;
2123
+ }
2124
+ const raw = error?.message ?? String(error);
2125
+ return parseChainAssert(raw).code;
2126
+ }
1923
2127
  function rethrowChainError(error) {
1924
2128
  const raw = error?.message ?? String(error);
1925
- const match = raw.match(/assertion failure with message: (.+?)(?:\n|$)/);
1926
- const clean = match ? match[1].trim() : raw;
1927
- throw new common.BadRequestException(clean);
2129
+ const match = raw.match(ASSERT_PREFIX);
2130
+ if (!match)
2131
+ throw DomainError.badRequest("CHAIN_ERROR", { message: raw });
2132
+ const { code, text } = parseChainAssert(raw);
2133
+ throw DomainError.badRequest(code ?? "CHAIN_ASSERT", { message: text });
1928
2134
  }
1929
2135
 
1930
2136
  exports.AmountFormatterUtils = AmountFormatterUtils;
@@ -1936,6 +2142,7 @@ exports.CurrencyValidationUtil = CurrencyValidationUtil;
1936
2142
  exports.CurrentUser = CurrentUser;
1937
2143
  exports.DEFAULT_DOCUMENT_VERSION = DEFAULT_DOCUMENT_VERSION;
1938
2144
  exports.DateUtils = DateUtils;
2145
+ exports.DomainError = DomainError;
1939
2146
  exports.EMPTY_HASH = EMPTY_HASH;
1940
2147
  exports.EXTENSION_APP_TERMINATE_EVENT = EXTENSION_APP_TERMINATE_EVENT;
1941
2148
  exports.EXTENSION_REPOSITORY = EXTENSION_REPOSITORY;
@@ -1957,16 +2164,21 @@ exports.PaginationUtils = PaginationUtils;
1957
2164
  exports.QuantityUtils = QuantityUtils;
1958
2165
  exports.ROLES_ANY_STATUS_KEY = ROLES_ANY_STATUS_KEY;
1959
2166
  exports.ROLES_DENY_SELF_KEY = ROLES_DENY_SELF_KEY;
2167
+ exports.SORT_FIELD_PATTERN = SORT_FIELD_PATTERN;
1960
2168
  exports.UseBucket = UseBucket;
1961
2169
  exports.bucketProvidersFor = bucketProvidersFor;
1962
2170
  exports.bucketTokenFor = bucketTokenFor;
1963
2171
  exports.buildPaginationResult = buildPaginationResult;
2172
+ exports.chainErrorCode = chainErrorCode;
2173
+ exports.chainTextDigest = chainTextDigest;
1964
2174
  exports.checkPaymentAmount = checkPaymentAmount;
1965
2175
  exports.checkPaymentSymbol = checkPaymentSymbol;
1966
2176
  exports.computeOnboardingExpiresAt = computeOnboardingExpiresAt;
1967
2177
  exports.configureExtensionAuth = configureExtensionAuth;
1968
2178
  exports.configurePlatformSettings = configurePlatformSettings;
1969
2179
  exports.createPaginationResult = createPaginationResult;
2180
+ exports.domainErrorMessage = domainErrorMessage;
2181
+ exports.extensionDatabaseMigrations = extensionDatabaseMigrations;
1970
2182
  exports.extensionEntities = extensionEntities;
1971
2183
  exports.generateHashFromString = generateHashFromString;
1972
2184
  exports.generateRandomHash = generateRandomHash;
@@ -1974,15 +2186,22 @@ exports.generateUniqueHash = generateUniqueHash;
1974
2186
  exports.getAmountPlusFee = getAmountPlusFee;
1975
2187
  exports.getAppliedBlockNum = getAppliedBlockNum;
1976
2188
  exports.hasServerSecret = hasServerSecret;
2189
+ exports.isChainTextDigest = isChainTextDigest;
1977
2190
  exports.isExtensionAvailable = isExtensionAvailable;
1978
2191
  exports.mergeSecretConfig = mergeSecretConfig;
1979
2192
  exports.paginationInputToOffset = paginationInputToOffset;
2193
+ exports.parseChainAssert = parseChainAssert;
1980
2194
  exports.platformSettings = platformSettings;
1981
2195
  exports.providerSuppliedPaths = providerSuppliedPaths;
1982
2196
  exports.redactSecretConfig = redactSecretConfig;
2197
+ exports.registerExtensionDatabaseMigrations = registerExtensionDatabaseMigrations;
1983
2198
  exports.registerExtensionEntities = registerExtensionEntities;
2199
+ exports.resetExtensionDatabaseMigrations = resetExtensionDatabaseMigrations;
1984
2200
  exports.resetExtensionEntities = resetExtensionEntities;
2201
+ exports.resolveChainText = resolveChainText;
2202
+ exports.resolveSortColumn = resolveSortColumn;
1985
2203
  exports.rethrowChainError = rethrowChainError;
1986
2204
  exports.sanitizeUserText = sanitizeUserText;
2205
+ exports.validationMessage = validationMessage;
1987
2206
  exports.verifySignedDocumentAgainstStoredDraft = verifySignedDocumentAgainstStoredDraft;
1988
- exports.waitAfterTransactBeforeChainTableRead = waitAfterTransactBeforeChainTableRead;
2207
+ exports.waitForEvent = waitForEvent;