@deorta-dev/nestjs-repository-core 0.1.0
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/LICENSE +21 -0
- package/README.es.md +260 -0
- package/README.md +259 -0
- package/dist/base-repository.service.d.ts +73 -0
- package/dist/base-repository.service.d.ts.map +1 -0
- package/dist/base-repository.service.js +309 -0
- package/dist/base-repository.service.js.map +1 -0
- package/dist/decorators/index.d.ts +2 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +18 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/repository-inject.decorator.d.ts +20 -0
- package/dist/decorators/repository-inject.decorator.d.ts.map +1 -0
- package/dist/decorators/repository-inject.decorator.js +29 -0
- package/dist/decorators/repository-inject.decorator.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/repository.module.d.ts +13 -0
- package/dist/repository.module.d.ts.map +1 -0
- package/dist/repository.module.js +92 -0
- package/dist/repository.module.js.map +1 -0
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +20 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/sync-checkpoint.schema.d.ts +22 -0
- package/dist/schema/sync-checkpoint.schema.d.ts.map +1 -0
- package/dist/schema/sync-checkpoint.schema.js +13 -0
- package/dist/schema/sync-checkpoint.schema.js.map +1 -0
- package/dist/schema/tombstone.schema.d.ts +24 -0
- package/dist/schema/tombstone.schema.d.ts.map +1 -0
- package/dist/schema/tombstone.schema.js +15 -0
- package/dist/schema/tombstone.schema.js.map +1 -0
- package/dist/schema/with-cache-ttl.d.ts +14 -0
- package/dist/schema/with-cache-ttl.d.ts.map +1 -0
- package/dist/schema/with-cache-ttl.js +24 -0
- package/dist/schema/with-cache-ttl.js.map +1 -0
- package/dist/sync/backup-sync.service.d.ts +61 -0
- package/dist/sync/backup-sync.service.d.ts.map +1 -0
- package/dist/sync/backup-sync.service.js +156 -0
- package/dist/sync/backup-sync.service.js.map +1 -0
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +18 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/types.d.ts +122 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +19 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/pending-ops-queue.d.ts +44 -0
- package/dist/utils/pending-ops-queue.d.ts.map +1 -0
- package/dist/utils/pending-ops-queue.js +82 -0
- package/dist/utils/pending-ops-queue.js.map +1 -0
- package/dist/utils/repository-token.util.d.ts +7 -0
- package/dist/utils/repository-token.util.d.ts.map +1 -0
- package/dist/utils/repository-token.util.js +21 -0
- package/dist/utils/repository-token.util.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Schema } from 'mongoose';
|
|
2
|
+
/**
|
|
3
|
+
* Guarda hasta qué `updatedTime` de `main` ya se sincronizó con los backups,
|
|
4
|
+
* para que cada corrida de sincronización solo procese lo nuevo.
|
|
5
|
+
*/
|
|
6
|
+
export declare const syncCheckpointSchema: Schema<any, import("mongoose").Model<any, any, any, any, any, any>, {}, {}, {}, {}, {
|
|
7
|
+
versionKey: false;
|
|
8
|
+
}, {
|
|
9
|
+
key: string;
|
|
10
|
+
value: NativeDate;
|
|
11
|
+
}, import("mongoose").Document<unknown, {}, import("mongoose").FlatRecord<{
|
|
12
|
+
key: string;
|
|
13
|
+
value: NativeDate;
|
|
14
|
+
}>, {}, import("mongoose").MergeType<import("mongoose").DefaultSchemaOptions, {
|
|
15
|
+
versionKey: false;
|
|
16
|
+
}>> & import("mongoose").FlatRecord<{
|
|
17
|
+
key: string;
|
|
18
|
+
value: NativeDate;
|
|
19
|
+
}> & {
|
|
20
|
+
_id: import("mongoose").Types.ObjectId;
|
|
21
|
+
}>;
|
|
22
|
+
//# sourceMappingURL=sync-checkpoint.schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-checkpoint.schema.d.ts","sourceRoot":"","sources":["../../src/schema/sync-checkpoint.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;EAMhC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.syncCheckpointSchema = void 0;
|
|
4
|
+
const mongoose_1 = require("mongoose");
|
|
5
|
+
/**
|
|
6
|
+
* Guarda hasta qué `updatedTime` de `main` ya se sincronizó con los backups,
|
|
7
|
+
* para que cada corrida de sincronización solo procese lo nuevo.
|
|
8
|
+
*/
|
|
9
|
+
exports.syncCheckpointSchema = new mongoose_1.Schema({
|
|
10
|
+
key: { type: String, required: true, unique: true },
|
|
11
|
+
value: { type: Date, required: true },
|
|
12
|
+
}, { versionKey: false });
|
|
13
|
+
//# sourceMappingURL=sync-checkpoint.schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-checkpoint.schema.js","sourceRoot":"","sources":["../../src/schema/sync-checkpoint.schema.ts"],"names":[],"mappings":";;;AAAA,uCAAkC;AAElC;;;GAGG;AACU,QAAA,oBAAoB,GAAG,IAAI,iBAAM,CAC1C;IACI,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;IACnD,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;CACxC,EACD,EAAE,UAAU,EAAE,KAAK,EAAE,CACxB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Schema } from 'mongoose';
|
|
2
|
+
/**
|
|
3
|
+
* Cada vez que se borra un documento en `main` (y existen backups
|
|
4
|
+
* configurados), guardamos aquí su _id y la fecha de borrado. El
|
|
5
|
+
* BackupSyncService lee estos registros para saber qué borrar en los
|
|
6
|
+
* backups, sin tener que comparar el set completo de _ids contra main.
|
|
7
|
+
*/
|
|
8
|
+
export declare const tombstoneSchema: Schema<any, import("mongoose").Model<any, any, any, any, any, any>, {}, {}, {}, {}, {
|
|
9
|
+
versionKey: false;
|
|
10
|
+
}, {
|
|
11
|
+
docId: any;
|
|
12
|
+
deletedAt: NativeDate;
|
|
13
|
+
}, import("mongoose").Document<unknown, {}, import("mongoose").FlatRecord<{
|
|
14
|
+
docId: any;
|
|
15
|
+
deletedAt: NativeDate;
|
|
16
|
+
}>, {}, import("mongoose").MergeType<import("mongoose").DefaultSchemaOptions, {
|
|
17
|
+
versionKey: false;
|
|
18
|
+
}>> & import("mongoose").FlatRecord<{
|
|
19
|
+
docId: any;
|
|
20
|
+
deletedAt: NativeDate;
|
|
21
|
+
}> & {
|
|
22
|
+
_id: import("mongoose").Types.ObjectId;
|
|
23
|
+
}>;
|
|
24
|
+
//# sourceMappingURL=tombstone.schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tombstone.schema.d.ts","sourceRoot":"","sources":["../../src/schema/tombstone.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAe,MAAM,UAAU,CAAC;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;EAM3B,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tombstoneSchema = void 0;
|
|
4
|
+
const mongoose_1 = require("mongoose");
|
|
5
|
+
/**
|
|
6
|
+
* Cada vez que se borra un documento en `main` (y existen backups
|
|
7
|
+
* configurados), guardamos aquí su _id y la fecha de borrado. El
|
|
8
|
+
* BackupSyncService lee estos registros para saber qué borrar en los
|
|
9
|
+
* backups, sin tener que comparar el set completo de _ids contra main.
|
|
10
|
+
*/
|
|
11
|
+
exports.tombstoneSchema = new mongoose_1.Schema({
|
|
12
|
+
docId: { type: mongoose_1.SchemaTypes.Mixed, required: true, index: true },
|
|
13
|
+
deletedAt: { type: Date, default: () => new Date(), index: true },
|
|
14
|
+
}, { versionKey: false });
|
|
15
|
+
//# sourceMappingURL=tombstone.schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tombstone.schema.js","sourceRoot":"","sources":["../../src/schema/tombstone.schema.ts"],"names":[],"mappings":";;;AAAA,uCAA+C;AAE/C;;;;;GAKG;AACU,QAAA,eAAe,GAAG,IAAI,iBAAM,CACrC;IACI,KAAK,EAAE,EAAE,IAAI,EAAE,sBAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IAC/D,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;CACpE,EACD,EAAE,UAAU,EAAE,KAAK,EAAE,CACxB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Schema } from 'mongoose';
|
|
2
|
+
/** Campo interno usado para el índice TTL en la conexión de caché. */
|
|
3
|
+
export declare const CACHE_EXPIRES_AT_FIELD = "_cacheExpiresAt";
|
|
4
|
+
/**
|
|
5
|
+
* Clona el schema original y le agrega un campo + índice TTL para que Mongo
|
|
6
|
+
* expire automáticamente los documentos de la conexión de caché.
|
|
7
|
+
*
|
|
8
|
+
* Usamos un índice TTL "a fecha exacta" (expireAfterSeconds: 0 sobre un campo
|
|
9
|
+
* Date) en vez del clásico "expira N segundos después de creado", porque así
|
|
10
|
+
* cada escritura puede definir su propio vencimiento (this.ttlSeconds) sin
|
|
11
|
+
* depender de cuándo se creó el índice.
|
|
12
|
+
*/
|
|
13
|
+
export declare function withCacheTtl(originalSchema: Schema, _ttlSeconds: number): Schema;
|
|
14
|
+
//# sourceMappingURL=with-cache-ttl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-cache-ttl.d.ts","sourceRoot":"","sources":["../../src/schema/with-cache-ttl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,sEAAsE;AACtE,eAAO,MAAM,sBAAsB,oBAAoB,CAAC;AAExD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAOhF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CACHE_EXPIRES_AT_FIELD = void 0;
|
|
4
|
+
exports.withCacheTtl = withCacheTtl;
|
|
5
|
+
/** Campo interno usado para el índice TTL en la conexión de caché. */
|
|
6
|
+
exports.CACHE_EXPIRES_AT_FIELD = '_cacheExpiresAt';
|
|
7
|
+
/**
|
|
8
|
+
* Clona el schema original y le agrega un campo + índice TTL para que Mongo
|
|
9
|
+
* expire automáticamente los documentos de la conexión de caché.
|
|
10
|
+
*
|
|
11
|
+
* Usamos un índice TTL "a fecha exacta" (expireAfterSeconds: 0 sobre un campo
|
|
12
|
+
* Date) en vez del clásico "expira N segundos después de creado", porque así
|
|
13
|
+
* cada escritura puede definir su propio vencimiento (this.ttlSeconds) sin
|
|
14
|
+
* depender de cuándo se creó el índice.
|
|
15
|
+
*/
|
|
16
|
+
function withCacheTtl(originalSchema, _ttlSeconds) {
|
|
17
|
+
const cacheSchema = originalSchema.clone();
|
|
18
|
+
cacheSchema.add({
|
|
19
|
+
[exports.CACHE_EXPIRES_AT_FIELD]: { type: Date },
|
|
20
|
+
});
|
|
21
|
+
cacheSchema.index({ [exports.CACHE_EXPIRES_AT_FIELD]: 1 }, { expireAfterSeconds: 0 });
|
|
22
|
+
return cacheSchema;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=with-cache-ttl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-cache-ttl.js","sourceRoot":"","sources":["../../src/schema/with-cache-ttl.ts"],"names":[],"mappings":";;;AAcA,oCAOC;AAnBD,sEAAsE;AACzD,QAAA,sBAAsB,GAAG,iBAAiB,CAAC;AAExD;;;;;;;;GAQG;AACH,SAAgB,YAAY,CAAC,cAAsB,EAAE,WAAmB;IACpE,MAAM,WAAW,GAAG,cAAc,CAAC,KAAK,EAAE,CAAC;IAC3C,WAAW,CAAC,GAAG,CAAC;QACZ,CAAC,8BAAsB,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;KACpC,CAAC,CAAC;IACV,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,8BAAsB,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,kBAAkB,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9E,OAAO,WAAW,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import { Model } from 'mongoose';
|
|
3
|
+
import { BackupSyncConfig } from '../types';
|
|
4
|
+
interface SyncOutcome {
|
|
5
|
+
upserted: number;
|
|
6
|
+
deleted: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Revisa periódicamente qué le falta a cada conexión de backup respecto a
|
|
10
|
+
* `main` y la pone al día:
|
|
11
|
+
* - Inserts/updates: documentos de `main` con `updatedTime` > checkpoint (uno por backup).
|
|
12
|
+
* - Deletes: usa la colección de tombstones (ver schema/tombstone.schema.ts), también con
|
|
13
|
+
* un checkpoint independiente por backup.
|
|
14
|
+
*
|
|
15
|
+
* Cada conexión de backup se sincroniza de forma INDEPENDIENTE: si una está
|
|
16
|
+
* caída o falla, las demás igual avanzan, y la caída simplemente se pondrá
|
|
17
|
+
* al día sola en una próxima corrida (no hay un único checkpoint compartido
|
|
18
|
+
* que se bloquee por una conexión problemática).
|
|
19
|
+
*
|
|
20
|
+
* Si `backupSync.enabled` es `false`, este servicio no arranca ningún
|
|
21
|
+
* temporizador interno: en ese caso eres tú (o un microservicio externo de
|
|
22
|
+
* bajo esfuerzo) quien debe llamar `syncNow()` cuando le convenga.
|
|
23
|
+
*/
|
|
24
|
+
export declare class BackupSyncService implements OnModuleInit, OnModuleDestroy {
|
|
25
|
+
protected readonly entityName: string;
|
|
26
|
+
protected readonly mainModel: Model<any>;
|
|
27
|
+
protected readonly backupModels: Model<any>[];
|
|
28
|
+
protected readonly backupLabels: string[];
|
|
29
|
+
protected readonly tombstoneModel: Model<any>;
|
|
30
|
+
protected readonly checkpointModel: Model<any>;
|
|
31
|
+
protected readonly config: BackupSyncConfig;
|
|
32
|
+
protected readonly logger: Logger;
|
|
33
|
+
protected timer?: NodeJS.Timeout;
|
|
34
|
+
protected running: boolean;
|
|
35
|
+
constructor(entityName: string, mainModel: Model<any>, backupModels: Model<any>[], backupLabels: string[], tombstoneModel: Model<any>, checkpointModel: Model<any>, config: BackupSyncConfig);
|
|
36
|
+
onModuleInit(): void;
|
|
37
|
+
onModuleDestroy(): void;
|
|
38
|
+
/**
|
|
39
|
+
* Dispara una verificación/actualización manual de TODOS los backups
|
|
40
|
+
* configurados. Útil si prefieres que un microservicio externo (de bajo
|
|
41
|
+
* esfuerzo) decida cuándo sincronizar, en vez del temporizador interno.
|
|
42
|
+
*/
|
|
43
|
+
syncNow(): Promise<SyncOutcome>;
|
|
44
|
+
/** Sincroniza un backup específico. Si no está listo, falla rápido sin tocar `main`. */
|
|
45
|
+
protected syncBackup(backup: Model<any>, label: string, batchSize: number): Promise<SyncOutcome>;
|
|
46
|
+
protected syncUpsertsFor(backup: Model<any>, label: string, batchSize: number): Promise<number>;
|
|
47
|
+
protected syncDeletesFor(backup: Model<any>, label: string, batchSize: number): Promise<number>;
|
|
48
|
+
/**
|
|
49
|
+
* Un tombstone solo se borra de la colección una vez que TODOS los
|
|
50
|
+
* backups configurados ya lo aplicaron (su checkpoint de deletes ya lo
|
|
51
|
+
* superó). Así, un backup que estuvo caído puede ponerse al día más
|
|
52
|
+
* tarde sin que el tombstone que necesitaba ya haya sido eliminado.
|
|
53
|
+
*/
|
|
54
|
+
protected purgeFullyAppliedTombstones(): Promise<void>;
|
|
55
|
+
protected upsertCheckpointKey(label: string): string;
|
|
56
|
+
protected deleteCheckpointKey(label: string): string;
|
|
57
|
+
protected getCheckpoint(key: string): Promise<Date>;
|
|
58
|
+
protected setCheckpoint(key: string, value: Date): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
export {};
|
|
61
|
+
//# sourceMappingURL=backup-sync.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup-sync.service.d.ts","sourceRoot":"","sources":["../../src/sync/backup-sync.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE5C,UAAU,WAAW;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,iBAAkB,YAAW,YAAY,EAAE,eAAe;IAM/D,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM;IACrC,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC;IACxC,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE;IAC7C,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE;IACzC,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,KAAK,CAAC,GAAG,CAAC;IAC7C,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,KAAK,CAAC,GAAG,CAAC;IAC9C,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,gBAAgB;IAX/C,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAClC,SAAS,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;IACjC,SAAS,CAAC,OAAO,UAAS;gBAGH,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,EACrB,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,EAC1B,YAAY,EAAE,MAAM,EAAE,EACtB,cAAc,EAAE,KAAK,CAAC,GAAG,CAAC,EAC1B,eAAe,EAAE,KAAK,CAAC,GAAG,CAAC,EAC3B,MAAM,EAAE,gBAAgB;IAK/C,YAAY,IAAI,IAAI;IAapB,eAAe,IAAI,IAAI;IAIvB;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,WAAW,CAAC;IAkCrC,wFAAwF;cACxE,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;cAUtF,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;cAyBrF,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAwBrG;;;;;OAKG;cACa,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5D,SAAS,CAAC,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAIpD,SAAS,CAAC,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;cAIpC,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;cAKzC,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAKzE"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BackupSyncService = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
/**
|
|
6
|
+
* Revisa periódicamente qué le falta a cada conexión de backup respecto a
|
|
7
|
+
* `main` y la pone al día:
|
|
8
|
+
* - Inserts/updates: documentos de `main` con `updatedTime` > checkpoint (uno por backup).
|
|
9
|
+
* - Deletes: usa la colección de tombstones (ver schema/tombstone.schema.ts), también con
|
|
10
|
+
* un checkpoint independiente por backup.
|
|
11
|
+
*
|
|
12
|
+
* Cada conexión de backup se sincroniza de forma INDEPENDIENTE: si una está
|
|
13
|
+
* caída o falla, las demás igual avanzan, y la caída simplemente se pondrá
|
|
14
|
+
* al día sola en una próxima corrida (no hay un único checkpoint compartido
|
|
15
|
+
* que se bloquee por una conexión problemática).
|
|
16
|
+
*
|
|
17
|
+
* Si `backupSync.enabled` es `false`, este servicio no arranca ningún
|
|
18
|
+
* temporizador interno: en ese caso eres tú (o un microservicio externo de
|
|
19
|
+
* bajo esfuerzo) quien debe llamar `syncNow()` cuando le convenga.
|
|
20
|
+
*/
|
|
21
|
+
class BackupSyncService {
|
|
22
|
+
constructor(entityName, mainModel, backupModels, backupLabels, tombstoneModel, checkpointModel, config) {
|
|
23
|
+
this.entityName = entityName;
|
|
24
|
+
this.mainModel = mainModel;
|
|
25
|
+
this.backupModels = backupModels;
|
|
26
|
+
this.backupLabels = backupLabels;
|
|
27
|
+
this.tombstoneModel = tombstoneModel;
|
|
28
|
+
this.checkpointModel = checkpointModel;
|
|
29
|
+
this.config = config;
|
|
30
|
+
this.running = false;
|
|
31
|
+
this.logger = new common_1.Logger(`BackupSync:${entityName}`);
|
|
32
|
+
}
|
|
33
|
+
onModuleInit() {
|
|
34
|
+
if (!this.config?.enabled || !this.backupModels.length)
|
|
35
|
+
return;
|
|
36
|
+
const interval = this.config.intervalMs ?? 60_000;
|
|
37
|
+
this.timer = setInterval(() => {
|
|
38
|
+
this.syncNow().catch((err) => this.logger.error(err));
|
|
39
|
+
}, interval);
|
|
40
|
+
if (this.config.runOnStart) {
|
|
41
|
+
this.syncNow().catch((err) => this.logger.error(err));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
onModuleDestroy() {
|
|
45
|
+
if (this.timer)
|
|
46
|
+
clearInterval(this.timer);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Dispara una verificación/actualización manual de TODOS los backups
|
|
50
|
+
* configurados. Útil si prefieres que un microservicio externo (de bajo
|
|
51
|
+
* esfuerzo) decida cuándo sincronizar, en vez del temporizador interno.
|
|
52
|
+
*/
|
|
53
|
+
async syncNow() {
|
|
54
|
+
if (this.running)
|
|
55
|
+
return { upserted: 0, deleted: 0 }; // evita solapamientos
|
|
56
|
+
this.running = true;
|
|
57
|
+
try {
|
|
58
|
+
const batchSize = this.config?.batchSize ?? 500;
|
|
59
|
+
const results = await Promise.allSettled(this.backupModels.map((backup, i) => this.syncBackup(backup, this.backupLabels[i], batchSize)));
|
|
60
|
+
const totals = { upserted: 0, deleted: 0 };
|
|
61
|
+
results.forEach((result, i) => {
|
|
62
|
+
if (result.status === 'fulfilled') {
|
|
63
|
+
totals.upserted += result.value.upserted;
|
|
64
|
+
totals.deleted += result.value.deleted;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
this.logger.warn(`[${this.backupLabels[i]}] no se pudo sincronizar, se reintentará en la próxima corrida: ${result.reason?.message ?? result.reason}`);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
// Solo borra tombstones que YA fueron aplicados en todos los backups (ver método).
|
|
71
|
+
await this.purgeFullyAppliedTombstones();
|
|
72
|
+
return totals;
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
this.running = false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Sincroniza un backup específico. Si no está listo, falla rápido sin tocar `main`. */
|
|
79
|
+
async syncBackup(backup, label, batchSize) {
|
|
80
|
+
if (backup.db.readyState !== 1) {
|
|
81
|
+
throw new Error(`conexión "${label}" no está lista (readyState=${backup.db.readyState})`);
|
|
82
|
+
}
|
|
83
|
+
const upserted = await this.syncUpsertsFor(backup, label, batchSize);
|
|
84
|
+
const deleted = await this.syncDeletesFor(backup, label, batchSize);
|
|
85
|
+
return { upserted, deleted };
|
|
86
|
+
}
|
|
87
|
+
async syncUpsertsFor(backup, label, batchSize) {
|
|
88
|
+
const checkpointKey = this.upsertCheckpointKey(label);
|
|
89
|
+
const checkpoint = await this.getCheckpoint(checkpointKey);
|
|
90
|
+
const pending = await this.mainModel
|
|
91
|
+
.find({ updatedTime: { $gt: checkpoint } })
|
|
92
|
+
.sort({ updatedTime: 1 })
|
|
93
|
+
.limit(batchSize)
|
|
94
|
+
.lean()
|
|
95
|
+
.exec();
|
|
96
|
+
if (!pending.length)
|
|
97
|
+
return 0;
|
|
98
|
+
await backup.bulkWrite(pending.map((doc) => ({
|
|
99
|
+
replaceOne: { filter: { _id: doc._id }, replacement: doc, upsert: true },
|
|
100
|
+
})));
|
|
101
|
+
const newCheckpoint = pending[pending.length - 1].updatedTime;
|
|
102
|
+
await this.setCheckpoint(checkpointKey, newCheckpoint);
|
|
103
|
+
return pending.length;
|
|
104
|
+
}
|
|
105
|
+
async syncDeletesFor(backup, label, batchSize) {
|
|
106
|
+
if (!this.tombstoneModel)
|
|
107
|
+
return 0;
|
|
108
|
+
const checkpointKey = this.deleteCheckpointKey(label);
|
|
109
|
+
const checkpoint = await this.getCheckpoint(checkpointKey);
|
|
110
|
+
const tombstones = await this.tombstoneModel
|
|
111
|
+
.find({ deletedAt: { $gt: checkpoint } })
|
|
112
|
+
.sort({ deletedAt: 1 })
|
|
113
|
+
.limit(batchSize)
|
|
114
|
+
.lean()
|
|
115
|
+
.exec();
|
|
116
|
+
if (!tombstones.length)
|
|
117
|
+
return 0;
|
|
118
|
+
const ids = tombstones.map((t) => t.docId);
|
|
119
|
+
await backup.deleteMany({ _id: { $in: ids } }).exec();
|
|
120
|
+
const newCheckpoint = tombstones[tombstones.length - 1].deletedAt;
|
|
121
|
+
await this.setCheckpoint(checkpointKey, newCheckpoint);
|
|
122
|
+
return tombstones.length;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Un tombstone solo se borra de la colección una vez que TODOS los
|
|
126
|
+
* backups configurados ya lo aplicaron (su checkpoint de deletes ya lo
|
|
127
|
+
* superó). Así, un backup que estuvo caído puede ponerse al día más
|
|
128
|
+
* tarde sin que el tombstone que necesitaba ya haya sido eliminado.
|
|
129
|
+
*/
|
|
130
|
+
async purgeFullyAppliedTombstones() {
|
|
131
|
+
if (!this.tombstoneModel || !this.backupLabels.length)
|
|
132
|
+
return;
|
|
133
|
+
const checkpoints = await Promise.all(this.backupLabels.map((label) => this.getCheckpoint(this.deleteCheckpointKey(label))));
|
|
134
|
+
const minCheckpoint = checkpoints.reduce((min, c) => (c.getTime() < min.getTime() ? c : min), checkpoints[0]);
|
|
135
|
+
if (!minCheckpoint || minCheckpoint.getTime() === 0)
|
|
136
|
+
return;
|
|
137
|
+
await this.tombstoneModel.deleteMany({ deletedAt: { $lte: minCheckpoint } }).exec();
|
|
138
|
+
}
|
|
139
|
+
upsertCheckpointKey(label) {
|
|
140
|
+
return `${this.entityName}:${label}:upsert`;
|
|
141
|
+
}
|
|
142
|
+
deleteCheckpointKey(label) {
|
|
143
|
+
return `${this.entityName}:${label}:delete`;
|
|
144
|
+
}
|
|
145
|
+
async getCheckpoint(key) {
|
|
146
|
+
const doc = await this.checkpointModel.findOne({ key }).lean().exec();
|
|
147
|
+
return doc?.value ?? new Date(0);
|
|
148
|
+
}
|
|
149
|
+
async setCheckpoint(key, value) {
|
|
150
|
+
await this.checkpointModel
|
|
151
|
+
.replaceOne({ key }, { key, value }, { upsert: true })
|
|
152
|
+
.exec();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
exports.BackupSyncService = BackupSyncService;
|
|
156
|
+
//# sourceMappingURL=backup-sync.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup-sync.service.js","sourceRoot":"","sources":["../../src/sync/backup-sync.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./backup-sync.service"), exports);
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,wDAAsC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { DynamicModule, Type } from '@nestjs/common';
|
|
2
|
+
import { FilterQuery, Schema, UpdateQuery } from 'mongoose';
|
|
3
|
+
import { Observable } from 'rxjs';
|
|
4
|
+
/**
|
|
5
|
+
* Reemplaza esta interfaz por tu BaseOrmOptions real (la que ya tienes en
|
|
6
|
+
* `../../../types`) si quieres tipado estricto. La dejamos abierta para que
|
|
7
|
+
* la librería no dependa de tu monorepo.
|
|
8
|
+
*/
|
|
9
|
+
export interface BaseOrmOptions {
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}
|
|
12
|
+
export type RepositoryQueryTarget = 'main' | 'cache';
|
|
13
|
+
export interface CacheConnectionConfig {
|
|
14
|
+
/** Nombre de la conexión de caché (igual que usas hoy con ConnectionNames). */
|
|
15
|
+
connectionName: string;
|
|
16
|
+
/** Segundos que un documento debe vivir en la conexión de caché (índice TTL). */
|
|
17
|
+
ttlSeconds: number;
|
|
18
|
+
}
|
|
19
|
+
export interface BackupConnectionConfig {
|
|
20
|
+
/** Nombre de la conexión de respaldo (solo-escritura). */
|
|
21
|
+
connectionName: string;
|
|
22
|
+
}
|
|
23
|
+
export interface BackupSyncConfig {
|
|
24
|
+
/** Si en false, nadie sincroniza automáticamente (puedes llamar syncNow() tú mismo desde un microservicio). */
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
/** Cada cuánto (ms) se revisa main vs backups. Default: 60000 (1 min). */
|
|
27
|
+
intervalMs?: number;
|
|
28
|
+
/** Si debe correr una verificación inmediatamente al arrancar el módulo. Default: false. */
|
|
29
|
+
runOnStart?: boolean;
|
|
30
|
+
/** Cuántos documentos procesar por lote en cada verificación. Default: 500. */
|
|
31
|
+
batchSize?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface PendingOpsConfig {
|
|
34
|
+
/** Cada cuánto (ms) se reintentan las escrituras pendientes hacia cache/backups. Default: 5000. */
|
|
35
|
+
retryIntervalMs?: number;
|
|
36
|
+
/** Máximo de operaciones pendientes en memoria por conexión secundaria antes de descartar la más vieja. Default: 1000. */
|
|
37
|
+
maxQueueSize?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface FindOptions {
|
|
40
|
+
/**
|
|
41
|
+
* 'cache' | 'main'. Si no se especifica:
|
|
42
|
+
* - Si hay conexión de caché configurada -> intenta caché, si no encuentra (o si la caché
|
|
43
|
+
* está caída) cae a main.
|
|
44
|
+
* - Si no hay caché -> consulta main directamente.
|
|
45
|
+
* Si se especifica explícitamente, NO hace fallback ni repuebla caché.
|
|
46
|
+
*/
|
|
47
|
+
target?: RepositoryQueryTarget;
|
|
48
|
+
sort?: Record<string, 1 | -1>;
|
|
49
|
+
limit?: number;
|
|
50
|
+
skip?: number;
|
|
51
|
+
projection?: Record<string, 0 | 1>;
|
|
52
|
+
}
|
|
53
|
+
export interface UpdateManyResult {
|
|
54
|
+
matched: number;
|
|
55
|
+
modified: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Contrato público que debe cumplir cualquier servicio de repositorio:
|
|
59
|
+
* tanto `BaseRepositoryService` (el que se usa por defecto) como cualquier
|
|
60
|
+
* servicio personalizado que pases en `RepositoryModuleOptions.customService`.
|
|
61
|
+
*
|
|
62
|
+
* Si decides implementar tu propio servicio en vez de usar
|
|
63
|
+
* `BaseRepositoryService`, esta es la interfaz que tu clase tiene que
|
|
64
|
+
* cumplir — TypeScript te lo exige apenas la asignas a `customService`.
|
|
65
|
+
*/
|
|
66
|
+
export interface IBaseRepositoryService<T = any> {
|
|
67
|
+
findOne(filter: FilterQuery<T>, opts?: FindOptions): Observable<T | null>;
|
|
68
|
+
find(filter?: FilterQuery<T>, opts?: FindOptions): Observable<T[]>;
|
|
69
|
+
create(dto: Partial<T>): Observable<T>;
|
|
70
|
+
insertMany(dtos: Partial<T>[]): Observable<T[]>;
|
|
71
|
+
updateOne(filter: FilterQuery<T>, update: UpdateQuery<T>): Observable<T | null>;
|
|
72
|
+
updateMany(filter: FilterQuery<T>, update: UpdateQuery<T>): Observable<UpdateManyResult>;
|
|
73
|
+
deleteOne(filter: FilterQuery<T>): Observable<boolean>;
|
|
74
|
+
deleteMany(filter: FilterQuery<T>): Observable<number>;
|
|
75
|
+
count(filter?: FilterQuery<T>): Observable<number>;
|
|
76
|
+
aggregate<R = any>(pipeline: any[]): Observable<R[]>;
|
|
77
|
+
updateObject(object: any): Observable<T | null>;
|
|
78
|
+
upsertBulk(bulkData: any[]): Observable<any>;
|
|
79
|
+
updateBulk(bulkData: any[]): Observable<any>;
|
|
80
|
+
}
|
|
81
|
+
export interface RepositoryModuleOptions<T> {
|
|
82
|
+
/** Clase de la entidad (la misma que hoy pasas a `super(model, Position, options)`). */
|
|
83
|
+
entity: Type<T>;
|
|
84
|
+
/** Schema de mongoose de la entidad. */
|
|
85
|
+
schema: Schema;
|
|
86
|
+
/** Conexión principal (ej. ConnectionNames.OPERATION_MDB). */
|
|
87
|
+
connectionName: string;
|
|
88
|
+
/** Opciones equivalentes a tu BaseOrmOptions actual. */
|
|
89
|
+
options?: BaseOrmOptions;
|
|
90
|
+
/** Conexión de caché opcional, con su propio TTL. */
|
|
91
|
+
cache?: CacheConnectionConfig;
|
|
92
|
+
/** Conexiones de respaldo, solo-escritura (réplicas completas de main). */
|
|
93
|
+
backups?: BackupConnectionConfig[];
|
|
94
|
+
/** Configuración de la verificación periódica de los backups contra main. */
|
|
95
|
+
backupSync?: BackupSyncConfig;
|
|
96
|
+
/**
|
|
97
|
+
* Configuración de la cola de reintentos para cache/backups. Si una de
|
|
98
|
+
* estas conexiones está caída o no lista, las escrituras quedan
|
|
99
|
+
* pendientes aquí en vez de fallar, y se reintentan solas.
|
|
100
|
+
*/
|
|
101
|
+
pendingOps?: PendingOpsConfig;
|
|
102
|
+
/**
|
|
103
|
+
* Servicio personalizado a usar en vez de `BaseRepositoryService`. Debe
|
|
104
|
+
* ser una clase que, al instanciarla, cumpla `IBaseRepositoryService<T>`
|
|
105
|
+
* (TypeScript te lo exige al asignarla aquí). Si no la pasas, se usa
|
|
106
|
+
* `BaseRepositoryService` por defecto.
|
|
107
|
+
*
|
|
108
|
+
* Recomendado: extiende `BaseRepositoryService<T>` y llama `super(...)`
|
|
109
|
+
* con los mismos argumentos que recibe tu constructor — así heredas toda
|
|
110
|
+
* la resiliencia de caché/backups y solo sobreescribes lo que te
|
|
111
|
+
* interese. Ver `examples/custom-repository-service.example.ts`.
|
|
112
|
+
*/
|
|
113
|
+
customService?: Type<IBaseRepositoryService<T>>;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Lo que retorna RepositoryOrmModule.register(...). Es un DynamicModule normal
|
|
117
|
+
* de Nest + el atributo REPOSITORY_SERVICE_KEY que necesita @RepositoryInject().
|
|
118
|
+
*/
|
|
119
|
+
export interface RepositoryDynamicModule extends DynamicModule {
|
|
120
|
+
REPOSITORY_SERVICE_KEY: string;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./pending-ops-queue"), exports);
|
|
18
|
+
__exportStar(require("./repository-token.util"), exports);
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,sDAAoC;AACpC,0DAAwC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Logger } from '@nestjs/common';
|
|
2
|
+
import { Connection } from 'mongoose';
|
|
3
|
+
export type PendingOperation = () => Promise<void>;
|
|
4
|
+
interface QueueItem {
|
|
5
|
+
label: string;
|
|
6
|
+
run: PendingOperation;
|
|
7
|
+
attempts: number;
|
|
8
|
+
enqueuedAt: Date;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Cola en memoria de operaciones que no se pudieron aplicar de inmediato en
|
|
12
|
+
* una conexión secundaria (caché o backup) porque la conexión no estaba
|
|
13
|
+
* lista o la operación falló. Se reintenta periódicamente y, además,
|
|
14
|
+
* apenas la conexión emite el evento `connected`.
|
|
15
|
+
*
|
|
16
|
+
* Las operaciones que se encolan aquí son siempre upserts/deletes por `_id`
|
|
17
|
+
* (idempotentes), así que reintentarlas en orden FIFO, incluso varias
|
|
18
|
+
* veces, es seguro.
|
|
19
|
+
*
|
|
20
|
+
* Nota: esta cola vive en memoria del proceso. Si el proceso se reinicia
|
|
21
|
+
* mientras hay operaciones pendientes, se pierden — pero no hay pérdida de
|
|
22
|
+
* datos real, porque `BackupSyncService` (para backups) vuelve a poner al
|
|
23
|
+
* día cualquier backup comparando contra `main` en su próxima corrida. Para
|
|
24
|
+
* la caché, un siguiente `find`/`findOne` simplemente la repuebla.
|
|
25
|
+
*/
|
|
26
|
+
export declare class PendingOpsQueue {
|
|
27
|
+
protected readonly label: string;
|
|
28
|
+
protected readonly intervalMs: number;
|
|
29
|
+
protected readonly maxQueueSize: number;
|
|
30
|
+
protected readonly logger: Logger;
|
|
31
|
+
protected queue: QueueItem[];
|
|
32
|
+
protected flushing: boolean;
|
|
33
|
+
protected timer?: NodeJS.Timeout;
|
|
34
|
+
constructor(label: string, intervalMs?: number, maxQueueSize?: number);
|
|
35
|
+
size(): number;
|
|
36
|
+
enqueue(run: PendingOperation, opLabel?: string): void;
|
|
37
|
+
start(): void;
|
|
38
|
+
stop(): void;
|
|
39
|
+
/** Reintenta de inmediato apenas la conexión vuelve a estar disponible, sin esperar al siguiente tick del timer. */
|
|
40
|
+
watchConnection(connection: Connection): void;
|
|
41
|
+
flush(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=pending-ops-queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-ops-queue.d.ts","sourceRoot":"","sources":["../../src/utils/pending-ops-queue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnD,UAAU,SAAS;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,gBAAgB,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,IAAI,CAAC;CACpB;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,eAAe;IAOpB,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM;IAChC,SAAS,CAAC,QAAQ,CAAC,UAAU;IAC7B,SAAS,CAAC,QAAQ,CAAC,YAAY;IARnC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAClC,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,CAAM;IAClC,SAAS,CAAC,QAAQ,UAAS;IAC3B,SAAS,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;gBAGV,KAAK,EAAE,MAAM,EACb,UAAU,SAAO,EACjB,YAAY,SAAO;IAK1C,IAAI,IAAI,MAAM;IAId,OAAO,CAAC,GAAG,EAAE,gBAAgB,EAAE,OAAO,SAAa,GAAG,IAAI;IAU1D,KAAK,IAAI,IAAI;IAOb,IAAI,IAAI,IAAI;IAKZ,oHAAoH;IACpH,eAAe,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAMvC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAuB/B"}
|