@coopenomics/extension-kit 2026.9.24-2 → 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
  /**
@@ -1564,12 +1566,13 @@ class CurrencyValidationUtil {
1564
1566
  /**
1565
1567
  * Форматирует сумму с правильным символом валюты
1566
1568
  * @param value Числовое значение
1567
- * @param precision Количество знаков после запятой (по умолчанию из конфига)
1569
+ * @param precision Количество знаков после запятой (по умолчанию из настроек платформы)
1568
1570
  * @returns string - отформатированная сумма
1569
1571
  */
1570
1572
  static formatAmount(value, precision) {
1571
- const actualPrecision = precision ?? config.blockchain.root_govern_precision;
1572
- const symbol = platformSettings().blockchain.rootGovernSymbol;
1573
+ const { rootGovernPrecision, rootGovernSymbol } = platformSettings().blockchain;
1574
+ const actualPrecision = precision ?? rootGovernPrecision;
1575
+ const symbol = rootGovernSymbol;
1573
1576
  return `${value.toFixed(actualPrecision)} ${symbol}`;
1574
1577
  }
1575
1578
  }
@@ -1677,12 +1680,12 @@ exports.DomainToBlockchainUtils = class DomainToBlockchainUtils {
1677
1680
  formatQuantityWithPrecision(quantity) {
1678
1681
  const parts = quantity.split(" ");
1679
1682
  if (parts.length !== 2) {
1680
- throw DomainError.internal("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1683
+ throw DomainError.badRequest("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1681
1684
  }
1682
1685
  const [amount, symbol] = parts;
1683
1686
  const numericAmount = parseFloat(amount);
1684
1687
  if (isNaN(numericAmount)) {
1685
- throw DomainError.internal("KIT_QUANTITY_AMOUNT_INVALID", { amount });
1688
+ throw DomainError.badRequest("KIT_QUANTITY_AMOUNT_INVALID", { amount });
1686
1689
  }
1687
1690
  const { rootSymbol, rootPrecision, rootGovernSymbol, rootGovernPrecision } = platformSettings().blockchain;
1688
1691
  let precision;
@@ -1691,7 +1694,7 @@ exports.DomainToBlockchainUtils = class DomainToBlockchainUtils {
1691
1694
  } else if (symbol === rootGovernSymbol) {
1692
1695
  precision = rootGovernPrecision;
1693
1696
  } else {
1694
- throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1697
+ throw DomainError.badRequest("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1695
1698
  }
1696
1699
  return `${numericAmount.toFixed(precision)} ${symbol}`;
1697
1700
  }
@@ -1699,10 +1702,10 @@ exports.DomainToBlockchainUtils = class DomainToBlockchainUtils {
1699
1702
  formatNumericStringToAssetString(numericString, precision, symbol) {
1700
1703
  const numericValue = parseFloat(numericString);
1701
1704
  if (isNaN(numericValue)) {
1702
- throw DomainError.internal("KIT_NUMBER_INVALID", { value: numericString });
1705
+ throw DomainError.badRequest("KIT_NUMBER_INVALID", { value: numericString });
1703
1706
  }
1704
1707
  if (numericValue < 0) {
1705
- throw DomainError.internal("KIT_NUMBER_NEGATIVE", { value: numericString });
1708
+ throw DomainError.badRequest("KIT_NUMBER_NEGATIVE", { value: numericString });
1706
1709
  }
1707
1710
  return `${numericValue.toFixed(precision)} ${symbol}`;
1708
1711
  }
@@ -1733,7 +1736,7 @@ exports.DomainToBlockchainUtils = __decorateClass([
1733
1736
  common.Injectable()
1734
1737
  ], exports.DomainToBlockchainUtils);
1735
1738
 
1736
- const DIGEST_RE = /^[0-9a-f]{64}$/;
1739
+ const DIGEST_RE = /^[0-9a-f]{64}$/i;
1737
1740
  function chainTextDigest(text) {
1738
1741
  if (!text)
1739
1742
  return "";
@@ -1742,6 +1745,9 @@ function chainTextDigest(text) {
1742
1745
  function isChainTextDigest(value) {
1743
1746
  return typeof value === "string" && DIGEST_RE.test(value);
1744
1747
  }
1748
+ function isChainTextOf(text, chainValue) {
1749
+ return chainTextDigest(text) === (chainValue ?? "").toLowerCase();
1750
+ }
1745
1751
  function resolveChainText(chainValue, current) {
1746
1752
  return isChainTextDigest(chainValue) ? current ?? "" : chainValue;
1747
1753
  }
@@ -1832,17 +1838,17 @@ class AmountFormatterUtils {
1832
1838
  */
1833
1839
  static formatAmount(amountStr) {
1834
1840
  if (!amountStr || typeof amountStr !== "string") {
1835
- throw DomainError.internal("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1841
+ throw DomainError.badRequest("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1836
1842
  }
1837
1843
  const parts = amountStr.trim().split(/\s+/);
1838
1844
  if (parts.length < 1 || !parts[0]) {
1839
- throw DomainError.internal("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1845
+ throw DomainError.badRequest("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1840
1846
  }
1841
1847
  const amountPart = parts[0].replace(",", ".");
1842
1848
  const currency = parts.length >= 2 ? parts.slice(1).join(" ") : "";
1843
1849
  const amount = parseFloat(amountPart);
1844
1850
  if (isNaN(amount)) {
1845
- throw DomainError.internal("KIT_AMOUNT_NUMBER_INVALID", { value: parts[0] });
1851
+ throw DomainError.badRequest("KIT_AMOUNT_NUMBER_INVALID", { value: parts[0] });
1846
1852
  }
1847
1853
  const formattedAmount = amount.toLocaleString("ru-RU", {
1848
1854
  minimumFractionDigits: 2,
@@ -1918,7 +1924,7 @@ class QuantityUtils {
1918
1924
  } else if (symbol === rootGovernSymbol) {
1919
1925
  return rootGovernPrecision;
1920
1926
  } else {
1921
- throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1927
+ throw DomainError.badRequest("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1922
1928
  }
1923
1929
  }
1924
1930
  /**
@@ -1928,7 +1934,7 @@ class QuantityUtils {
1928
1934
  static validateSymbol(symbol) {
1929
1935
  if (!this.isSupportedSymbol(symbol)) {
1930
1936
  const { rootSymbol, rootGovernSymbol } = platformSettings().blockchain;
1931
- throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1937
+ throw DomainError.badRequest("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1932
1938
  }
1933
1939
  }
1934
1940
  /**
@@ -1940,7 +1946,7 @@ class QuantityUtils {
1940
1946
  static formatQuantityForBlockchain(amount, symbol) {
1941
1947
  this.validateSymbol(symbol);
1942
1948
  if (isNaN(amount) || amount < 0) {
1943
- throw DomainError.internal("KIT_NUMBER_INVALID", { value: amount });
1949
+ throw DomainError.badRequest("KIT_NUMBER_INVALID", { value: amount });
1944
1950
  }
1945
1951
  const precision = this.getPrecisionForSymbol(symbol);
1946
1952
  const formattedAmount = amount.toFixed(precision);
@@ -1955,7 +1961,7 @@ class QuantityUtils {
1955
1961
  static combineQuantityAndSymbol(amount, symbol) {
1956
1962
  this.validateSymbol(symbol);
1957
1963
  if (isNaN(amount) || amount < 0) {
1958
- throw DomainError.internal("KIT_NUMBER_INVALID", { value: amount });
1964
+ throw DomainError.badRequest("KIT_NUMBER_INVALID", { value: amount });
1959
1965
  }
1960
1966
  return `${amount} ${symbol}`;
1961
1967
  }
@@ -1967,12 +1973,12 @@ class QuantityUtils {
1967
1973
  static parseQuantityString(quantity) {
1968
1974
  const parts = quantity.split(" ");
1969
1975
  if (parts.length !== 2) {
1970
- throw DomainError.internal("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1976
+ throw DomainError.badRequest("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1971
1977
  }
1972
1978
  const [amountStr, symbol] = parts;
1973
1979
  const amount = parseFloat(amountStr);
1974
1980
  if (isNaN(amount)) {
1975
- throw DomainError.internal("KIT_QUANTITY_AMOUNT_INVALID", { amount: amountStr });
1981
+ throw DomainError.badRequest("KIT_QUANTITY_AMOUNT_INVALID", { amount: amountStr });
1976
1982
  }
1977
1983
  this.validateSymbol(symbol);
1978
1984
  return { amount, symbol };
@@ -2093,6 +2099,79 @@ function resolveSortColumn(repository, sortBy, fallback) {
2093
2099
  return isKnownColumn ? sortBy : fallback;
2094
2100
  }
2095
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
+
2096
2175
  class HttpApiError extends common.HttpException {
2097
2176
  constructor(statusCode, message, isOperational = true, stack, subcode) {
2098
2177
  super(message, statusCode);
@@ -2108,6 +2187,7 @@ class HttpApiError extends common.HttpException {
2108
2187
 
2109
2188
  const ASSERT_PREFIX = /assertion failure with message: (.+?)(?:\n|$)/;
2110
2189
  const CODED = /^([A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+):\s*(.*)$/s;
2190
+ const ENCODING = /^Encoding error at root<[^>]*>((?:\.[^.<:]+<[^>]*>)*):/;
2111
2191
  function parseChainAssert(message) {
2112
2192
  const assert = message.match(ASSERT_PREFIX);
2113
2193
  const body = (assert ? assert[1] : message).trim();
@@ -2123,7 +2203,23 @@ function chainErrorCode(error) {
2123
2203
  const raw = error?.message ?? String(error);
2124
2204
  return parseChainAssert(raw).code;
2125
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
+ }
2126
2220
  function rethrowChainError(error) {
2221
+ if (error instanceof DomainError)
2222
+ throw error;
2127
2223
  const raw = error?.message ?? String(error);
2128
2224
  const match = raw.match(ASSERT_PREFIX);
2129
2225
  if (!match)
@@ -2154,6 +2250,7 @@ exports.HttpApiError = HttpApiError;
2154
2250
  exports.InjectBucket = InjectBucket;
2155
2251
  exports.LOG_EXTENSION_REPOSITORY = LOG_EXTENSION_REPOSITORY;
2156
2252
  exports.LangType = LangType;
2253
+ exports.LocalChangesCollector = LocalChangesCollector;
2157
2254
  exports.LogExtensionDomainEntity = LogExtensionDomainEntity;
2158
2255
  exports.ONBOARDING_EXPIRY_DAYS = ONBOARDING_EXPIRY_DAYS;
2159
2256
  exports.ONBOARDING_EXPIRY_MS = ONBOARDING_EXPIRY_MS;
@@ -2164,11 +2261,13 @@ exports.QuantityUtils = QuantityUtils;
2164
2261
  exports.ROLES_ANY_STATUS_KEY = ROLES_ANY_STATUS_KEY;
2165
2262
  exports.ROLES_DENY_SELF_KEY = ROLES_DENY_SELF_KEY;
2166
2263
  exports.SORT_FIELD_PATTERN = SORT_FIELD_PATTERN;
2264
+ exports.SafeMarkup = SafeMarkup;
2167
2265
  exports.UseBucket = UseBucket;
2168
2266
  exports.bucketProvidersFor = bucketProvidersFor;
2169
2267
  exports.bucketTokenFor = bucketTokenFor;
2170
2268
  exports.buildPaginationResult = buildPaginationResult;
2171
2269
  exports.chainErrorCode = chainErrorCode;
2270
+ exports.chainRefusalOf = chainRefusalOf;
2172
2271
  exports.chainTextDigest = chainTextDigest;
2173
2272
  exports.checkPaymentAmount = checkPaymentAmount;
2174
2273
  exports.checkPaymentSymbol = checkPaymentSymbol;
@@ -2186,8 +2285,10 @@ exports.getAmountPlusFee = getAmountPlusFee;
2186
2285
  exports.getAppliedBlockNum = getAppliedBlockNum;
2187
2286
  exports.hasServerSecret = hasServerSecret;
2188
2287
  exports.isChainTextDigest = isChainTextDigest;
2288
+ exports.isChainTextOf = isChainTextOf;
2189
2289
  exports.isExtensionAvailable = isExtensionAvailable;
2190
2290
  exports.mergeSecretConfig = mergeSecretConfig;
2291
+ exports.numericColumnTransformer = numericColumnTransformer;
2191
2292
  exports.paginationInputToOffset = paginationInputToOffset;
2192
2293
  exports.parseChainAssert = parseChainAssert;
2193
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
  * Строки, записанные до выноса текстов, несут сам текст и копируются как раньше.
@@ -1596,7 +1599,7 @@ declare class CurrencyValidationUtil {
1596
1599
  /**
1597
1600
  * Форматирует сумму с правильным символом валюты
1598
1601
  * @param value Числовое значение
1599
- * @param precision Количество знаков после запятой (по умолчанию из конфига)
1602
+ * @param precision Количество знаков после запятой (по умолчанию из настроек платформы)
1600
1603
  * @returns string - отформатированная сумма
1601
1604
  */
1602
1605
  static formatAmount(value: number, precision?: number): string;
@@ -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
  * Строки, записанные до выноса текстов, несут сам текст и копируются как раньше.
@@ -1596,7 +1599,7 @@ declare class CurrencyValidationUtil {
1596
1599
  /**
1597
1600
  * Форматирует сумму с правильным символом валюты
1598
1601
  * @param value Числовое значение
1599
- * @param precision Количество знаков после запятой (по умолчанию из конфига)
1602
+ * @param precision Количество знаков после запятой (по умолчанию из настроек платформы)
1600
1603
  * @returns string - отформатированная сумма
1601
1604
  */
1602
1605
  static formatAmount(value: number, precision?: number): string;
@@ -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
  * Строки, записанные до выноса текстов, несут сам текст и копируются как раньше.
@@ -1596,7 +1599,7 @@ declare class CurrencyValidationUtil {
1596
1599
  /**
1597
1600
  * Форматирует сумму с правильным символом валюты
1598
1601
  * @param value Числовое значение
1599
- * @param precision Количество знаков после запятой (по умолчанию из конфига)
1602
+ * @param precision Количество знаков после запятой (по умолчанию из настроек платформы)
1600
1603
  * @returns string - отформатированная сумма
1601
1604
  */
1602
1605
  static formatAmount(value: number, precision?: number): string;
@@ -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
  /**
@@ -1556,12 +1558,13 @@ class CurrencyValidationUtil {
1556
1558
  /**
1557
1559
  * Форматирует сумму с правильным символом валюты
1558
1560
  * @param value Числовое значение
1559
- * @param precision Количество знаков после запятой (по умолчанию из конфига)
1561
+ * @param precision Количество знаков после запятой (по умолчанию из настроек платформы)
1560
1562
  * @returns string - отформатированная сумма
1561
1563
  */
1562
1564
  static formatAmount(value, precision) {
1563
- const actualPrecision = precision ?? config.blockchain.root_govern_precision;
1564
- const symbol = platformSettings().blockchain.rootGovernSymbol;
1565
+ const { rootGovernPrecision, rootGovernSymbol } = platformSettings().blockchain;
1566
+ const actualPrecision = precision ?? rootGovernPrecision;
1567
+ const symbol = rootGovernSymbol;
1565
1568
  return `${value.toFixed(actualPrecision)} ${symbol}`;
1566
1569
  }
1567
1570
  }
@@ -1669,12 +1672,12 @@ let DomainToBlockchainUtils = class {
1669
1672
  formatQuantityWithPrecision(quantity) {
1670
1673
  const parts = quantity.split(" ");
1671
1674
  if (parts.length !== 2) {
1672
- throw DomainError.internal("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1675
+ throw DomainError.badRequest("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1673
1676
  }
1674
1677
  const [amount, symbol] = parts;
1675
1678
  const numericAmount = parseFloat(amount);
1676
1679
  if (isNaN(numericAmount)) {
1677
- throw DomainError.internal("KIT_QUANTITY_AMOUNT_INVALID", { amount });
1680
+ throw DomainError.badRequest("KIT_QUANTITY_AMOUNT_INVALID", { amount });
1678
1681
  }
1679
1682
  const { rootSymbol, rootPrecision, rootGovernSymbol, rootGovernPrecision } = platformSettings().blockchain;
1680
1683
  let precision;
@@ -1683,7 +1686,7 @@ let DomainToBlockchainUtils = class {
1683
1686
  } else if (symbol === rootGovernSymbol) {
1684
1687
  precision = rootGovernPrecision;
1685
1688
  } else {
1686
- throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1689
+ throw DomainError.badRequest("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1687
1690
  }
1688
1691
  return `${numericAmount.toFixed(precision)} ${symbol}`;
1689
1692
  }
@@ -1691,10 +1694,10 @@ let DomainToBlockchainUtils = class {
1691
1694
  formatNumericStringToAssetString(numericString, precision, symbol) {
1692
1695
  const numericValue = parseFloat(numericString);
1693
1696
  if (isNaN(numericValue)) {
1694
- throw DomainError.internal("KIT_NUMBER_INVALID", { value: numericString });
1697
+ throw DomainError.badRequest("KIT_NUMBER_INVALID", { value: numericString });
1695
1698
  }
1696
1699
  if (numericValue < 0) {
1697
- throw DomainError.internal("KIT_NUMBER_NEGATIVE", { value: numericString });
1700
+ throw DomainError.badRequest("KIT_NUMBER_NEGATIVE", { value: numericString });
1698
1701
  }
1699
1702
  return `${numericValue.toFixed(precision)} ${symbol}`;
1700
1703
  }
@@ -1725,7 +1728,7 @@ DomainToBlockchainUtils = __decorateClass([
1725
1728
  Injectable()
1726
1729
  ], DomainToBlockchainUtils);
1727
1730
 
1728
- const DIGEST_RE = /^[0-9a-f]{64}$/;
1731
+ const DIGEST_RE = /^[0-9a-f]{64}$/i;
1729
1732
  function chainTextDigest(text) {
1730
1733
  if (!text)
1731
1734
  return "";
@@ -1734,6 +1737,9 @@ function chainTextDigest(text) {
1734
1737
  function isChainTextDigest(value) {
1735
1738
  return typeof value === "string" && DIGEST_RE.test(value);
1736
1739
  }
1740
+ function isChainTextOf(text, chainValue) {
1741
+ return chainTextDigest(text) === (chainValue ?? "").toLowerCase();
1742
+ }
1737
1743
  function resolveChainText(chainValue, current) {
1738
1744
  return isChainTextDigest(chainValue) ? current ?? "" : chainValue;
1739
1745
  }
@@ -1824,17 +1830,17 @@ class AmountFormatterUtils {
1824
1830
  */
1825
1831
  static formatAmount(amountStr) {
1826
1832
  if (!amountStr || typeof amountStr !== "string") {
1827
- throw DomainError.internal("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1833
+ throw DomainError.badRequest("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1828
1834
  }
1829
1835
  const parts = amountStr.trim().split(/\s+/);
1830
1836
  if (parts.length < 1 || !parts[0]) {
1831
- throw DomainError.internal("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1837
+ throw DomainError.badRequest("KIT_AMOUNT_FORMAT_INVALID", { amount: amountStr });
1832
1838
  }
1833
1839
  const amountPart = parts[0].replace(",", ".");
1834
1840
  const currency = parts.length >= 2 ? parts.slice(1).join(" ") : "";
1835
1841
  const amount = parseFloat(amountPart);
1836
1842
  if (isNaN(amount)) {
1837
- throw DomainError.internal("KIT_AMOUNT_NUMBER_INVALID", { value: parts[0] });
1843
+ throw DomainError.badRequest("KIT_AMOUNT_NUMBER_INVALID", { value: parts[0] });
1838
1844
  }
1839
1845
  const formattedAmount = amount.toLocaleString("ru-RU", {
1840
1846
  minimumFractionDigits: 2,
@@ -1910,7 +1916,7 @@ class QuantityUtils {
1910
1916
  } else if (symbol === rootGovernSymbol) {
1911
1917
  return rootGovernPrecision;
1912
1918
  } else {
1913
- throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1919
+ throw DomainError.badRequest("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1914
1920
  }
1915
1921
  }
1916
1922
  /**
@@ -1920,7 +1926,7 @@ class QuantityUtils {
1920
1926
  static validateSymbol(symbol) {
1921
1927
  if (!this.isSupportedSymbol(symbol)) {
1922
1928
  const { rootSymbol, rootGovernSymbol } = platformSettings().blockchain;
1923
- throw DomainError.internal("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1929
+ throw DomainError.badRequest("KIT_SYMBOL_UNSUPPORTED", { symbol, rootSymbol, rootGovernSymbol });
1924
1930
  }
1925
1931
  }
1926
1932
  /**
@@ -1932,7 +1938,7 @@ class QuantityUtils {
1932
1938
  static formatQuantityForBlockchain(amount, symbol) {
1933
1939
  this.validateSymbol(symbol);
1934
1940
  if (isNaN(amount) || amount < 0) {
1935
- throw DomainError.internal("KIT_NUMBER_INVALID", { value: amount });
1941
+ throw DomainError.badRequest("KIT_NUMBER_INVALID", { value: amount });
1936
1942
  }
1937
1943
  const precision = this.getPrecisionForSymbol(symbol);
1938
1944
  const formattedAmount = amount.toFixed(precision);
@@ -1947,7 +1953,7 @@ class QuantityUtils {
1947
1953
  static combineQuantityAndSymbol(amount, symbol) {
1948
1954
  this.validateSymbol(symbol);
1949
1955
  if (isNaN(amount) || amount < 0) {
1950
- throw DomainError.internal("KIT_NUMBER_INVALID", { value: amount });
1956
+ throw DomainError.badRequest("KIT_NUMBER_INVALID", { value: amount });
1951
1957
  }
1952
1958
  return `${amount} ${symbol}`;
1953
1959
  }
@@ -1959,12 +1965,12 @@ class QuantityUtils {
1959
1965
  static parseQuantityString(quantity) {
1960
1966
  const parts = quantity.split(" ");
1961
1967
  if (parts.length !== 2) {
1962
- throw DomainError.internal("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1968
+ throw DomainError.badRequest("KIT_QUANTITY_FORMAT_INVALID", { quantity });
1963
1969
  }
1964
1970
  const [amountStr, symbol] = parts;
1965
1971
  const amount = parseFloat(amountStr);
1966
1972
  if (isNaN(amount)) {
1967
- throw DomainError.internal("KIT_QUANTITY_AMOUNT_INVALID", { amount: amountStr });
1973
+ throw DomainError.badRequest("KIT_QUANTITY_AMOUNT_INVALID", { amount: amountStr });
1968
1974
  }
1969
1975
  this.validateSymbol(symbol);
1970
1976
  return { amount, symbol };
@@ -2085,6 +2091,79 @@ function resolveSortColumn(repository, sortBy, fallback) {
2085
2091
  return isKnownColumn ? sortBy : fallback;
2086
2092
  }
2087
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
+
2088
2167
  class HttpApiError extends HttpException {
2089
2168
  constructor(statusCode, message, isOperational = true, stack, subcode) {
2090
2169
  super(message, statusCode);
@@ -2100,6 +2179,7 @@ class HttpApiError extends HttpException {
2100
2179
 
2101
2180
  const ASSERT_PREFIX = /assertion failure with message: (.+?)(?:\n|$)/;
2102
2181
  const CODED = /^([A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+):\s*(.*)$/s;
2182
+ const ENCODING = /^Encoding error at root<[^>]*>((?:\.[^.<:]+<[^>]*>)*):/;
2103
2183
  function parseChainAssert(message) {
2104
2184
  const assert = message.match(ASSERT_PREFIX);
2105
2185
  const body = (assert ? assert[1] : message).trim();
@@ -2115,7 +2195,23 @@ function chainErrorCode(error) {
2115
2195
  const raw = error?.message ?? String(error);
2116
2196
  return parseChainAssert(raw).code;
2117
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
+ }
2118
2212
  function rethrowChainError(error) {
2213
+ if (error instanceof DomainError)
2214
+ throw error;
2119
2215
  const raw = error?.message ?? String(error);
2120
2216
  const match = raw.match(ASSERT_PREFIX);
2121
2217
  if (!match)
@@ -2124,4 +2220,4 @@ function rethrowChainError(error) {
2124
2220
  throw DomainError.badRequest(code ?? "CHAIN_ASSERT", { message: text });
2125
2221
  }
2126
2222
 
2127
- 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-2",
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-2",
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-2",
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-2",
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": "08943be043e9451a6548b4934770d65ad37fa9e2"
85
+ "gitHead": "1346a804b5b9daec565a4bea5104510a6c832b0e"
86
86
  }