@coopenomics/extension-kit 2026.8.18-2

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.
@@ -0,0 +1,735 @@
1
+ import { Repository, DataSource } from 'typeorm';
2
+
3
+ /**
4
+ * Дельта таблицы блокчейна — то, что прилетает из SHiP-парсера на каждое
5
+ * изменение строки в state контракта.
6
+ *
7
+ * Каркас синхронизации обязан знать эту форму: с неё начинается вся цепочка
8
+ * `delta -> mapper -> сущность -> репозиторий`. Ядро контроллера объявляло тот
9
+ * же тип в `~/types/common` и теперь реэкспортирует его отсюда, чтобы описание
10
+ * оставалось одно.
11
+ */
12
+ interface IDelta {
13
+ chain_id: string;
14
+ block_num: number;
15
+ block_id: string;
16
+ /** ISO-8601 время блока (UTC) из SHiP-трейса. parser2 отдаёт, parser1 не отдавал. */
17
+ block_time?: string;
18
+ present: boolean;
19
+ code: string;
20
+ scope: string;
21
+ table: string;
22
+ primary_key: string;
23
+ value?: any;
24
+ }
25
+
26
+ /**
27
+ * Минимальный контракт логгера, которым пользуется каркас синхронизации.
28
+ *
29
+ * Зачем отдельный тип, а не порт из `@coopenomics/innercoop`: пакеты каркаса и
30
+ * контрактов ортогональны и не зависят друг от друга (INV-007). Связывать их
31
+ * ради пяти методов нельзя, а дублировать порт — значит завести второе имя
32
+ * для одной сущности.
33
+ *
34
+ * Поэтому здесь объявлена именно та часть, которую вызывает каркас, и опора
35
+ * идёт на структурную типизацию: `WinstonLoggerService` ядра подходит под этот
36
+ * контракт как есть, поэтому наследники передают его в `super()` без правок,
37
+ * и `ILoggerPort` из innercoop подойдёт так же, когда появится.
38
+ */
39
+ interface ISyncLogger {
40
+ /** Пометить, от чьего имени идут записи (обычно имя класса-наследника). */
41
+ setContext(context: string): void;
42
+ log(message: any, ...optionalParams: any[]): void;
43
+ debug(message: any, ...optionalParams: any[]): void;
44
+ warn(message: any, ...optionalParams: any[]): void;
45
+ error(message: any, ...optionalParams: any[]): void;
46
+ }
47
+
48
+ /**
49
+ * Базовый интерфейс данных из базы данных для всех синхронизируемых сущностей
50
+ */
51
+ interface IBaseDatabaseData {
52
+ /** Внутренний ID базы данных */
53
+ _id: string;
54
+ /** Номер блока последнего обновления */
55
+ block_num?: number;
56
+ /** Существует ли запись в блокчейне */
57
+ present: boolean;
58
+ /** Статус сущности */
59
+ status?: string;
60
+ /** Дата создания в базе данных */
61
+ _created_at?: Date;
62
+ /** Дата последнего обновления в базе данных */
63
+ _updated_at?: Date;
64
+ }
65
+
66
+ /**
67
+ * Интерфейс для данных с привязкой к блоку
68
+ */
69
+ interface IBlockchainSyncData {
70
+ /** Номер блока в котором произошло изменение */
71
+ block_num: number;
72
+ }
73
+ /**
74
+ * Интерфейс для сущностей, которые могут быть синхронизированы с блокчейном
75
+ */
76
+ interface IBlockchainSynchronizable {
77
+ /** Номер блока последнего обновления */
78
+ getBlockNum(): number | undefined;
79
+ /** Ключ для поиска сущности в блокчейне */
80
+ getPrimaryKey(): string;
81
+ /** Ключ для синхронизации сущности в блокчейне и базе данных */
82
+ getSyncKey(): string;
83
+ /** Обновление данных из блокчейна */
84
+ updateFromBlockchain(blockchainData: any, blockNum: number, present?: boolean): void;
85
+ }
86
+ /**
87
+ * Интерфейс для маппинга дельт блокчейна в доменные данные
88
+ */
89
+ interface IBlockchainDeltaMapper<TBlockchainData = any, _TDomainEntity = any> {
90
+ /** Маппинг данных дельты в блокчейн-данные */
91
+ mapDeltaToBlockchainData(delta: IDelta): TBlockchainData | null;
92
+ /** Получение идентификатора сущности из дельты */
93
+ extractSyncValue(delta: IDelta): string;
94
+ /** Получение ключа для синхронизации сущности в блокчейне и базе данных */
95
+ extractSyncKey(): string;
96
+ /** Получение всех возможных паттернов событий */
97
+ getAllEventPatterns(): string[];
98
+ /** Получение всех поддерживаемых имен таблиц */
99
+ getSupportedTableNames(): string[];
100
+ /** Получение всех поддерживаемых имен контрактов */
101
+ getSupportedContractNames(): string[];
102
+ }
103
+ /**
104
+ * Интерфейс для репозиториев с поддержкой синхронизации блокчейна
105
+ */
106
+ interface IBlockchainSyncRepository<TEntity extends IBlockchainSynchronizable> {
107
+ /** Найти сущность по кастомному ключу синхронизации */
108
+ findBySyncKey(syncKey: string, syncValue: string): Promise<TEntity | null>;
109
+ /** Найти сущности с номером блока больше указанного */
110
+ findByBlockNumGreaterThan(blockNum: number): Promise<TEntity[]>;
111
+ /** Создание и валидация сущности без сохранения в базу данных */
112
+ create(entity: TEntity): Promise<any>;
113
+ /** Сохранение созданной сущности в базу данных */
114
+ saveCreated(entity: any): Promise<TEntity>;
115
+ /** Сохранить сущность */
116
+ save(entity: TEntity): Promise<TEntity>;
117
+ /** Обновить сущность */
118
+ update(entity: TEntity): Promise<TEntity>;
119
+ /** Создать сущность если не существует */
120
+ createIfNotExists(blockchainData: any, blockNum: number, present?: boolean): Promise<TEntity>;
121
+ /** Удалить сущности с номером блока больше указанного (для обработки форков) */
122
+ deleteByBlockNumGreaterThan(blockNum: number): Promise<void>;
123
+ /** Восстановить сущности из версий после форка */
124
+ restoreFromVersions?(forkBlockNum: number): Promise<void>;
125
+ /**
126
+ * Story 4.4: атомарно перенести live-сущности WHERE block_num > forkBlockNum в архив
127
+ * (invalidated_entities) и удалить из исходной таблицы. Возвращает count.
128
+ */
129
+ archiveInvalidatedSince?(forkBlockNum: number, forkEventId?: string | null): Promise<number>;
130
+ /**
131
+ * Story 4.4: атомарно перенести версии этой entity_table WHERE block_num > forkBlockNum
132
+ * в архив (invalidated_entity_versions) и удалить из entity_versions. Возвращает count.
133
+ */
134
+ archiveInvalidatedVersionsSince?(forkBlockNum: number, forkEventId?: string | null): Promise<number>;
135
+ }
136
+ /**
137
+ * Результат синхронизации сущности
138
+ */
139
+ interface ISyncResult {
140
+ /** Была ли сущность создана */
141
+ created: boolean;
142
+ /** Была ли сущность обновлена */
143
+ updated: boolean;
144
+ /** Идентификатор сущности */
145
+ blockchainId: string;
146
+ /** Номер блока */
147
+ blockNum: number;
148
+ }
149
+
150
+ declare class BaseTypeormEntity {
151
+ _id: string;
152
+ block_num: number;
153
+ present: boolean;
154
+ status: string;
155
+ _created_at: Date;
156
+ _updated_at: Date;
157
+ /**
158
+ * Получить имя таблицы для сущности
159
+ * ДОЛЖЕН БЫТЬ ПЕРЕОПРЕДЕЛЕН в каждом наследнике!
160
+ */
161
+ static getTableName(): string;
162
+ }
163
+
164
+ /**
165
+ * Базовый класс для доменных сущностей
166
+ *
167
+ * Предоставляет общую логику для всех доменных сущностей:
168
+ * - Генерация _id при необходимости
169
+ * - Установка базовых полей из IBaseDatabaseData
170
+ */
171
+ declare abstract class BaseDomainEntity<T extends IBaseDatabaseData> {
172
+ _id: string;
173
+ block_num?: number;
174
+ present: boolean;
175
+ status?: string;
176
+ _created_at: Date;
177
+ _updated_at: Date;
178
+ /**
179
+ * Конструктор базового класса
180
+ *
181
+ * @param databaseData - данные из базы данных
182
+ * @param defaultStatus - статус по умолчанию, если не указан
183
+ */
184
+ constructor(databaseData: T, defaultStatus?: string);
185
+ /**
186
+ * Обновление базовых данных сущности
187
+ */
188
+ updateBase(data: Partial<T>): void;
189
+ }
190
+
191
+ /**
192
+ * Базовый GraphQL Output DTO для сущностей
193
+ */
194
+ declare class BaseOutputDTO {
195
+ _id: string;
196
+ present: boolean;
197
+ block_num?: number;
198
+ _created_at: Date;
199
+ _updated_at: Date;
200
+ }
201
+
202
+ /**
203
+ * Абстрактный базовый класс для всех блокчейн дельта-мапперов
204
+ * Предоставляет общую реализацию getAllEventPatterns()
205
+ */
206
+ declare abstract class AbstractBlockchainDeltaMapper<TBlockchainData = any, TDomainEntity = any> implements IBlockchainDeltaMapper<TBlockchainData, TDomainEntity> {
207
+ /**
208
+ * Получение всех возможных паттернов событий для подписки
209
+ * Возвращает массив паттернов типа "delta::contract::table"
210
+ */
211
+ getAllEventPatterns(): string[];
212
+ abstract getSupportedContractNames(): string[];
213
+ abstract getSupportedTableNames(): string[];
214
+ abstract mapDeltaToBlockchainData(delta: IDelta): TBlockchainData | null;
215
+ abstract extractSyncValue(delta: IDelta): string;
216
+ abstract extractSyncKey(): string;
217
+ }
218
+
219
+ /**
220
+ * Контракт syncer'а, который умеет откатывать свои сущности при форке (ADR-005, Story 4.1).
221
+ *
222
+ * Реализуется один раз — в AbstractEntitySyncService, поэтому каждый наследник (capital,
223
+ * agreements, wallet и пр.) получает поведение автоматически через `implements` родителя.
224
+ *
225
+ * ForkRegistryService собирает реализующих через DiscoveryService по symbol-маркеру
226
+ * FORK_AWARE_MARKER на onApplicationBootstrap — без правок onModuleInit у наследников,
227
+ * без instanceof-зависимости (Symbol на прототипе работает кросс-extension).
228
+ *
229
+ * ForkRegistryService обходит зарегистрированных syncer'ов **последовательно** (for-of await)
230
+ * для каждой `handleFork(blockNum)`: re-throw любой ошибки останавливает дальнейший обход
231
+ * и не даёт parser2 ACK'нуть форк-событие (повторная доставка пересыграет цепочку).
232
+ */
233
+ interface IForkAwareSyncer {
234
+ /**
235
+ * Откатить сущности этого syncer'а до состояния на блок forkBlockNum включительно.
236
+ * При ошибке — обязан re-throw (silent catch ломает контракт sequential apply).
237
+ *
238
+ * Story 4.4: `forkEventId` (optional) — локально-вычисленный controller-формат
239
+ * event_id (см. computeForkEventId), пробрасывается syncer'ом в архив инвалидированных
240
+ * сущностей (invalidated_entities.fork_event_id) для группировки по форкам. Старые
241
+ * вызовы без второго параметра остаются валидными — поле в архиве записывается NULL.
242
+ */
243
+ handleFork(forkBlockNum: number, forkEventId?: string | null): Promise<void>;
244
+ /**
245
+ * Опциональный приоритет для FK-зависимостей внутри одного контракта (меньше = раньше).
246
+ * Если не задан — порядок берётся из обхода DiscoveryService (отражает DI-граф Nest).
247
+ */
248
+ readonly forkRollbackPriority?: number;
249
+ }
250
+ /**
251
+ * Marker symbol для отделения форк-aware syncer'ов от прочих провайдеров при сканировании
252
+ * DiscoveryService. Класс-родитель AbstractEntitySyncService выставляет marker = true на
253
+ * своих экземплярах, поэтому все 20+ наследников автоматически попадают в обход без
254
+ * правок их onModuleInit.
255
+ */
256
+ declare const FORK_AWARE_MARKER: unique symbol;
257
+ /**
258
+ * Type guard для проверки, что произвольный провайдер реализует IForkAwareSyncer.
259
+ * Проверяет наличие symbol-маркера на инстансе и метода handleFork — duck typing
260
+ * с защитой от ложных срабатываний.
261
+ */
262
+ declare function isForkAware(candidate: unknown): candidate is IForkAwareSyncer;
263
+
264
+ /**
265
+ * Абстрактный сервис для синхронизации сущностей с блокчейном
266
+ *
267
+ * Предоставляет базовую логику для:
268
+ * - Обработки дельт блокчейна
269
+ * - Создания/обновления сущностей
270
+ * - Обработки форков (Story 4.1: реализует IForkAwareSyncer — ForkRegistryService
271
+ * собирает наследников через DiscoveryService по symbol-маркеру и обходит
272
+ * sequential при форке)
273
+ */
274
+ declare abstract class AbstractEntitySyncService<TEntity extends IBlockchainSynchronizable, TBlockchainData = any> implements IForkAwareSyncer {
275
+ protected readonly repository: IBlockchainSyncRepository<TEntity>;
276
+ protected readonly mapper: IBlockchainDeltaMapper<TBlockchainData>;
277
+ protected readonly logger: ISyncLogger;
278
+ protected abstract readonly entityName: string;
279
+ /**
280
+ * Symbol-маркер для ForkRegistryService (Story 4.1). Все 20+ наследников
281
+ * автоматически попадают в реестр через bootstrap-сканирование Discovery —
282
+ * без правок их onModuleInit.
283
+ */
284
+ readonly [FORK_AWARE_MARKER] = true;
285
+ constructor(repository: IBlockchainSyncRepository<TEntity>, mapper: IBlockchainDeltaMapper<TBlockchainData>, logger: ISyncLogger);
286
+ /**
287
+ * Обработка дельты блокчейна
288
+ */
289
+ processDelta(delta: IDelta): Promise<ISyncResult | null>;
290
+ /**
291
+ * Обработка создания/обновления сущности
292
+ */
293
+ handleSyncDelta(syncKey: string, syncValue: string, blockchainData: TBlockchainData, blockNum: number, present?: boolean): Promise<ISyncResult>;
294
+ /**
295
+ * Обработка удаления сущности
296
+ */
297
+ private handleEntityDeletion;
298
+ /**
299
+ * Обработка форка — архивирование снесённых сущностей + восстановление из versions
300
+ * + архивирование инвалидированных версий.
301
+ *
302
+ * Story 4.1: ошибки больше НЕ глотаются — обязательный re-throw для контракта
303
+ * sequential ForkRegistry.runAll (INV-T03). Если rollback упадёт — parser2 не
304
+ * ACK'нет fork-event, повторная доставка пересыграет цепочку. Уже отработавшие
305
+ * syncer'ы в цепи будут no-op (versions уже подняты), сбойный — попробует ещё раз.
306
+ *
307
+ * Story 4.4: hard-delete заменён на «архив + delete» атомарно. Порядок:
308
+ * 1) archiveInvalidatedSince — live-ряды WHERE block_num > N переезжают в
309
+ * invalidated_entities, оригинал удаляется (одна транзакция).
310
+ * 2) restoreFromVersions — поднять previous_data из ещё-живых entity_versions.
311
+ * 3) archiveInvalidatedVersionsSince — entity_versions WHERE entity_table=... AND
312
+ * block_num > N переезжают в invalidated_entity_versions, оригинал удаляется.
313
+ * Запускается ПОСЛЕ restore, иначе restore не сможет прочитать живые версии.
314
+ * Если репо не реализует archive методы (off-chain) — graceful no-op + fallback
315
+ * на старую findByBlockNumGreaterThan/deleteByBlockNumGreaterThan для бэк-совместимости.
316
+ */
317
+ handleFork(forkBlockNum: number, forkEventId?: string | null): Promise<void>;
318
+ /**
319
+ * Дополнительные действия после обработки форка
320
+ * Может быть переопределен в наследниках
321
+ */
322
+ protected afterForkProcessing(_forkBlockNum: number, _affectedEntities: TEntity[]): Promise<void>;
323
+ /**
324
+ * Получение всех возможных имен событий для подписки
325
+ */
326
+ getAllEventPatterns(): string[];
327
+ /**
328
+ * Получение всех поддерживаемых таблиц и контрактов для логирования
329
+ */
330
+ getSupportedVersions(): {
331
+ contracts: string[];
332
+ tables: string[];
333
+ };
334
+ /**
335
+ * Получение имени события для подписки на форки
336
+ */
337
+ getForkEventPattern(): string;
338
+ }
339
+
340
+ /**
341
+ * Единая таблица для хранения версий всех сущностей системы.
342
+ * Используется для восстановления состояния при форках блокчейна.
343
+ */
344
+ declare class EntityVersionTypeormEntity {
345
+ id: string;
346
+ /**
347
+ * Название таблицы сущности (например, 'chairman_approvals')
348
+ */
349
+ entity_table: string;
350
+ /**
351
+ * ID сущности в её таблице (_id для TypeORM сущностей)
352
+ */
353
+ entity_id: string;
354
+ /**
355
+ * Полные данные предыдущей версии сущности в JSON формате
356
+ */
357
+ previous_data: Record<string, any>;
358
+ /**
359
+ * Номер блока, на котором произошло изменение
360
+ */
361
+ block_num?: number | null;
362
+ /**
363
+ * Тип изменения (blockchain_sync, local_change и т.д.)
364
+ */
365
+ change_type: string;
366
+ /**
367
+ * Дополнительные метаданные изменения
368
+ */
369
+ metadata?: Record<string, any>;
370
+ created_at: Date;
371
+ }
372
+
373
+ /**
374
+ * Репозиторий для работы с версиями сущностей.
375
+ * Используется для восстановления состояния при форках.
376
+ */
377
+ declare class EntityVersionRepository {
378
+ private readonly repository;
379
+ constructor(repository: Repository<EntityVersionTypeormEntity>);
380
+ /**
381
+ * Соединение, на котором работает репозиторий.
382
+ *
383
+ * Нужно тем, кому требуется транзакция на несколько таблиц. Инжектить
384
+ * `DataSource` через `@InjectDataSource()` в пакете нельзя: pnpm вшивает в
385
+ * путь хэш peer-зависимостей, у пакета и контроллера оказываются разные
386
+ * экземпляры `@nestjs/typeorm`, и токен не совпадает — Nest не находит
387
+ * провайдера. Токен `@InjectRepository` такой беды не знает: он считается от
388
+ * класса сущности, а она в пакете одна.
389
+ */
390
+ get dataSource(): DataSource;
391
+ /**
392
+ * Сохранить предыдущую версию сущности
393
+ */
394
+ saveVersion(entityTable: string, entityId: string, previousData: Record<string, any>, blockNum: number | null, changeType: string, metadata?: Record<string, any>): Promise<EntityVersionTypeormEntity>;
395
+ /**
396
+ * Получить последнюю версию сущности до указанного блока
397
+ */
398
+ getLastVersionBeforeBlock(entityTable: string, entityId: string, blockNum: number): Promise<EntityVersionTypeormEntity | null>;
399
+ /**
400
+ * Удалить все версии после указанного блока (локальные изменения остаются)
401
+ */
402
+ deleteVersionsAfterBlock(blockNum: number): Promise<number>;
403
+ /**
404
+ * Получить все версии для сущностей, которые нужно восстановить
405
+ */
406
+ getVersionsForRecovery(entityTable: string, maxBlockNum: number): Promise<EntityVersionTypeormEntity[]>;
407
+ /**
408
+ * Очистить версии для указанной сущности
409
+ */
410
+ clearVersionsForEntity(entityTable: string, entityId: string): Promise<number>;
411
+ }
412
+
413
+ /**
414
+ * Архив сущностей, снесённых форком. Каждый ряд = одна live-запись, которая была
415
+ * в зеркале блокчейна на момент форка (block_num > forkBlockNum). Story 4.4.
416
+ *
417
+ * `invalidated_by_block` = block_num форка (т.е. forked_from_block из ForkEvent).
418
+ * `fork_event_id` группирует все снесённые одним форком ряды (опционально — старые форки до Story 4.4 без id).
419
+ *
420
+ * Retention: BlockchainArchiveRetentionService раз в час удаляет WHERE invalidated_by_block < LIB - 1000.
421
+ */
422
+ declare class InvalidatedEntityTypeormEntity {
423
+ id: string;
424
+ entity_table: string;
425
+ entity_id: string;
426
+ data: Record<string, any>;
427
+ invalidated_by_block: number;
428
+ fork_event_id?: string | null;
429
+ created_at: Date;
430
+ }
431
+
432
+ interface InvalidatedEntityRecord {
433
+ entity_table: string;
434
+ entity_id: string;
435
+ data: Record<string, any>;
436
+ invalidated_by_block: number;
437
+ fork_event_id?: string | null;
438
+ }
439
+ /**
440
+ * Репозиторий архива снесённых форком live-сущностей (Story 4.4).
441
+ */
442
+ declare class InvalidatedEntityRepository {
443
+ private readonly repository;
444
+ constructor(repository: Repository<InvalidatedEntityTypeormEntity>);
445
+ bulkInsert(records: InvalidatedEntityRecord[]): Promise<number>;
446
+ /**
447
+ * Retention: удалить архив старше указанного блока. Делается отдельной транзакцией,
448
+ * не транзакционно с архивированием — это фоновая очистка.
449
+ */
450
+ deleteOlderThan(minInvalidatedByBlock: number): Promise<number>;
451
+ /**
452
+ * Forensic-read для AC «список из invalidated_entities, сгруппированный по fork_event_id».
453
+ * UI/CLI обёртка — Epic 9 (out of scope 4.4); сам repository-метод доступен из backend-кода.
454
+ */
455
+ findGroupedByForkEventId(opts: {
456
+ blockNum?: number;
457
+ limit?: number;
458
+ }): Promise<Map<string | null, InvalidatedEntityTypeormEntity[]>>;
459
+ }
460
+
461
+ /**
462
+ * Архив версий-снимков, потерявших инвалидирующий блок при форке. Story 4.4.
463
+ *
464
+ * Каждый ряд entity_versions хранит previous_data + block_num (блок, в котором данное
465
+ * previous_data перестало быть актуальным). При форке на N все entity_versions
466
+ * WHERE block_num > N теряют свой инвалидирующий блок (он на снесённой ветке) и
467
+ * становятся «осиротевшими». Если не убрать — при повторном форке restoreFromVersions
468
+ * подберёт их и поднимет не ту ветку.
469
+ *
470
+ * `original_block_num` = блок-инвалидатор из исходного entity_versions ряда (может быть null
471
+ * для локальных pre-blockchain изменений). `invalidated_by_block` = блок форка.
472
+ */
473
+ declare class InvalidatedEntityVersionTypeormEntity {
474
+ id: string;
475
+ entity_table: string;
476
+ entity_id: string;
477
+ previous_data: Record<string, any>;
478
+ original_block_num?: number | null;
479
+ invalidated_by_block: number;
480
+ fork_event_id?: string | null;
481
+ change_type: string;
482
+ metadata?: Record<string, any> | null;
483
+ created_at: Date;
484
+ }
485
+
486
+ interface InvalidatedEntityVersionRecord {
487
+ entity_table: string;
488
+ entity_id: string;
489
+ previous_data: Record<string, any>;
490
+ original_block_num?: number | null;
491
+ invalidated_by_block: number;
492
+ fork_event_id?: string | null;
493
+ change_type: string;
494
+ metadata?: Record<string, any> | null;
495
+ }
496
+ /**
497
+ * Репозиторий архива снесённых форком версий-снимков entity_versions (Story 4.4).
498
+ */
499
+ declare class InvalidatedEntityVersionRepository {
500
+ private readonly repository;
501
+ constructor(repository: Repository<InvalidatedEntityVersionTypeormEntity>);
502
+ bulkInsert(records: InvalidatedEntityVersionRecord[]): Promise<number>;
503
+ deleteOlderThan(minInvalidatedByBlock: number): Promise<number>;
504
+ }
505
+
506
+ /**
507
+ * Сервис для версионирования сущностей.
508
+ * Автоматически сохраняет предыдущие версии при изменениях и восстанавливает их при форках.
509
+ *
510
+ * Story 4.4: расширен двумя архивными методами — archiveAndDeleteLiveAfterFork /
511
+ * archiveAndDeleteVersionsAfterFork. Используются из AbstractEntitySyncService.handleFork
512
+ * через делегацию BaseBlockchainRepository (см. base-blockchain.repository.ts).
513
+ */
514
+ declare class EntityVersioningService {
515
+ private readonly entityVersionRepository;
516
+ private readonly invalidatedEntityRepository;
517
+ private readonly invalidatedEntityVersionRepository;
518
+ constructor(entityVersionRepository: EntityVersionRepository, invalidatedEntityRepository: InvalidatedEntityRepository, invalidatedEntityVersionRepository: InvalidatedEntityVersionRepository);
519
+ /**
520
+ * Соединение берём у репозитория, а не инъекцией `@InjectDataSource()`:
521
+ * у пакета и контроллера разные экземпляры `@nestjs/typeorm`, и токен
522
+ * источника данных не совпал бы — Nest не нашёл бы провайдера.
523
+ */
524
+ private get dataSource();
525
+ /**
526
+ * Сохранить версию сущности перед её изменением
527
+ */
528
+ saveVersionBeforeUpdate<TEntity extends IBaseDatabaseData>(repository: Repository<TEntity>, entityTable: string, updatedEntity: Partial<TEntity>, blockNum: number | null, changeType: string, metadata?: Record<string, any>): Promise<void>;
529
+ /**
530
+ * Восстановить версии сущностей после форка
531
+ */
532
+ restoreVersionsAfterFork<TEntity extends IBaseDatabaseData>(repository: Repository<TEntity>, entityTable: string, forkBlockNum: number): Promise<void>;
533
+ /**
534
+ * Определить, должна ли новая версия заменить существующую
535
+ */
536
+ private shouldReplaceVersion;
537
+ /**
538
+ * Очистить версии после успешного восстановления
539
+ */
540
+ clearVersionsAfterBlock(blockNum: number): Promise<number>;
541
+ /**
542
+ * Story 4.4: атомарно перенести live-ряды WHERE block_num > forkBlockNum в архив
543
+ * `invalidated_entities` и удалить их из исходной таблицы. Возвращает количество
544
+ * перенесённых рядов. Транзакция через DataSource — INSERT и DELETE либо оба
545
+ * успешны, либо оба откатываются.
546
+ *
547
+ * Заменяет прежнюю пару findByBlockNumGreaterThan + deleteByBlockNumGreaterThan
548
+ * в hot-path handleFork (sequence сейчас: archive → restoreFromVersions →
549
+ * archiveVersions).
550
+ */
551
+ archiveAndDeleteLiveAfterFork<TEntity extends IBaseDatabaseData>(repository: Repository<TEntity>, entityTable: string, forkBlockNum: number, forkEventId?: string | null): Promise<number>;
552
+ /**
553
+ * Story 4.4: атомарно перенести entity_versions WHERE entity_table=... AND block_num > forkBlockNum
554
+ * в архив `invalidated_entity_versions` и удалить их из entity_versions.
555
+ * Возвращает количество перенесённых рядов.
556
+ *
557
+ * Запускается ПОСЛЕ restoreFromVersions — иначе restore не сможет прочитать
558
+ * ещё-живые версии.
559
+ */
560
+ archiveAndDeleteVersionsAfterFork(entityTable: string, forkBlockNum: number, forkEventId?: string | null): Promise<number>;
561
+ }
562
+
563
+ /**
564
+ * Базовый абстрактный класс для репозиториев блокчейн-сущностей
565
+ *
566
+ * Предоставляет общую реализацию методов:
567
+ * - Синхронизации с блокчейном: findByBlockchainId, findByBlockNumGreaterThan, createIfNotExists, deleteByBlockNumGreaterThan
568
+ * - CRUD операций: findAll, findById, save, update, delete
569
+ */
570
+ declare abstract class BaseBlockchainRepository<TDomainEntity extends IBlockchainSynchronizable, TTypeormEntity extends IBaseDatabaseData> implements IBlockchainSyncRepository<TDomainEntity> {
571
+ protected readonly repository: Repository<TTypeormEntity>;
572
+ protected readonly entityVersioningService: EntityVersioningService;
573
+ protected constructor(repository: Repository<TTypeormEntity>, entityVersioningService: EntityVersioningService);
574
+ /**
575
+ * Маппер для преобразования между доменной и TypeORM сущностями
576
+ * Должен содержать методы:
577
+ * - toDomain(typeormEntity: TTypeormEntity): TDomainEntity
578
+ * - toEntity(domainEntity: TDomainEntity): Partial<TTypeormEntity>
579
+ */
580
+ protected abstract getMapper(): {
581
+ toDomain: (typeormEntity: TTypeormEntity) => TDomainEntity;
582
+ toEntity: (domainEntity: TDomainEntity) => Partial<TTypeormEntity>;
583
+ };
584
+ /**
585
+ * Получить имя таблицы сущности
586
+ */
587
+ protected getEntityTableName(): string;
588
+ /**
589
+ * Найти сущность по кастомному ключу синхронизации
590
+ */
591
+ findBySyncKey(syncKey: string, syncValue: string): Promise<TDomainEntity | null>;
592
+ /**
593
+ * Найти сущности с номером блока больше указанного
594
+ */
595
+ findByBlockNumGreaterThan(blockNum: number): Promise<TDomainEntity[]>;
596
+ /**
597
+ * Создать сущность если не существует
598
+ * Используется для синхронизации данных из блокчейна
599
+ */
600
+ createIfNotExists(blockchainData: any, blockNum: number, present?: boolean): Promise<TDomainEntity>;
601
+ /**
602
+ * Удалить сущности с номером блока больше указанного (для обработки форков)
603
+ */
604
+ deleteByBlockNumGreaterThan(blockNum: number): Promise<void>;
605
+ /**
606
+ * Восстановить сущности из версий после форка
607
+ */
608
+ restoreFromVersions(forkBlockNum: number): Promise<void>;
609
+ /**
610
+ * Story 4.4: архивировать live-ряды WHERE block_num > forkBlockNum в invalidated_entities
611
+ * и удалить из исходной таблицы (атомарно). Возвращает count. Заменяет в hot-path
612
+ * handleFork прежнюю пару findByBlockNumGreaterThan + deleteByBlockNumGreaterThan.
613
+ */
614
+ archiveInvalidatedSince(forkBlockNum: number, forkEventId?: string | null): Promise<number>;
615
+ /**
616
+ * Story 4.4: архивировать entity_versions WHERE entity_table=... AND block_num > forkBlockNum
617
+ * в invalidated_entity_versions и удалить из entity_versions (атомарно). Возвращает count.
618
+ * Должен вызываться ПОСЛЕ restoreFromVersions — иначе restore не сможет прочитать ещё-живые версии.
619
+ */
620
+ archiveInvalidatedVersionsSince(forkBlockNum: number, forkEventId?: string | null): Promise<number>;
621
+ /**
622
+ * Обновить сущность
623
+ */
624
+ update(entity: TDomainEntity): Promise<TDomainEntity>;
625
+ /**
626
+ * Создание и валидация сущности без сохранения в базу данных
627
+ */
628
+ create(entity: TDomainEntity): Promise<any>;
629
+ /**
630
+ * Сохранение созданной сущности в базу данных
631
+ */
632
+ saveCreated(entity: any): Promise<TDomainEntity>;
633
+ /**
634
+ * Сохранить сущность
635
+ */
636
+ save(entity: TDomainEntity): Promise<TDomainEntity>;
637
+ /**
638
+ * Найти все сущности
639
+ */
640
+ findAll(): Promise<TDomainEntity[]>;
641
+ /**
642
+ * Найти сущность по внутреннему ID базы данных
643
+ */
644
+ findById(_id: string): Promise<TDomainEntity | null>;
645
+ /**
646
+ * Удалить сущность по внутреннему ID базы данных
647
+ */
648
+ delete(_id: string): Promise<void>;
649
+ /**
650
+ * Создать доменную сущность
651
+ * Должен быть реализован в наследниках для создания конкретного типа сущности
652
+ */
653
+ protected abstract createDomainEntity(databaseData: any, blockchainData: any): TDomainEntity;
654
+ /**
655
+ * Получить ключ синхронизации для данной сущности
656
+ * Должен быть реализован в наследниках для возврата правильного ключа
657
+ */
658
+ protected abstract getSyncKey(): string;
659
+ /**
660
+ * Извлечь значение ключа синхронизации из блокчейн данных
661
+ */
662
+ protected extractSyncValueFromBlockchainData(blockchainData: any, syncKey: string): string;
663
+ }
664
+
665
+ /**
666
+ * Story 6.5 (Epic 6): сигнализирует, что mapper не смог разобрать дельту блокчейна
667
+ * (mapDeltaToBlockchainData вернул null). Бросается из `AbstractEntitySyncService.processDelta`
668
+ * в strict-mode (`config.blockchain.unsupported_version_strict=true`).
669
+ *
670
+ * В non-strict режиме (default) ошибка не бросается — пишется только `logger.error` для
671
+ * аудита; парсер ACK'нет fork-event-like (поведение совместимое с текущим).
672
+ */
673
+ declare class UnsupportedContractVersionError extends Error {
674
+ readonly entityName: string;
675
+ readonly context: {
676
+ contract?: string;
677
+ table?: string;
678
+ primary_key?: string | number;
679
+ block_num?: number;
680
+ };
681
+ constructor(entityName: string, context: {
682
+ contract?: string;
683
+ table?: string;
684
+ primary_key?: string | number;
685
+ block_num?: number;
686
+ });
687
+ }
688
+
689
+ /**
690
+ * Story 6.5 (Epic 6): helper для эталонной точки `mapStatusToDomain`.
691
+ * При попадании на default-ветку (unknown статус из цепи) пишет `logger.error`
692
+ * с контекстом (entity, статус, ожидаемые статусы) — это audit-trail для schema drift.
693
+ *
694
+ * Возврата нет — caller сам решает, какой UNDEFINED-fallback использовать.
695
+ */
696
+ interface AuditLoggerLike {
697
+ error(message: string, ...meta: any[]): void;
698
+ }
699
+ declare function auditUnknownStatus(entityName: string, receivedStatus: unknown, logger: AuditLoggerLike, allowedStatuses?: ReadonlyArray<string>): void;
700
+
701
+ /**
702
+ * Политика каркаса синхронизации.
703
+ *
704
+ * Отделена от настроек контура (`platformSettings`) намеренно: те описывают
705
+ * кооператив — имя, адреса, зону, — а здесь режим обработки дельт, свойство
706
+ * стенда, а не кооператива.
707
+ *
708
+ * Задаёт её composition root контроллера при старте, рядом с остальными
709
+ * предусловиями. Расширение, собранное отдельно, получит значения по умолчанию
710
+ * — они совпадают с поведением контроллера без переменных окружения.
711
+ */
712
+ interface SyncPolicy {
713
+ /**
714
+ * Что делать с дельтой неизвестной версии контракта.
715
+ *
716
+ * `false` (по умолчанию) — записать предупреждение и пропустить: расхождение
717
+ * схемы не должно останавливать синхронизацию рабочего кооператива.
718
+ * `true` — бросить `UnsupportedContractVersionError`, парсер не подтвердит
719
+ * дельту и она уйдёт в dead-letter. Так включают на стенде, когда нужно
720
+ * убедиться, что расхождения схемы нет вовсе.
721
+ */
722
+ unsupportedVersionStrict: boolean;
723
+ }
724
+ /** Вызывается composition root'ом при старте. Повторный вызов перезаписывает. */
725
+ declare function configureSyncPolicy(next: Partial<SyncPolicy>): void;
726
+ /** Текущая политика; без настройки — значения по умолчанию. */
727
+ declare function syncPolicy(): SyncPolicy;
728
+
729
+ /** Задать журнал аудита. Вызывается из composition root при старте. */
730
+ declare function configureAuditLogger(logger: AuditLoggerLike): void;
731
+ /** Журнал аудита, которым пользуются каркас и расширения. */
732
+ declare function auditLogger(): AuditLoggerLike;
733
+
734
+ export { AbstractBlockchainDeltaMapper, AbstractEntitySyncService, BaseBlockchainRepository, BaseDomainEntity, BaseOutputDTO, BaseTypeormEntity, EntityVersionRepository, EntityVersionTypeormEntity, EntityVersioningService, FORK_AWARE_MARKER, InvalidatedEntityRepository, InvalidatedEntityTypeormEntity, InvalidatedEntityVersionRepository, InvalidatedEntityVersionTypeormEntity, UnsupportedContractVersionError, auditLogger, auditUnknownStatus, configureAuditLogger, configureSyncPolicy, isForkAware, syncPolicy };
735
+ export type { AuditLoggerLike, IBaseDatabaseData, IBlockchainDeltaMapper, IBlockchainSyncData, IBlockchainSyncRepository, IBlockchainSynchronizable, IDelta, IForkAwareSyncer, ISyncLogger, ISyncResult, InvalidatedEntityRecord, InvalidatedEntityVersionRecord, SyncPolicy };