@coopenomics/extension-kit 2026.9.24-3 → 2026.9.30

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
@@ -1086,7 +1086,9 @@ __decorateClass$7([
1086
1086
  graphql.Field(() => String, {
1087
1087
  description: '\u041D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u0441\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u043A\u0438 ("ASC" \u0438\u043B\u0438 "DESC")',
1088
1088
  defaultValue: "ASC"
1089
- })
1089
+ }),
1090
+ classValidator.IsOptional(),
1091
+ classValidator.IsIn(["ASC", "DESC"], { message: validationMessage("kit.pagination.invalidSortOrder") })
1090
1092
  ], exports.PaginationInputDTO.prototype, "sortOrder", 2);
1091
1093
  exports.PaginationInputDTO = __decorateClass$7([
1092
1094
  graphql.InputType("PaginationInput")
@@ -1154,16 +1156,16 @@ class PaginationUtils {
1154
1156
  static validatePaginationOptions(options) {
1155
1157
  const { page = 1, limit = 10, sortBy, sortOrder = "ASC" } = options;
1156
1158
  if (page < 1) {
1157
- throw DomainError.internal("KIT_PAGE_NUMBER_INVALID");
1159
+ throw DomainError.badRequest("KIT_PAGE_NUMBER_INVALID");
1158
1160
  }
1159
1161
  if (limit < 1 || limit > 1e3) {
1160
- throw DomainError.internal("KIT_PAGE_LIMIT_INVALID");
1162
+ throw DomainError.badRequest("KIT_PAGE_LIMIT_INVALID");
1161
1163
  }
1162
1164
  if (sortOrder !== "ASC" && sortOrder !== "DESC") {
1163
- throw DomainError.internal("KIT_SORT_ORDER_INVALID");
1165
+ throw DomainError.badRequest("KIT_SORT_ORDER_INVALID");
1164
1166
  }
1165
1167
  if (sortBy !== void 0 && sortBy !== null && !SORT_FIELD_PATTERN.test(sortBy)) {
1166
- throw DomainError.internal("KIT_SORT_FIELD_INVALID");
1168
+ throw DomainError.badRequest("KIT_SORT_FIELD_INVALID");
1167
1169
  }
1168
1170
  return {
1169
1171
  page,
@@ -1533,7 +1535,7 @@ class CurrencyValidationUtil {
1533
1535
  static validateCurrencySymbol(amount, fieldName = server.t("kit.currency.defaultFieldName")) {
1534
1536
  if (!this.hasValidCurrencySymbol(amount)) {
1535
1537
  const expectedSymbol = platformSettings().blockchain.rootGovernSymbol;
1536
- throw DomainError.internal("KIT_CURRENCY_SYMBOL_INVALID", { fieldName, expectedSymbol });
1538
+ throw DomainError.badRequest("KIT_CURRENCY_SYMBOL_INVALID", { fieldName, expectedSymbol });
1537
1539
  }
1538
1540
  }
1539
1541
  /**
@@ -1678,12 +1680,12 @@ exports.DomainToBlockchainUtils = class DomainToBlockchainUtils {
1678
1680
  formatQuantityWithPrecision(quantity) {
1679
1681
  const parts = quantity.split(" ");
1680
1682
  if (parts.length !== 2) {
1681
- throw DomainError.internal("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1683
+ throw DomainError.badRequest("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1682
1684
  }
1683
1685
  const [amount, symbol] = parts;
1684
1686
  const numericAmount = parseFloat(amount);
1685
1687
  if (isNaN(numericAmount)) {
1686
- throw DomainError.internal("KIT_QUANTITY_AMOUNT_INVALID", { amount });
1688
+ throw DomainError.badRequest("KIT_QUANTITY_AMOUNT_INVALID", { amount });
1687
1689
  }
1688
1690
  const { rootSymbol, rootPrecision, rootGovernSymbol, rootGovernPrecision } = platformSettings().blockchain;
1689
1691
  let precision;
@@ -1692,7 +1694,7 @@ exports.DomainToBlockchainUtils = class DomainToBlockchainUtils {
1692
1694
  } else if (symbol === rootGovernSymbol) {
1693
1695
  precision = rootGovernPrecision;
1694
1696
  } else {
1695
- throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1697
+ throw DomainError.badRequest("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1696
1698
  }
1697
1699
  return `${numericAmount.toFixed(precision)} ${symbol}`;
1698
1700
  }
@@ -1700,10 +1702,10 @@ exports.DomainToBlockchainUtils = class DomainToBlockchainUtils {
1700
1702
  formatNumericStringToAssetString(numericString, precision, symbol) {
1701
1703
  const numericValue = parseFloat(numericString);
1702
1704
  if (isNaN(numericValue)) {
1703
- throw DomainError.internal("KIT_NUMBER_INVALID", { value: numericString });
1705
+ throw DomainError.badRequest("KIT_NUMBER_INVALID", { value: numericString });
1704
1706
  }
1705
1707
  if (numericValue < 0) {
1706
- throw DomainError.internal("KIT_NUMBER_NEGATIVE", { value: numericString });
1708
+ throw DomainError.badRequest("KIT_NUMBER_NEGATIVE", { value: numericString });
1707
1709
  }
1708
1710
  return `${numericValue.toFixed(precision)} ${symbol}`;
1709
1711
  }
@@ -1734,7 +1736,7 @@ exports.DomainToBlockchainUtils = __decorateClass([
1734
1736
  common.Injectable()
1735
1737
  ], exports.DomainToBlockchainUtils);
1736
1738
 
1737
- const DIGEST_RE = /^[0-9a-f]{64}$/;
1739
+ const DIGEST_RE = /^[0-9a-f]{64}$/i;
1738
1740
  function chainTextDigest(text) {
1739
1741
  if (!text)
1740
1742
  return "";
@@ -1743,6 +1745,9 @@ function chainTextDigest(text) {
1743
1745
  function isChainTextDigest(value) {
1744
1746
  return typeof value === "string" && DIGEST_RE.test(value);
1745
1747
  }
1748
+ function isChainTextOf(text, chainValue) {
1749
+ return chainTextDigest(text) === (chainValue ?? "").toLowerCase();
1750
+ }
1746
1751
  function resolveChainText(chainValue, current) {
1747
1752
  return isChainTextDigest(chainValue) ? current ?? "" : chainValue;
1748
1753
  }
@@ -1833,17 +1838,17 @@ class AmountFormatterUtils {
1833
1838
  */
1834
1839
  static formatAmount(amountStr) {
1835
1840
  if (!amountStr || typeof amountStr !== "string") {
1836
- throw DomainError.internal("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1841
+ throw DomainError.badRequest("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1837
1842
  }
1838
1843
  const parts = amountStr.trim().split(/\s+/);
1839
1844
  if (parts.length < 1 || !parts[0]) {
1840
- throw DomainError.internal("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1845
+ throw DomainError.badRequest("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1841
1846
  }
1842
1847
  const amountPart = parts[0].replace(",", ".");
1843
1848
  const currency = parts.length >= 2 ? parts.slice(1).join(" ") : "";
1844
1849
  const amount = parseFloat(amountPart);
1845
1850
  if (isNaN(amount)) {
1846
- throw DomainError.internal("KIT_AMOUNT_NUMBER_INVALID", { value: parts[0] });
1851
+ throw DomainError.badRequest("KIT_AMOUNT_NUMBER_INVALID", { value: parts[0] });
1847
1852
  }
1848
1853
  const formattedAmount = amount.toLocaleString("ru-RU", {
1849
1854
  minimumFractionDigits: 2,
@@ -1919,7 +1924,7 @@ class QuantityUtils {
1919
1924
  } else if (symbol === rootGovernSymbol) {
1920
1925
  return rootGovernPrecision;
1921
1926
  } else {
1922
- throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1927
+ throw DomainError.badRequest("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1923
1928
  }
1924
1929
  }
1925
1930
  /**
@@ -1929,7 +1934,7 @@ class QuantityUtils {
1929
1934
  static validateSymbol(symbol) {
1930
1935
  if (!this.isSupportedSymbol(symbol)) {
1931
1936
  const { rootSymbol, rootGovernSymbol } = platformSettings().blockchain;
1932
- throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1937
+ throw DomainError.badRequest("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1933
1938
  }
1934
1939
  }
1935
1940
  /**
@@ -1941,7 +1946,7 @@ class QuantityUtils {
1941
1946
  static formatQuantityForBlockchain(amount, symbol) {
1942
1947
  this.validateSymbol(symbol);
1943
1948
  if (isNaN(amount) || amount < 0) {
1944
- throw DomainError.internal("KIT_NUMBER_INVALID", { value: amount });
1949
+ throw DomainError.badRequest("KIT_NUMBER_INVALID", { value: amount });
1945
1950
  }
1946
1951
  const precision = this.getPrecisionForSymbol(symbol);
1947
1952
  const formattedAmount = amount.toFixed(precision);
@@ -1956,7 +1961,7 @@ class QuantityUtils {
1956
1961
  static combineQuantityAndSymbol(amount, symbol) {
1957
1962
  this.validateSymbol(symbol);
1958
1963
  if (isNaN(amount) || amount < 0) {
1959
- throw DomainError.internal("KIT_NUMBER_INVALID", { value: amount });
1964
+ throw DomainError.badRequest("KIT_NUMBER_INVALID", { value: amount });
1960
1965
  }
1961
1966
  return `${amount} ${symbol}`;
1962
1967
  }
@@ -1968,12 +1973,12 @@ class QuantityUtils {
1968
1973
  static parseQuantityString(quantity) {
1969
1974
  const parts = quantity.split(" ");
1970
1975
  if (parts.length !== 2) {
1971
- throw DomainError.internal("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1976
+ throw DomainError.badRequest("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1972
1977
  }
1973
1978
  const [amountStr, symbol] = parts;
1974
1979
  const amount = parseFloat(amountStr);
1975
1980
  if (isNaN(amount)) {
1976
- throw DomainError.internal("KIT_QUANTITY_AMOUNT_INVALID", { amount: amountStr });
1981
+ throw DomainError.badRequest("KIT_QUANTITY_AMOUNT_INVALID", { amount: amountStr });
1977
1982
  }
1978
1983
  this.validateSymbol(symbol);
1979
1984
  return { amount, symbol };
@@ -2094,6 +2099,79 @@ function resolveSortColumn(repository, sortBy, fallback) {
2094
2099
  return isKnownColumn ? sortBy : fallback;
2095
2100
  }
2096
2101
 
2102
+ class LocalChangesCollector {
2103
+ constructor(isWatched, publish) {
2104
+ this.isWatched = isWatched;
2105
+ this.publish = publish;
2106
+ this.pending = /* @__PURE__ */ new WeakMap();
2107
+ }
2108
+ afterInsert(event) {
2109
+ this.collect(event.metadata, event.queryRunner, event.entity);
2110
+ }
2111
+ afterUpdate(event) {
2112
+ this.collect(event.metadata, event.queryRunner, { ...event.databaseEntity ?? {}, ...event.entity ?? {} });
2113
+ }
2114
+ afterRemove(event) {
2115
+ const row = event.databaseEntity ?? event.entity;
2116
+ this.collect(event.metadata, event.queryRunner, row, event.entityId);
2117
+ }
2118
+ afterTransactionCommit(event) {
2119
+ const changes = this.pending.get(event.queryRunner);
2120
+ if (!changes)
2121
+ return;
2122
+ this.pending.delete(event.queryRunner);
2123
+ changes.forEach((c) => void this.publish(c.table, c.primary_key, c.row));
2124
+ }
2125
+ afterTransactionRollback(event) {
2126
+ this.pending.delete(event.queryRunner);
2127
+ }
2128
+ collect(metadata, queryRunner, row, entityId) {
2129
+ const table = metadata.tableName;
2130
+ if (!this.isWatched(table))
2131
+ return;
2132
+ const change = { table, primary_key: primaryKeyOf(metadata, row, entityId), row };
2133
+ if (queryRunner?.isTransactionActive) {
2134
+ const list = this.pending.get(queryRunner) ?? [];
2135
+ list.push(change);
2136
+ this.pending.set(queryRunner, list);
2137
+ return;
2138
+ }
2139
+ void this.publish(change.table, change.primary_key, change.row);
2140
+ }
2141
+ }
2142
+ function primaryKeyOf(metadata, row, entityId) {
2143
+ if (row) {
2144
+ const ids = metadata.primaryColumns.map((c) => c.getEntityValue(row)).filter((v) => v !== void 0 && v !== null);
2145
+ if (ids.length)
2146
+ return ids.map(String).join(":");
2147
+ }
2148
+ if (entityId !== void 0 && entityId !== null) {
2149
+ return typeof entityId === "object" ? Object.values(entityId).map(String).join(":") : String(entityId);
2150
+ }
2151
+ return "";
2152
+ }
2153
+
2154
+ const numericColumnTransformer = {
2155
+ to: (value) => value,
2156
+ from: (value) => value === null || value === void 0 ? value : Number(value)
2157
+ };
2158
+
2159
+ function SafeMarkup(validationOptions) {
2160
+ return function(object, propertyName) {
2161
+ classValidator.registerDecorator({
2162
+ name: "safeMarkup",
2163
+ target: object.constructor,
2164
+ propertyName,
2165
+ options: { message: validationMessage("kit.markup.executable"), ...validationOptions },
2166
+ validator: {
2167
+ validate(value) {
2168
+ return typeof value !== "string" || !cooptypes._Common.Text.hasExecutableMarkup(value);
2169
+ }
2170
+ }
2171
+ });
2172
+ };
2173
+ }
2174
+
2097
2175
  class HttpApiError extends common.HttpException {
2098
2176
  constructor(statusCode, message, isOperational = true, stack, subcode) {
2099
2177
  super(message, statusCode);
@@ -2109,6 +2187,7 @@ class HttpApiError extends common.HttpException {
2109
2187
 
2110
2188
  const ASSERT_PREFIX = /assertion failure with message: (.+?)(?:\n|$)/;
2111
2189
  const CODED = /^([A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+):\s*(.*)$/s;
2190
+ const ENCODING = /^Encoding error at root<[^>]*>((?:\.[^.<:]+<[^>]*>)*):/;
2112
2191
  function parseChainAssert(message) {
2113
2192
  const assert = message.match(ASSERT_PREFIX);
2114
2193
  const body = (assert ? assert[1] : message).trim();
@@ -2124,7 +2203,23 @@ function chainErrorCode(error) {
2124
2203
  const raw = error?.message ?? String(error);
2125
2204
  return parseChainAssert(raw).code;
2126
2205
  }
2206
+ function chainRefusalOf(error) {
2207
+ if (error instanceof DomainError)
2208
+ return error;
2209
+ const raw = error?.message ?? String(error);
2210
+ const encoding = raw.match(ENCODING);
2211
+ if (encoding) {
2212
+ const field = encoding[1].split(".").filter(Boolean).map((part) => part.replace(/<[^>]*>$/, "")).join(".");
2213
+ return DomainError.badRequest("CHAIN_ACTION_ARGUMENT_INVALID", { field });
2214
+ }
2215
+ if (!ASSERT_PREFIX.test(raw))
2216
+ return null;
2217
+ const { code, text } = parseChainAssert(raw);
2218
+ return DomainError.badRequest(code ?? "CHAIN_ASSERT", { message: text });
2219
+ }
2127
2220
  function rethrowChainError(error) {
2221
+ if (error instanceof DomainError)
2222
+ throw error;
2128
2223
  const raw = error?.message ?? String(error);
2129
2224
  const match = raw.match(ASSERT_PREFIX);
2130
2225
  if (!match)
@@ -2155,6 +2250,7 @@ exports.HttpApiError = HttpApiError;
2155
2250
  exports.InjectBucket = InjectBucket;
2156
2251
  exports.LOG_EXTENSION_REPOSITORY = LOG_EXTENSION_REPOSITORY;
2157
2252
  exports.LangType = LangType;
2253
+ exports.LocalChangesCollector = LocalChangesCollector;
2158
2254
  exports.LogExtensionDomainEntity = LogExtensionDomainEntity;
2159
2255
  exports.ONBOARDING_EXPIRY_DAYS = ONBOARDING_EXPIRY_DAYS;
2160
2256
  exports.ONBOARDING_EXPIRY_MS = ONBOARDING_EXPIRY_MS;
@@ -2165,11 +2261,13 @@ exports.QuantityUtils = QuantityUtils;
2165
2261
  exports.ROLES_ANY_STATUS_KEY = ROLES_ANY_STATUS_KEY;
2166
2262
  exports.ROLES_DENY_SELF_KEY = ROLES_DENY_SELF_KEY;
2167
2263
  exports.SORT_FIELD_PATTERN = SORT_FIELD_PATTERN;
2264
+ exports.SafeMarkup = SafeMarkup;
2168
2265
  exports.UseBucket = UseBucket;
2169
2266
  exports.bucketProvidersFor = bucketProvidersFor;
2170
2267
  exports.bucketTokenFor = bucketTokenFor;
2171
2268
  exports.buildPaginationResult = buildPaginationResult;
2172
2269
  exports.chainErrorCode = chainErrorCode;
2270
+ exports.chainRefusalOf = chainRefusalOf;
2173
2271
  exports.chainTextDigest = chainTextDigest;
2174
2272
  exports.checkPaymentAmount = checkPaymentAmount;
2175
2273
  exports.checkPaymentSymbol = checkPaymentSymbol;
@@ -2187,8 +2285,10 @@ exports.getAmountPlusFee = getAmountPlusFee;
2187
2285
  exports.getAppliedBlockNum = getAppliedBlockNum;
2188
2286
  exports.hasServerSecret = hasServerSecret;
2189
2287
  exports.isChainTextDigest = isChainTextDigest;
2288
+ exports.isChainTextOf = isChainTextOf;
2190
2289
  exports.isExtensionAvailable = isExtensionAvailable;
2191
2290
  exports.mergeSecretConfig = mergeSecretConfig;
2291
+ exports.numericColumnTransformer = numericColumnTransformer;
2192
2292
  exports.paginationInputToOffset = paginationInputToOffset;
2193
2293
  exports.parseChainAssert = parseChainAssert;
2194
2294
  exports.platformSettings = platformSettings;
package/dist/index.d.cts CHANGED
@@ -5,7 +5,8 @@ import { Reflector } from '@nestjs/core';
5
5
  import { Observable } from 'rxjs';
6
6
  import { ZodObject } from 'zod';
7
7
  import { Cooperative } from 'cooptypes';
8
- import { MigrationInterface, ObjectLiteral, Repository } from 'typeorm';
8
+ import { MigrationInterface, ObjectLiteral, Repository, EntitySubscriberInterface, InsertEvent, UpdateEvent, RemoveEvent, TransactionCommitEvent, TransactionRollbackEvent, ValueTransformer } from 'typeorm';
9
+ import { ValidationOptions } from 'class-validator';
9
10
 
10
11
  /**
11
12
  * Пути внутри объекта, по которым пайщик признаётся «своим» для поля.
@@ -1320,6 +1321,8 @@ declare class DomainToBlockchainUtils {
1320
1321
  declare function chainTextDigest(text: string | null | undefined): string;
1321
1322
  /** Значение из цепи — хеш текста, а не сам текст. */
1322
1323
  declare function isChainTextDigest(value: string | null | undefined): value is string;
1324
+ /** Хеш в цепи — хеш именно этого текста (регистр хеша не важен). */
1325
+ declare function isChainTextOf(text: string | null | undefined, chainValue: string | null | undefined): boolean;
1323
1326
  /**
1324
1327
  * Значение поля из цепи для записи в базу: хеш текст в базе не заменяет.
1325
1328
  * Строки, записанные до выноса текстов, несут сам текст и копируются как раньше.
@@ -1631,6 +1634,58 @@ declare function sanitizeUserText<T extends string | null | undefined>(value: T)
1631
1634
  */
1632
1635
  declare function resolveSortColumn<T extends ObjectLiteral>(repository: Repository<T>, sortBy: string | undefined, fallback: string): string;
1633
1636
 
1637
+ /** Публикация сигнала ленты изменений: таблица базы, ключ строки, строка. */
1638
+ type LocalChangePublisher = (table: string, primary_key: string, row: Record<string, unknown> | undefined) => unknown;
1639
+ /**
1640
+ * Сигналы ленты изменений для данных узла вне цепи — общая часть подписчика
1641
+ * TypeORM для ядра и расширений со своей базой.
1642
+ *
1643
+ * Любая запись в наблюдаемую таблицу — через репозиторий, менеджер или
1644
+ * построитель запросов — порождает сигнал «перечитай». Сигнал уходит после
1645
+ * фиксации транзакции: стол по нему читает уже записанное, а откаченная запись
1646
+ * сигнала не даёт. Сырой `query()` подписчик не видит — такую запись
1647
+ * публикует сам её автор.
1648
+ *
1649
+ * Из `typeorm` здесь только типы: метаданные и события приходят от источника
1650
+ * данных вызывающей стороны.
1651
+ */
1652
+ declare class LocalChangesCollector implements EntitySubscriberInterface {
1653
+ private readonly isWatched;
1654
+ private readonly publish;
1655
+ private readonly pending;
1656
+ constructor(isWatched: (table: string) => boolean, publish: LocalChangePublisher);
1657
+ afterInsert(event: InsertEvent<any>): void;
1658
+ afterUpdate(event: UpdateEvent<any>): void;
1659
+ afterRemove(event: RemoveEvent<any>): void;
1660
+ afterTransactionCommit(event: TransactionCommitEvent): void;
1661
+ afterTransactionRollback(event: TransactionRollbackEvent): void;
1662
+ private collect;
1663
+ }
1664
+
1665
+ /**
1666
+ * Трансформер колонок `numeric`, которые в домене — число: Postgres отдаёт
1667
+ * numeric строкой, а сущности и арифметика работают с `number`. Годится для
1668
+ * количеств (0.5 кг) и сумм, которые домен держит числом (сумма платежа);
1669
+ * денежные поля, которые домен держит строкой актива, его не используют.
1670
+ *
1671
+ * Один экземпляр на ядро и расширения — из пакета (из typeorm здесь только тип).
1672
+ */
1673
+ declare const numericColumnTransformer: ValueTransformer;
1674
+
1675
+ /**
1676
+ * Текст, который уходит в подписываемый документ (повестка собрания, проект
1677
+ * решения совета), без исполняемой разметки.
1678
+ *
1679
+ * Вёрстка допускается: проекты решений на утверждение совета несут абзацы,
1680
+ * таблицы и стили документа. Отклоняются только скрипты, встраиваемые рамки и
1681
+ * объекты, формы, обработчики событий и опасные ссылки — правило одно с
1682
+ * очисткой редактора (`_Common.Text.hasExecutableMarkup` в cooptypes).
1683
+ *
1684
+ * Отказ, а не очистка: документ подписывается по хэшу, и переписанный текст
1685
+ * был бы не тем, что ввёл человек (решение владельца 25.09.2026, C28-80).
1686
+ */
1687
+ declare function SafeMarkup(validationOptions?: ValidationOptions): (object: object, propertyName: string) => void;
1688
+
1634
1689
  /**
1635
1690
  * Ошибка API с кодом состояния HTTP.
1636
1691
  *
@@ -1647,35 +1702,6 @@ declare class HttpApiError extends HttpException {
1647
1702
  constructor(statusCode: HttpStatus, message: string, isOperational?: boolean, stack?: string, subcode?: any);
1648
1703
  }
1649
1704
 
1650
- /**
1651
- * Разбор отказа контракта. Контракт пишет причину как `КОД: текст`
1652
- * (`GATEWAY_OUTCOME_NOT_FOUND: Объект возврата не существует…`); старые
1653
- * отказы — одним текстом, без кода.
1654
- */
1655
- declare function parseChainAssert(message: string): {
1656
- code?: string;
1657
- text: string;
1658
- };
1659
- /**
1660
- * Код отказа контракта — из DomainError, в который отказ уже превращён,
1661
- * или из сырой ошибки цепи. Отказ без кода — undefined. Сравнивать отказы
1662
- * контракта — по этому коду, а не по тексту: текст переводится и правится.
1663
- */
1664
- declare function chainErrorCode(error: unknown): string | undefined;
1665
- /**
1666
- * Пробросить отказ цепи человеку.
1667
- *
1668
- * `eosio::check` возвращает сообщение обёрнутым в `assertion failure with
1669
- * message: ...`; пайщику нужна причина, а не обёртка виртуальной машины.
1670
- * Живёт в каркасе, потому что в цепь ходит не одно расширение: вторая копия
1671
- * этой чистки разошлась бы с первой на первом же новом формате ошибки.
1672
- *
1673
- * Отказ с кодом (`КОД: текст`) уходит с этим кодом, текст — из словаря,
1674
- * а без записи в словаре — текст контракта. Отказ без кода — `CHAIN_ASSERT`,
1675
- * прочий сбой цепи — `CHAIN_ERROR`, текст — параметром `message`.
1676
- */
1677
- declare function rethrowChainError(error: unknown): never;
1678
-
1679
1705
  type DomainErrorParams = Record<string, string | number | boolean | null | undefined>;
1680
1706
  interface Branded {
1681
1707
  code: string;
@@ -1733,5 +1759,43 @@ declare function validationMessage(key: string, params?: DomainErrorParams): (ar
1733
1759
  constraints?: unknown[];
1734
1760
  }) => string;
1735
1761
 
1736
- 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 };
1737
- export type { AuthRolesOptions, BucketSource, BucketSpec, DeserializedDescriptionOfExtension, DomainErrorParams, EventSource, EventWait, ExcludeCommonProps, ExtensionAppTerminatePayload, ExtensionAuthOptions, ExtensionConfigFieldPolicy, ExtensionConfigPolicy, ExtensionDatabaseMigrationClass, ExtensionDomainInterface, ExtensionDomainRepository, ExtensionEntityClass, ExtensionSchemaMigrationAfterContext, GeneratedDocumentDomainInterface, GeneratedDocumentDraft, IDesktopConfig, IExtensionSchemaMigration, IRegistryExtension, IResolvedRegistryExtension, LoadGeneratedDocumentByDocHash, LogExtensionDomainInterface, LogExtensionDomainRepository, LogExtensionFilter, LogExtensionPaginationOptions, LogExtensionPaginationResult, PaymentDetails, PlatformBlockchainSettings, PlatformSettings, RegisteredBucket, RequireFields, SignedDocumentMetaCompareMode, SignedDocumentMetaVerification, SignedDocumentToVerify, TransactResultWithResponse };
1762
+ /**
1763
+ * Разбор отказа контракта. Контракт пишет причину как `КОД: текст`
1764
+ * (`GATEWAY_OUTCOME_NOT_FOUND: Объект возврата не существует…`); старые
1765
+ * отказы — одним текстом, без кода.
1766
+ */
1767
+ declare function parseChainAssert(message: string): {
1768
+ code?: string;
1769
+ text: string;
1770
+ };
1771
+ /**
1772
+ * Код отказа контракта — из DomainError, в который отказ уже превращён,
1773
+ * или из сырой ошибки цепи. Отказ без кода — undefined. Сравнивать отказы
1774
+ * контракта — по этому коду, а не по тексту: текст переводится и правится.
1775
+ */
1776
+ declare function chainErrorCode(error: unknown): string | undefined;
1777
+ /**
1778
+ * Отказ контракта (`eosio::check`) как отказ с кодом; иной сбой цепи — null.
1779
+ *
1780
+ * Отказ контракта — ответ на действие пайщика, а не сбой сервера. Клиент цепи
1781
+ * (@wharfkit/session) превращает его в простой Error, и без перевода он уходил
1782
+ * ответом 500 без кода — в журнал ошибок как поломка (C28-80). Переводит
1783
+ * единственная отправка в цепь, поэтому код получают все мутации сразу.
1784
+ */
1785
+ declare function chainRefusalOf(error: unknown): DomainError | null;
1786
+ /**
1787
+ * Пробросить отказ цепи человеку.
1788
+ *
1789
+ * `eosio::check` возвращает сообщение обёрнутым в `assertion failure with
1790
+ * message: ...`; пайщику нужна причина, а не обёртка виртуальной машины.
1791
+ * Живёт в каркасе, потому что в цепь ходит не одно расширение: вторая копия
1792
+ * этой чистки разошлась бы с первой на первом же новом формате ошибки.
1793
+ *
1794
+ * Отказ с кодом (`КОД: текст`) уходит с этим кодом, текст — из словаря,
1795
+ * а без записи в словаре — текст контракта. Отказ без кода — `CHAIN_ASSERT`,
1796
+ * прочий сбой цепи — `CHAIN_ERROR`, текст — параметром `message`.
1797
+ */
1798
+ declare function rethrowChainError(error: unknown): never;
1799
+
1800
+ 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, LocalChangesCollector, 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, SafeMarkup, SignatureInfoDTO, SignatureInfoInputDTO, SignedDigitalDocumentDTO, SignedDigitalDocumentInputDTO, TransactionDTO, UseBucket, bucketProvidersFor, bucketTokenFor, buildPaginationResult, chainErrorCode, chainRefusalOf, chainTextDigest, checkPaymentAmount, checkPaymentSymbol, computeOnboardingExpiresAt, configureExtensionAuth, configurePlatformSettings, createPaginationResult, domainErrorMessage, extensionDatabaseMigrations, extensionEntities, generateHashFromString, generateRandomHash, generateUniqueHash, getAmountPlusFee, getAppliedBlockNum, hasServerSecret, isChainTextDigest, isChainTextOf, isExtensionAvailable, mergeSecretConfig, numericColumnTransformer, paginationInputToOffset, parseChainAssert, platformSettings, providerSuppliedPaths, redactSecretConfig, registerExtensionDatabaseMigrations, registerExtensionEntities, resetExtensionDatabaseMigrations, resetExtensionEntities, resolveChainText, resolveSortColumn, rethrowChainError, sanitizeUserText, validationMessage, verifySignedDocumentAgainstStoredDraft, waitForEvent };
1801
+ export type { AuthRolesOptions, BucketSource, BucketSpec, DeserializedDescriptionOfExtension, DomainErrorParams, EventSource, EventWait, ExcludeCommonProps, ExtensionAppTerminatePayload, ExtensionAuthOptions, ExtensionConfigFieldPolicy, ExtensionConfigPolicy, ExtensionDatabaseMigrationClass, ExtensionDomainInterface, ExtensionDomainRepository, ExtensionEntityClass, ExtensionSchemaMigrationAfterContext, GeneratedDocumentDomainInterface, GeneratedDocumentDraft, IDesktopConfig, IExtensionSchemaMigration, IRegistryExtension, IResolvedRegistryExtension, LoadGeneratedDocumentByDocHash, LocalChangePublisher, LogExtensionDomainInterface, LogExtensionDomainRepository, LogExtensionFilter, LogExtensionPaginationOptions, LogExtensionPaginationResult, PaymentDetails, PlatformBlockchainSettings, PlatformSettings, RegisteredBucket, RequireFields, SignedDocumentMetaCompareMode, SignedDocumentMetaVerification, SignedDocumentToVerify, TransactResultWithResponse };
package/dist/index.d.mts CHANGED
@@ -5,7 +5,8 @@ import { Reflector } from '@nestjs/core';
5
5
  import { Observable } from 'rxjs';
6
6
  import { ZodObject } from 'zod';
7
7
  import { Cooperative } from 'cooptypes';
8
- import { MigrationInterface, ObjectLiteral, Repository } from 'typeorm';
8
+ import { MigrationInterface, ObjectLiteral, Repository, EntitySubscriberInterface, InsertEvent, UpdateEvent, RemoveEvent, TransactionCommitEvent, TransactionRollbackEvent, ValueTransformer } from 'typeorm';
9
+ import { ValidationOptions } from 'class-validator';
9
10
 
10
11
  /**
11
12
  * Пути внутри объекта, по которым пайщик признаётся «своим» для поля.
@@ -1320,6 +1321,8 @@ declare class DomainToBlockchainUtils {
1320
1321
  declare function chainTextDigest(text: string | null | undefined): string;
1321
1322
  /** Значение из цепи — хеш текста, а не сам текст. */
1322
1323
  declare function isChainTextDigest(value: string | null | undefined): value is string;
1324
+ /** Хеш в цепи — хеш именно этого текста (регистр хеша не важен). */
1325
+ declare function isChainTextOf(text: string | null | undefined, chainValue: string | null | undefined): boolean;
1323
1326
  /**
1324
1327
  * Значение поля из цепи для записи в базу: хеш текст в базе не заменяет.
1325
1328
  * Строки, записанные до выноса текстов, несут сам текст и копируются как раньше.
@@ -1631,6 +1634,58 @@ declare function sanitizeUserText<T extends string | null | undefined>(value: T)
1631
1634
  */
1632
1635
  declare function resolveSortColumn<T extends ObjectLiteral>(repository: Repository<T>, sortBy: string | undefined, fallback: string): string;
1633
1636
 
1637
+ /** Публикация сигнала ленты изменений: таблица базы, ключ строки, строка. */
1638
+ type LocalChangePublisher = (table: string, primary_key: string, row: Record<string, unknown> | undefined) => unknown;
1639
+ /**
1640
+ * Сигналы ленты изменений для данных узла вне цепи — общая часть подписчика
1641
+ * TypeORM для ядра и расширений со своей базой.
1642
+ *
1643
+ * Любая запись в наблюдаемую таблицу — через репозиторий, менеджер или
1644
+ * построитель запросов — порождает сигнал «перечитай». Сигнал уходит после
1645
+ * фиксации транзакции: стол по нему читает уже записанное, а откаченная запись
1646
+ * сигнала не даёт. Сырой `query()` подписчик не видит — такую запись
1647
+ * публикует сам её автор.
1648
+ *
1649
+ * Из `typeorm` здесь только типы: метаданные и события приходят от источника
1650
+ * данных вызывающей стороны.
1651
+ */
1652
+ declare class LocalChangesCollector implements EntitySubscriberInterface {
1653
+ private readonly isWatched;
1654
+ private readonly publish;
1655
+ private readonly pending;
1656
+ constructor(isWatched: (table: string) => boolean, publish: LocalChangePublisher);
1657
+ afterInsert(event: InsertEvent<any>): void;
1658
+ afterUpdate(event: UpdateEvent<any>): void;
1659
+ afterRemove(event: RemoveEvent<any>): void;
1660
+ afterTransactionCommit(event: TransactionCommitEvent): void;
1661
+ afterTransactionRollback(event: TransactionRollbackEvent): void;
1662
+ private collect;
1663
+ }
1664
+
1665
+ /**
1666
+ * Трансформер колонок `numeric`, которые в домене — число: Postgres отдаёт
1667
+ * numeric строкой, а сущности и арифметика работают с `number`. Годится для
1668
+ * количеств (0.5 кг) и сумм, которые домен держит числом (сумма платежа);
1669
+ * денежные поля, которые домен держит строкой актива, его не используют.
1670
+ *
1671
+ * Один экземпляр на ядро и расширения — из пакета (из typeorm здесь только тип).
1672
+ */
1673
+ declare const numericColumnTransformer: ValueTransformer;
1674
+
1675
+ /**
1676
+ * Текст, который уходит в подписываемый документ (повестка собрания, проект
1677
+ * решения совета), без исполняемой разметки.
1678
+ *
1679
+ * Вёрстка допускается: проекты решений на утверждение совета несут абзацы,
1680
+ * таблицы и стили документа. Отклоняются только скрипты, встраиваемые рамки и
1681
+ * объекты, формы, обработчики событий и опасные ссылки — правило одно с
1682
+ * очисткой редактора (`_Common.Text.hasExecutableMarkup` в cooptypes).
1683
+ *
1684
+ * Отказ, а не очистка: документ подписывается по хэшу, и переписанный текст
1685
+ * был бы не тем, что ввёл человек (решение владельца 25.09.2026, C28-80).
1686
+ */
1687
+ declare function SafeMarkup(validationOptions?: ValidationOptions): (object: object, propertyName: string) => void;
1688
+
1634
1689
  /**
1635
1690
  * Ошибка API с кодом состояния HTTP.
1636
1691
  *
@@ -1647,35 +1702,6 @@ declare class HttpApiError extends HttpException {
1647
1702
  constructor(statusCode: HttpStatus, message: string, isOperational?: boolean, stack?: string, subcode?: any);
1648
1703
  }
1649
1704
 
1650
- /**
1651
- * Разбор отказа контракта. Контракт пишет причину как `КОД: текст`
1652
- * (`GATEWAY_OUTCOME_NOT_FOUND: Объект возврата не существует…`); старые
1653
- * отказы — одним текстом, без кода.
1654
- */
1655
- declare function parseChainAssert(message: string): {
1656
- code?: string;
1657
- text: string;
1658
- };
1659
- /**
1660
- * Код отказа контракта — из DomainError, в который отказ уже превращён,
1661
- * или из сырой ошибки цепи. Отказ без кода — undefined. Сравнивать отказы
1662
- * контракта — по этому коду, а не по тексту: текст переводится и правится.
1663
- */
1664
- declare function chainErrorCode(error: unknown): string | undefined;
1665
- /**
1666
- * Пробросить отказ цепи человеку.
1667
- *
1668
- * `eosio::check` возвращает сообщение обёрнутым в `assertion failure with
1669
- * message: ...`; пайщику нужна причина, а не обёртка виртуальной машины.
1670
- * Живёт в каркасе, потому что в цепь ходит не одно расширение: вторая копия
1671
- * этой чистки разошлась бы с первой на первом же новом формате ошибки.
1672
- *
1673
- * Отказ с кодом (`КОД: текст`) уходит с этим кодом, текст — из словаря,
1674
- * а без записи в словаре — текст контракта. Отказ без кода — `CHAIN_ASSERT`,
1675
- * прочий сбой цепи — `CHAIN_ERROR`, текст — параметром `message`.
1676
- */
1677
- declare function rethrowChainError(error: unknown): never;
1678
-
1679
1705
  type DomainErrorParams = Record<string, string | number | boolean | null | undefined>;
1680
1706
  interface Branded {
1681
1707
  code: string;
@@ -1733,5 +1759,43 @@ declare function validationMessage(key: string, params?: DomainErrorParams): (ar
1733
1759
  constraints?: unknown[];
1734
1760
  }) => string;
1735
1761
 
1736
- 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 };
1737
- export type { AuthRolesOptions, BucketSource, BucketSpec, DeserializedDescriptionOfExtension, DomainErrorParams, EventSource, EventWait, ExcludeCommonProps, ExtensionAppTerminatePayload, ExtensionAuthOptions, ExtensionConfigFieldPolicy, ExtensionConfigPolicy, ExtensionDatabaseMigrationClass, ExtensionDomainInterface, ExtensionDomainRepository, ExtensionEntityClass, ExtensionSchemaMigrationAfterContext, GeneratedDocumentDomainInterface, GeneratedDocumentDraft, IDesktopConfig, IExtensionSchemaMigration, IRegistryExtension, IResolvedRegistryExtension, LoadGeneratedDocumentByDocHash, LogExtensionDomainInterface, LogExtensionDomainRepository, LogExtensionFilter, LogExtensionPaginationOptions, LogExtensionPaginationResult, PaymentDetails, PlatformBlockchainSettings, PlatformSettings, RegisteredBucket, RequireFields, SignedDocumentMetaCompareMode, SignedDocumentMetaVerification, SignedDocumentToVerify, TransactResultWithResponse };
1762
+ /**
1763
+ * Разбор отказа контракта. Контракт пишет причину как `КОД: текст`
1764
+ * (`GATEWAY_OUTCOME_NOT_FOUND: Объект возврата не существует…`); старые
1765
+ * отказы — одним текстом, без кода.
1766
+ */
1767
+ declare function parseChainAssert(message: string): {
1768
+ code?: string;
1769
+ text: string;
1770
+ };
1771
+ /**
1772
+ * Код отказа контракта — из DomainError, в который отказ уже превращён,
1773
+ * или из сырой ошибки цепи. Отказ без кода — undefined. Сравнивать отказы
1774
+ * контракта — по этому коду, а не по тексту: текст переводится и правится.
1775
+ */
1776
+ declare function chainErrorCode(error: unknown): string | undefined;
1777
+ /**
1778
+ * Отказ контракта (`eosio::check`) как отказ с кодом; иной сбой цепи — null.
1779
+ *
1780
+ * Отказ контракта — ответ на действие пайщика, а не сбой сервера. Клиент цепи
1781
+ * (@wharfkit/session) превращает его в простой Error, и без перевода он уходил
1782
+ * ответом 500 без кода — в журнал ошибок как поломка (C28-80). Переводит
1783
+ * единственная отправка в цепь, поэтому код получают все мутации сразу.
1784
+ */
1785
+ declare function chainRefusalOf(error: unknown): DomainError | null;
1786
+ /**
1787
+ * Пробросить отказ цепи человеку.
1788
+ *
1789
+ * `eosio::check` возвращает сообщение обёрнутым в `assertion failure with
1790
+ * message: ...`; пайщику нужна причина, а не обёртка виртуальной машины.
1791
+ * Живёт в каркасе, потому что в цепь ходит не одно расширение: вторая копия
1792
+ * этой чистки разошлась бы с первой на первом же новом формате ошибки.
1793
+ *
1794
+ * Отказ с кодом (`КОД: текст`) уходит с этим кодом, текст — из словаря,
1795
+ * а без записи в словаре — текст контракта. Отказ без кода — `CHAIN_ASSERT`,
1796
+ * прочий сбой цепи — `CHAIN_ERROR`, текст — параметром `message`.
1797
+ */
1798
+ declare function rethrowChainError(error: unknown): never;
1799
+
1800
+ 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, LocalChangesCollector, 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, SafeMarkup, SignatureInfoDTO, SignatureInfoInputDTO, SignedDigitalDocumentDTO, SignedDigitalDocumentInputDTO, TransactionDTO, UseBucket, bucketProvidersFor, bucketTokenFor, buildPaginationResult, chainErrorCode, chainRefusalOf, chainTextDigest, checkPaymentAmount, checkPaymentSymbol, computeOnboardingExpiresAt, configureExtensionAuth, configurePlatformSettings, createPaginationResult, domainErrorMessage, extensionDatabaseMigrations, extensionEntities, generateHashFromString, generateRandomHash, generateUniqueHash, getAmountPlusFee, getAppliedBlockNum, hasServerSecret, isChainTextDigest, isChainTextOf, isExtensionAvailable, mergeSecretConfig, numericColumnTransformer, paginationInputToOffset, parseChainAssert, platformSettings, providerSuppliedPaths, redactSecretConfig, registerExtensionDatabaseMigrations, registerExtensionEntities, resetExtensionDatabaseMigrations, resetExtensionEntities, resolveChainText, resolveSortColumn, rethrowChainError, sanitizeUserText, validationMessage, verifySignedDocumentAgainstStoredDraft, waitForEvent };
1801
+ export type { AuthRolesOptions, BucketSource, BucketSpec, DeserializedDescriptionOfExtension, DomainErrorParams, EventSource, EventWait, ExcludeCommonProps, ExtensionAppTerminatePayload, ExtensionAuthOptions, ExtensionConfigFieldPolicy, ExtensionConfigPolicy, ExtensionDatabaseMigrationClass, ExtensionDomainInterface, ExtensionDomainRepository, ExtensionEntityClass, ExtensionSchemaMigrationAfterContext, GeneratedDocumentDomainInterface, GeneratedDocumentDraft, IDesktopConfig, IExtensionSchemaMigration, IRegistryExtension, IResolvedRegistryExtension, LoadGeneratedDocumentByDocHash, LocalChangePublisher, LogExtensionDomainInterface, LogExtensionDomainRepository, LogExtensionFilter, LogExtensionPaginationOptions, LogExtensionPaginationResult, PaymentDetails, PlatformBlockchainSettings, PlatformSettings, RegisteredBucket, RequireFields, SignedDocumentMetaCompareMode, SignedDocumentMetaVerification, SignedDocumentToVerify, TransactResultWithResponse };
package/dist/index.d.ts CHANGED
@@ -5,7 +5,8 @@ import { Reflector } from '@nestjs/core';
5
5
  import { Observable } from 'rxjs';
6
6
  import { ZodObject } from 'zod';
7
7
  import { Cooperative } from 'cooptypes';
8
- import { MigrationInterface, ObjectLiteral, Repository } from 'typeorm';
8
+ import { MigrationInterface, ObjectLiteral, Repository, EntitySubscriberInterface, InsertEvent, UpdateEvent, RemoveEvent, TransactionCommitEvent, TransactionRollbackEvent, ValueTransformer } from 'typeorm';
9
+ import { ValidationOptions } from 'class-validator';
9
10
 
10
11
  /**
11
12
  * Пути внутри объекта, по которым пайщик признаётся «своим» для поля.
@@ -1320,6 +1321,8 @@ declare class DomainToBlockchainUtils {
1320
1321
  declare function chainTextDigest(text: string | null | undefined): string;
1321
1322
  /** Значение из цепи — хеш текста, а не сам текст. */
1322
1323
  declare function isChainTextDigest(value: string | null | undefined): value is string;
1324
+ /** Хеш в цепи — хеш именно этого текста (регистр хеша не важен). */
1325
+ declare function isChainTextOf(text: string | null | undefined, chainValue: string | null | undefined): boolean;
1323
1326
  /**
1324
1327
  * Значение поля из цепи для записи в базу: хеш текст в базе не заменяет.
1325
1328
  * Строки, записанные до выноса текстов, несут сам текст и копируются как раньше.
@@ -1631,6 +1634,58 @@ declare function sanitizeUserText<T extends string | null | undefined>(value: T)
1631
1634
  */
1632
1635
  declare function resolveSortColumn<T extends ObjectLiteral>(repository: Repository<T>, sortBy: string | undefined, fallback: string): string;
1633
1636
 
1637
+ /** Публикация сигнала ленты изменений: таблица базы, ключ строки, строка. */
1638
+ type LocalChangePublisher = (table: string, primary_key: string, row: Record<string, unknown> | undefined) => unknown;
1639
+ /**
1640
+ * Сигналы ленты изменений для данных узла вне цепи — общая часть подписчика
1641
+ * TypeORM для ядра и расширений со своей базой.
1642
+ *
1643
+ * Любая запись в наблюдаемую таблицу — через репозиторий, менеджер или
1644
+ * построитель запросов — порождает сигнал «перечитай». Сигнал уходит после
1645
+ * фиксации транзакции: стол по нему читает уже записанное, а откаченная запись
1646
+ * сигнала не даёт. Сырой `query()` подписчик не видит — такую запись
1647
+ * публикует сам её автор.
1648
+ *
1649
+ * Из `typeorm` здесь только типы: метаданные и события приходят от источника
1650
+ * данных вызывающей стороны.
1651
+ */
1652
+ declare class LocalChangesCollector implements EntitySubscriberInterface {
1653
+ private readonly isWatched;
1654
+ private readonly publish;
1655
+ private readonly pending;
1656
+ constructor(isWatched: (table: string) => boolean, publish: LocalChangePublisher);
1657
+ afterInsert(event: InsertEvent<any>): void;
1658
+ afterUpdate(event: UpdateEvent<any>): void;
1659
+ afterRemove(event: RemoveEvent<any>): void;
1660
+ afterTransactionCommit(event: TransactionCommitEvent): void;
1661
+ afterTransactionRollback(event: TransactionRollbackEvent): void;
1662
+ private collect;
1663
+ }
1664
+
1665
+ /**
1666
+ * Трансформер колонок `numeric`, которые в домене — число: Postgres отдаёт
1667
+ * numeric строкой, а сущности и арифметика работают с `number`. Годится для
1668
+ * количеств (0.5 кг) и сумм, которые домен держит числом (сумма платежа);
1669
+ * денежные поля, которые домен держит строкой актива, его не используют.
1670
+ *
1671
+ * Один экземпляр на ядро и расширения — из пакета (из typeorm здесь только тип).
1672
+ */
1673
+ declare const numericColumnTransformer: ValueTransformer;
1674
+
1675
+ /**
1676
+ * Текст, который уходит в подписываемый документ (повестка собрания, проект
1677
+ * решения совета), без исполняемой разметки.
1678
+ *
1679
+ * Вёрстка допускается: проекты решений на утверждение совета несут абзацы,
1680
+ * таблицы и стили документа. Отклоняются только скрипты, встраиваемые рамки и
1681
+ * объекты, формы, обработчики событий и опасные ссылки — правило одно с
1682
+ * очисткой редактора (`_Common.Text.hasExecutableMarkup` в cooptypes).
1683
+ *
1684
+ * Отказ, а не очистка: документ подписывается по хэшу, и переписанный текст
1685
+ * был бы не тем, что ввёл человек (решение владельца 25.09.2026, C28-80).
1686
+ */
1687
+ declare function SafeMarkup(validationOptions?: ValidationOptions): (object: object, propertyName: string) => void;
1688
+
1634
1689
  /**
1635
1690
  * Ошибка API с кодом состояния HTTP.
1636
1691
  *
@@ -1647,35 +1702,6 @@ declare class HttpApiError extends HttpException {
1647
1702
  constructor(statusCode: HttpStatus, message: string, isOperational?: boolean, stack?: string, subcode?: any);
1648
1703
  }
1649
1704
 
1650
- /**
1651
- * Разбор отказа контракта. Контракт пишет причину как `КОД: текст`
1652
- * (`GATEWAY_OUTCOME_NOT_FOUND: Объект возврата не существует…`); старые
1653
- * отказы — одним текстом, без кода.
1654
- */
1655
- declare function parseChainAssert(message: string): {
1656
- code?: string;
1657
- text: string;
1658
- };
1659
- /**
1660
- * Код отказа контракта — из DomainError, в который отказ уже превращён,
1661
- * или из сырой ошибки цепи. Отказ без кода — undefined. Сравнивать отказы
1662
- * контракта — по этому коду, а не по тексту: текст переводится и правится.
1663
- */
1664
- declare function chainErrorCode(error: unknown): string | undefined;
1665
- /**
1666
- * Пробросить отказ цепи человеку.
1667
- *
1668
- * `eosio::check` возвращает сообщение обёрнутым в `assertion failure with
1669
- * message: ...`; пайщику нужна причина, а не обёртка виртуальной машины.
1670
- * Живёт в каркасе, потому что в цепь ходит не одно расширение: вторая копия
1671
- * этой чистки разошлась бы с первой на первом же новом формате ошибки.
1672
- *
1673
- * Отказ с кодом (`КОД: текст`) уходит с этим кодом, текст — из словаря,
1674
- * а без записи в словаре — текст контракта. Отказ без кода — `CHAIN_ASSERT`,
1675
- * прочий сбой цепи — `CHAIN_ERROR`, текст — параметром `message`.
1676
- */
1677
- declare function rethrowChainError(error: unknown): never;
1678
-
1679
1705
  type DomainErrorParams = Record<string, string | number | boolean | null | undefined>;
1680
1706
  interface Branded {
1681
1707
  code: string;
@@ -1733,5 +1759,43 @@ declare function validationMessage(key: string, params?: DomainErrorParams): (ar
1733
1759
  constraints?: unknown[];
1734
1760
  }) => string;
1735
1761
 
1736
- 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 };
1737
- export type { AuthRolesOptions, BucketSource, BucketSpec, DeserializedDescriptionOfExtension, DomainErrorParams, EventSource, EventWait, ExcludeCommonProps, ExtensionAppTerminatePayload, ExtensionAuthOptions, ExtensionConfigFieldPolicy, ExtensionConfigPolicy, ExtensionDatabaseMigrationClass, ExtensionDomainInterface, ExtensionDomainRepository, ExtensionEntityClass, ExtensionSchemaMigrationAfterContext, GeneratedDocumentDomainInterface, GeneratedDocumentDraft, IDesktopConfig, IExtensionSchemaMigration, IRegistryExtension, IResolvedRegistryExtension, LoadGeneratedDocumentByDocHash, LogExtensionDomainInterface, LogExtensionDomainRepository, LogExtensionFilter, LogExtensionPaginationOptions, LogExtensionPaginationResult, PaymentDetails, PlatformBlockchainSettings, PlatformSettings, RegisteredBucket, RequireFields, SignedDocumentMetaCompareMode, SignedDocumentMetaVerification, SignedDocumentToVerify, TransactResultWithResponse };
1762
+ /**
1763
+ * Разбор отказа контракта. Контракт пишет причину как `КОД: текст`
1764
+ * (`GATEWAY_OUTCOME_NOT_FOUND: Объект возврата не существует…`); старые
1765
+ * отказы — одним текстом, без кода.
1766
+ */
1767
+ declare function parseChainAssert(message: string): {
1768
+ code?: string;
1769
+ text: string;
1770
+ };
1771
+ /**
1772
+ * Код отказа контракта — из DomainError, в который отказ уже превращён,
1773
+ * или из сырой ошибки цепи. Отказ без кода — undefined. Сравнивать отказы
1774
+ * контракта — по этому коду, а не по тексту: текст переводится и правится.
1775
+ */
1776
+ declare function chainErrorCode(error: unknown): string | undefined;
1777
+ /**
1778
+ * Отказ контракта (`eosio::check`) как отказ с кодом; иной сбой цепи — null.
1779
+ *
1780
+ * Отказ контракта — ответ на действие пайщика, а не сбой сервера. Клиент цепи
1781
+ * (@wharfkit/session) превращает его в простой Error, и без перевода он уходил
1782
+ * ответом 500 без кода — в журнал ошибок как поломка (C28-80). Переводит
1783
+ * единственная отправка в цепь, поэтому код получают все мутации сразу.
1784
+ */
1785
+ declare function chainRefusalOf(error: unknown): DomainError | null;
1786
+ /**
1787
+ * Пробросить отказ цепи человеку.
1788
+ *
1789
+ * `eosio::check` возвращает сообщение обёрнутым в `assertion failure with
1790
+ * message: ...`; пайщику нужна причина, а не обёртка виртуальной машины.
1791
+ * Живёт в каркасе, потому что в цепь ходит не одно расширение: вторая копия
1792
+ * этой чистки разошлась бы с первой на первом же новом формате ошибки.
1793
+ *
1794
+ * Отказ с кодом (`КОД: текст`) уходит с этим кодом, текст — из словаря,
1795
+ * а без записи в словаре — текст контракта. Отказ без кода — `CHAIN_ASSERT`,
1796
+ * прочий сбой цепи — `CHAIN_ERROR`, текст — параметром `message`.
1797
+ */
1798
+ declare function rethrowChainError(error: unknown): never;
1799
+
1800
+ 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, LocalChangesCollector, 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, SafeMarkup, SignatureInfoDTO, SignatureInfoInputDTO, SignedDigitalDocumentDTO, SignedDigitalDocumentInputDTO, TransactionDTO, UseBucket, bucketProvidersFor, bucketTokenFor, buildPaginationResult, chainErrorCode, chainRefusalOf, chainTextDigest, checkPaymentAmount, checkPaymentSymbol, computeOnboardingExpiresAt, configureExtensionAuth, configurePlatformSettings, createPaginationResult, domainErrorMessage, extensionDatabaseMigrations, extensionEntities, generateHashFromString, generateRandomHash, generateUniqueHash, getAmountPlusFee, getAppliedBlockNum, hasServerSecret, isChainTextDigest, isChainTextOf, isExtensionAvailable, mergeSecretConfig, numericColumnTransformer, paginationInputToOffset, parseChainAssert, platformSettings, providerSuppliedPaths, redactSecretConfig, registerExtensionDatabaseMigrations, registerExtensionEntities, resetExtensionDatabaseMigrations, resetExtensionEntities, resolveChainText, resolveSortColumn, rethrowChainError, sanitizeUserText, validationMessage, verifySignedDocumentAgainstStoredDraft, waitForEvent };
1801
+ export type { AuthRolesOptions, BucketSource, BucketSpec, DeserializedDescriptionOfExtension, DomainErrorParams, EventSource, EventWait, ExcludeCommonProps, ExtensionAppTerminatePayload, ExtensionAuthOptions, ExtensionConfigFieldPolicy, ExtensionConfigPolicy, ExtensionDatabaseMigrationClass, ExtensionDomainInterface, ExtensionDomainRepository, ExtensionEntityClass, ExtensionSchemaMigrationAfterContext, GeneratedDocumentDomainInterface, GeneratedDocumentDraft, IDesktopConfig, IExtensionSchemaMigration, IRegistryExtension, IResolvedRegistryExtension, LoadGeneratedDocumentByDocHash, LocalChangePublisher, LogExtensionDomainInterface, LogExtensionDomainRepository, LogExtensionFilter, LogExtensionPaginationOptions, LogExtensionPaginationResult, PaymentDetails, PlatformBlockchainSettings, PlatformSettings, RegisteredBucket, RequireFields, SignedDocumentMetaCompareMode, SignedDocumentMetaVerification, SignedDocumentToVerify, TransactResultWithResponse };
package/dist/index.mjs CHANGED
@@ -5,7 +5,7 @@ import { Reflector } from '@nestjs/core';
5
5
  import { AuthGuard } from '@nestjs/passport';
6
6
  import { timingSafeEqual } from 'node:crypto';
7
7
  import { z } from 'zod';
8
- import { IsNumber, IsString, IsArray, ArrayMinSize, ValidateNested, IsEnum, IsInt, IsOptional, Matches, IsNotEmpty, IsObject } from 'class-validator';
8
+ import { IsNumber, IsString, IsArray, ArrayMinSize, ValidateNested, IsEnum, IsInt, IsOptional, Matches, IsNotEmpty, IsIn, IsObject, registerDecorator } from 'class-validator';
9
9
  import { Type } from 'class-transformer';
10
10
  import { GraphQLJSON } from 'graphql-type-json';
11
11
  import { Classes } from '@coopenomics/sdk';
@@ -1078,7 +1078,9 @@ __decorateClass$7([
1078
1078
  Field(() => String, {
1079
1079
  description: '\u041D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u0441\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u043A\u0438 ("ASC" \u0438\u043B\u0438 "DESC")',
1080
1080
  defaultValue: "ASC"
1081
- })
1081
+ }),
1082
+ IsOptional(),
1083
+ IsIn(["ASC", "DESC"], { message: validationMessage("kit.pagination.invalidSortOrder") })
1082
1084
  ], PaginationInputDTO.prototype, "sortOrder", 2);
1083
1085
  PaginationInputDTO = __decorateClass$7([
1084
1086
  InputType("PaginationInput")
@@ -1146,16 +1148,16 @@ class PaginationUtils {
1146
1148
  static validatePaginationOptions(options) {
1147
1149
  const { page = 1, limit = 10, sortBy, sortOrder = "ASC" } = options;
1148
1150
  if (page < 1) {
1149
- throw DomainError.internal("KIT_PAGE_NUMBER_INVALID");
1151
+ throw DomainError.badRequest("KIT_PAGE_NUMBER_INVALID");
1150
1152
  }
1151
1153
  if (limit < 1 || limit > 1e3) {
1152
- throw DomainError.internal("KIT_PAGE_LIMIT_INVALID");
1154
+ throw DomainError.badRequest("KIT_PAGE_LIMIT_INVALID");
1153
1155
  }
1154
1156
  if (sortOrder !== "ASC" && sortOrder !== "DESC") {
1155
- throw DomainError.internal("KIT_SORT_ORDER_INVALID");
1157
+ throw DomainError.badRequest("KIT_SORT_ORDER_INVALID");
1156
1158
  }
1157
1159
  if (sortBy !== void 0 && sortBy !== null && !SORT_FIELD_PATTERN.test(sortBy)) {
1158
- throw DomainError.internal("KIT_SORT_FIELD_INVALID");
1160
+ throw DomainError.badRequest("KIT_SORT_FIELD_INVALID");
1159
1161
  }
1160
1162
  return {
1161
1163
  page,
@@ -1525,7 +1527,7 @@ class CurrencyValidationUtil {
1525
1527
  static validateCurrencySymbol(amount, fieldName = t("kit.currency.defaultFieldName")) {
1526
1528
  if (!this.hasValidCurrencySymbol(amount)) {
1527
1529
  const expectedSymbol = platformSettings().blockchain.rootGovernSymbol;
1528
- throw DomainError.internal("KIT_CURRENCY_SYMBOL_INVALID", { fieldName, expectedSymbol });
1530
+ throw DomainError.badRequest("KIT_CURRENCY_SYMBOL_INVALID", { fieldName, expectedSymbol });
1529
1531
  }
1530
1532
  }
1531
1533
  /**
@@ -1670,12 +1672,12 @@ let DomainToBlockchainUtils = class {
1670
1672
  formatQuantityWithPrecision(quantity) {
1671
1673
  const parts = quantity.split(" ");
1672
1674
  if (parts.length !== 2) {
1673
- throw DomainError.internal("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1675
+ throw DomainError.badRequest("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1674
1676
  }
1675
1677
  const [amount, symbol] = parts;
1676
1678
  const numericAmount = parseFloat(amount);
1677
1679
  if (isNaN(numericAmount)) {
1678
- throw DomainError.internal("KIT_QUANTITY_AMOUNT_INVALID", { amount });
1680
+ throw DomainError.badRequest("KIT_QUANTITY_AMOUNT_INVALID", { amount });
1679
1681
  }
1680
1682
  const { rootSymbol, rootPrecision, rootGovernSymbol, rootGovernPrecision } = platformSettings().blockchain;
1681
1683
  let precision;
@@ -1684,7 +1686,7 @@ let DomainToBlockchainUtils = class {
1684
1686
  } else if (symbol === rootGovernSymbol) {
1685
1687
  precision = rootGovernPrecision;
1686
1688
  } else {
1687
- throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1689
+ throw DomainError.badRequest("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1688
1690
  }
1689
1691
  return `${numericAmount.toFixed(precision)} ${symbol}`;
1690
1692
  }
@@ -1692,10 +1694,10 @@ let DomainToBlockchainUtils = class {
1692
1694
  formatNumericStringToAssetString(numericString, precision, symbol) {
1693
1695
  const numericValue = parseFloat(numericString);
1694
1696
  if (isNaN(numericValue)) {
1695
- throw DomainError.internal("KIT_NUMBER_INVALID", { value: numericString });
1697
+ throw DomainError.badRequest("KIT_NUMBER_INVALID", { value: numericString });
1696
1698
  }
1697
1699
  if (numericValue < 0) {
1698
- throw DomainError.internal("KIT_NUMBER_NEGATIVE", { value: numericString });
1700
+ throw DomainError.badRequest("KIT_NUMBER_NEGATIVE", { value: numericString });
1699
1701
  }
1700
1702
  return `${numericValue.toFixed(precision)} ${symbol}`;
1701
1703
  }
@@ -1726,7 +1728,7 @@ DomainToBlockchainUtils = __decorateClass([
1726
1728
  Injectable()
1727
1729
  ], DomainToBlockchainUtils);
1728
1730
 
1729
- const DIGEST_RE = /^[0-9a-f]{64}$/;
1731
+ const DIGEST_RE = /^[0-9a-f]{64}$/i;
1730
1732
  function chainTextDigest(text) {
1731
1733
  if (!text)
1732
1734
  return "";
@@ -1735,6 +1737,9 @@ function chainTextDigest(text) {
1735
1737
  function isChainTextDigest(value) {
1736
1738
  return typeof value === "string" && DIGEST_RE.test(value);
1737
1739
  }
1740
+ function isChainTextOf(text, chainValue) {
1741
+ return chainTextDigest(text) === (chainValue ?? "").toLowerCase();
1742
+ }
1738
1743
  function resolveChainText(chainValue, current) {
1739
1744
  return isChainTextDigest(chainValue) ? current ?? "" : chainValue;
1740
1745
  }
@@ -1825,17 +1830,17 @@ class AmountFormatterUtils {
1825
1830
  */
1826
1831
  static formatAmount(amountStr) {
1827
1832
  if (!amountStr || typeof amountStr !== "string") {
1828
- throw DomainError.internal("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1833
+ throw DomainError.badRequest("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1829
1834
  }
1830
1835
  const parts = amountStr.trim().split(/\s+/);
1831
1836
  if (parts.length < 1 || !parts[0]) {
1832
- throw DomainError.internal("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1837
+ throw DomainError.badRequest("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1833
1838
  }
1834
1839
  const amountPart = parts[0].replace(",", ".");
1835
1840
  const currency = parts.length >= 2 ? parts.slice(1).join(" ") : "";
1836
1841
  const amount = parseFloat(amountPart);
1837
1842
  if (isNaN(amount)) {
1838
- throw DomainError.internal("KIT_AMOUNT_NUMBER_INVALID", { value: parts[0] });
1843
+ throw DomainError.badRequest("KIT_AMOUNT_NUMBER_INVALID", { value: parts[0] });
1839
1844
  }
1840
1845
  const formattedAmount = amount.toLocaleString("ru-RU", {
1841
1846
  minimumFractionDigits: 2,
@@ -1911,7 +1916,7 @@ class QuantityUtils {
1911
1916
  } else if (symbol === rootGovernSymbol) {
1912
1917
  return rootGovernPrecision;
1913
1918
  } else {
1914
- throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1919
+ throw DomainError.badRequest("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1915
1920
  }
1916
1921
  }
1917
1922
  /**
@@ -1921,7 +1926,7 @@ class QuantityUtils {
1921
1926
  static validateSymbol(symbol) {
1922
1927
  if (!this.isSupportedSymbol(symbol)) {
1923
1928
  const { rootSymbol, rootGovernSymbol } = platformSettings().blockchain;
1924
- throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1929
+ throw DomainError.badRequest("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1925
1930
  }
1926
1931
  }
1927
1932
  /**
@@ -1933,7 +1938,7 @@ class QuantityUtils {
1933
1938
  static formatQuantityForBlockchain(amount, symbol) {
1934
1939
  this.validateSymbol(symbol);
1935
1940
  if (isNaN(amount) || amount < 0) {
1936
- throw DomainError.internal("KIT_NUMBER_INVALID", { value: amount });
1941
+ throw DomainError.badRequest("KIT_NUMBER_INVALID", { value: amount });
1937
1942
  }
1938
1943
  const precision = this.getPrecisionForSymbol(symbol);
1939
1944
  const formattedAmount = amount.toFixed(precision);
@@ -1948,7 +1953,7 @@ class QuantityUtils {
1948
1953
  static combineQuantityAndSymbol(amount, symbol) {
1949
1954
  this.validateSymbol(symbol);
1950
1955
  if (isNaN(amount) || amount < 0) {
1951
- throw DomainError.internal("KIT_NUMBER_INVALID", { value: amount });
1956
+ throw DomainError.badRequest("KIT_NUMBER_INVALID", { value: amount });
1952
1957
  }
1953
1958
  return `${amount} ${symbol}`;
1954
1959
  }
@@ -1960,12 +1965,12 @@ class QuantityUtils {
1960
1965
  static parseQuantityString(quantity) {
1961
1966
  const parts = quantity.split(" ");
1962
1967
  if (parts.length !== 2) {
1963
- throw DomainError.internal("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1968
+ throw DomainError.badRequest("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1964
1969
  }
1965
1970
  const [amountStr, symbol] = parts;
1966
1971
  const amount = parseFloat(amountStr);
1967
1972
  if (isNaN(amount)) {
1968
- throw DomainError.internal("KIT_QUANTITY_AMOUNT_INVALID", { amount: amountStr });
1973
+ throw DomainError.badRequest("KIT_QUANTITY_AMOUNT_INVALID", { amount: amountStr });
1969
1974
  }
1970
1975
  this.validateSymbol(symbol);
1971
1976
  return { amount, symbol };
@@ -2086,6 +2091,79 @@ function resolveSortColumn(repository, sortBy, fallback) {
2086
2091
  return isKnownColumn ? sortBy : fallback;
2087
2092
  }
2088
2093
 
2094
+ class LocalChangesCollector {
2095
+ constructor(isWatched, publish) {
2096
+ this.isWatched = isWatched;
2097
+ this.publish = publish;
2098
+ this.pending = /* @__PURE__ */ new WeakMap();
2099
+ }
2100
+ afterInsert(event) {
2101
+ this.collect(event.metadata, event.queryRunner, event.entity);
2102
+ }
2103
+ afterUpdate(event) {
2104
+ this.collect(event.metadata, event.queryRunner, { ...event.databaseEntity ?? {}, ...event.entity ?? {} });
2105
+ }
2106
+ afterRemove(event) {
2107
+ const row = event.databaseEntity ?? event.entity;
2108
+ this.collect(event.metadata, event.queryRunner, row, event.entityId);
2109
+ }
2110
+ afterTransactionCommit(event) {
2111
+ const changes = this.pending.get(event.queryRunner);
2112
+ if (!changes)
2113
+ return;
2114
+ this.pending.delete(event.queryRunner);
2115
+ changes.forEach((c) => void this.publish(c.table, c.primary_key, c.row));
2116
+ }
2117
+ afterTransactionRollback(event) {
2118
+ this.pending.delete(event.queryRunner);
2119
+ }
2120
+ collect(metadata, queryRunner, row, entityId) {
2121
+ const table = metadata.tableName;
2122
+ if (!this.isWatched(table))
2123
+ return;
2124
+ const change = { table, primary_key: primaryKeyOf(metadata, row, entityId), row };
2125
+ if (queryRunner?.isTransactionActive) {
2126
+ const list = this.pending.get(queryRunner) ?? [];
2127
+ list.push(change);
2128
+ this.pending.set(queryRunner, list);
2129
+ return;
2130
+ }
2131
+ void this.publish(change.table, change.primary_key, change.row);
2132
+ }
2133
+ }
2134
+ function primaryKeyOf(metadata, row, entityId) {
2135
+ if (row) {
2136
+ const ids = metadata.primaryColumns.map((c) => c.getEntityValue(row)).filter((v) => v !== void 0 && v !== null);
2137
+ if (ids.length)
2138
+ return ids.map(String).join(":");
2139
+ }
2140
+ if (entityId !== void 0 && entityId !== null) {
2141
+ return typeof entityId === "object" ? Object.values(entityId).map(String).join(":") : String(entityId);
2142
+ }
2143
+ return "";
2144
+ }
2145
+
2146
+ const numericColumnTransformer = {
2147
+ to: (value) => value,
2148
+ from: (value) => value === null || value === void 0 ? value : Number(value)
2149
+ };
2150
+
2151
+ function SafeMarkup(validationOptions) {
2152
+ return function(object, propertyName) {
2153
+ registerDecorator({
2154
+ name: "safeMarkup",
2155
+ target: object.constructor,
2156
+ propertyName,
2157
+ options: { message: validationMessage("kit.markup.executable"), ...validationOptions },
2158
+ validator: {
2159
+ validate(value) {
2160
+ return typeof value !== "string" || !_Common.Text.hasExecutableMarkup(value);
2161
+ }
2162
+ }
2163
+ });
2164
+ };
2165
+ }
2166
+
2089
2167
  class HttpApiError extends HttpException {
2090
2168
  constructor(statusCode, message, isOperational = true, stack, subcode) {
2091
2169
  super(message, statusCode);
@@ -2101,6 +2179,7 @@ class HttpApiError extends HttpException {
2101
2179
 
2102
2180
  const ASSERT_PREFIX = /assertion failure with message: (.+?)(?:\n|$)/;
2103
2181
  const CODED = /^([A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+):\s*(.*)$/s;
2182
+ const ENCODING = /^Encoding error at root<[^>]*>((?:\.[^.<:]+<[^>]*>)*):/;
2104
2183
  function parseChainAssert(message) {
2105
2184
  const assert = message.match(ASSERT_PREFIX);
2106
2185
  const body = (assert ? assert[1] : message).trim();
@@ -2116,7 +2195,23 @@ function chainErrorCode(error) {
2116
2195
  const raw = error?.message ?? String(error);
2117
2196
  return parseChainAssert(raw).code;
2118
2197
  }
2198
+ function chainRefusalOf(error) {
2199
+ if (error instanceof DomainError)
2200
+ return error;
2201
+ const raw = error?.message ?? String(error);
2202
+ const encoding = raw.match(ENCODING);
2203
+ if (encoding) {
2204
+ const field = encoding[1].split(".").filter(Boolean).map((part) => part.replace(/<[^>]*>$/, "")).join(".");
2205
+ return DomainError.badRequest("CHAIN_ACTION_ARGUMENT_INVALID", { field });
2206
+ }
2207
+ if (!ASSERT_PREFIX.test(raw))
2208
+ return null;
2209
+ const { code, text } = parseChainAssert(raw);
2210
+ return DomainError.badRequest(code ?? "CHAIN_ASSERT", { message: text });
2211
+ }
2119
2212
  function rethrowChainError(error) {
2213
+ if (error instanceof DomainError)
2214
+ throw error;
2120
2215
  const raw = error?.message ?? String(error);
2121
2216
  const match = raw.match(ASSERT_PREFIX);
2122
2217
  if (!match)
@@ -2125,4 +2220,4 @@ function rethrowChainError(error) {
2125
2220
  throw DomainError.badRequest(code ?? "CHAIN_ASSERT", { message: text });
2126
2221
  }
2127
2222
 
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 };
2223
+ 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, LocalChangesCollector, 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, SafeMarkup, SignatureInfoDTO, SignatureInfoInputDTO, SignedDigitalDocumentDTO, SignedDigitalDocumentInputDTO, TransactionDTO, UseBucket, bucketProvidersFor, bucketTokenFor, buildPaginationResult, chainErrorCode, chainRefusalOf, chainTextDigest, checkPaymentAmount, checkPaymentSymbol, computeOnboardingExpiresAt, configureExtensionAuth, configurePlatformSettings, createPaginationResult, domainErrorMessage, extensionDatabaseMigrations, extensionEntities, generateHashFromString, generateRandomHash, generateUniqueHash, getAmountPlusFee, getAppliedBlockNum, hasServerSecret, isChainTextDigest, isChainTextOf, isExtensionAvailable, mergeSecretConfig, numericColumnTransformer, paginationInputToOffset, parseChainAssert, platformSettings, providerSuppliedPaths, redactSecretConfig, registerExtensionDatabaseMigrations, registerExtensionEntities, resetExtensionDatabaseMigrations, resetExtensionEntities, resolveChainText, resolveSortColumn, rethrowChainError, sanitizeUserText, validationMessage, verifySignedDocumentAgainstStoredDraft, waitForEvent };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coopenomics/extension-kit",
3
3
  "type": "module",
4
- "version": "2026.9.24-3",
4
+ "version": "2026.9.30",
5
5
  "description": "Служебный каркас расширения цифрового кооператива: базовый модуль, сущности и репозитории расширения, авторизация, каркас блокчейн-синхронизации, контракт миграций схемы, контракт реестра",
6
6
  "author": "Alex Ant <dacom.dark.sun@gmail.com>",
7
7
  "license": "MIT",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@coopenomics/i18n": "workspace:*",
42
- "@coopenomics/sdk": "2026.9.24-3",
42
+ "@coopenomics/sdk": "2026.9.30",
43
43
  "@nestjs/common": "^10.0.0",
44
44
  "@nestjs/core": "^10.0.0",
45
45
  "@nestjs/graphql": "^12.2.1",
@@ -60,7 +60,7 @@
60
60
  "zod": "^3.0.0"
61
61
  },
62
62
  "devDependencies": {
63
- "@coopenomics/i18n": "2026.9.24-3",
63
+ "@coopenomics/i18n": "2026.9.30",
64
64
  "@nestjs/common": "^10.0.0",
65
65
  "@nestjs/core": "^10.0.0",
66
66
  "@nestjs/graphql": "^12.2.1",
@@ -70,7 +70,7 @@
70
70
  "@wharfkit/session": "^1.4.0",
71
71
  "class-transformer": "^0.5.1",
72
72
  "class-validator": "^0.14.1",
73
- "cooptypes": "2026.9.24-3",
73
+ "cooptypes": "2026.9.30",
74
74
  "graphql": "^16.9.0",
75
75
  "graphql-type-json": "^0.3.2",
76
76
  "moment": "^2.30.1",
@@ -82,5 +82,5 @@
82
82
  "unbuild": "^2.0.0",
83
83
  "zod": "^3.0.0"
84
84
  },
85
- "gitHead": "66942b7c4f7f59cecfc9b4bd2dc7cac90bf40782"
85
+ "gitHead": "1346a804b5b9daec565a4bea5104510a6c832b0e"
86
86
  }