@nextage/era-lib 1.0.195 → 1.0.196
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/build/events/types/enums.d.ts.map +1 -1
- package/build/events/types/enums.js.map +1 -1
- package/build/services/index.d.ts +2 -0
- package/build/services/index.d.ts.map +1 -1
- package/build/services/index.js +2 -0
- package/build/services/index.js.map +1 -1
- package/build/services/tenant-archive.d.ts +90 -0
- package/build/services/tenant-archive.d.ts.map +1 -0
- package/build/services/tenant-archive.interfaces.d.ts +54 -0
- package/build/services/tenant-archive.interfaces.d.ts.map +1 -0
- package/build/services/tenant-archive.interfaces.js +3 -0
- package/build/services/tenant-archive.interfaces.js.map +1 -0
- package/build/services/tenant-archive.js +120 -0
- package/build/services/tenant-archive.js.map +1 -0
- package/build/utils/env.constants.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../../../src/events/types/enums.ts"],"names":[],"mappings":"AAAA,oBAAY,QAAQ;IAElB;;sBAEkB;IAGlB,mBAAmB,yBAA8B;IACjD,mBAAmB,yBAA8B;IACjD,mBAAmB,yBAA8B;IAGjD,eAAe,qBAA8B;IAC7C,eAAe,qBAA8B;IAC7C,eAAe,qBAA8B;IAG7C,oBAAoB,0BAA8B;IAClD,oBAAoB,0BAA8B;IAClD,oBAAoB,0BAA8B;IAGlD,kBAAkB,wBAA8B;IAChD,kBAAkB,wBAA8B;IAChD,kBAAkB,wBAA8B;IAGhD,uBAAuB,6BAA8B;IACrD,uBAAuB,6BAA8B;IACrD,uBAAuB,6BAA8B;IAGrD,gBAAgB,sBAA8B;IAC9C,gBAAgB,sBAA8B;IAC9C,gBAAgB,sBAA8B;IAE9C;;sBAEkB;IAGlB,iBAAiB,uBAA2B;IAC5C,iBAAiB,uBAA2B;IAC5C,iBAAiB,uBAA2B;IAG5C,cAAc,oBAA2B;IACzC,cAAc,oBAA2B;IACzC,cAAc,oBAA2B;IAGzC,aAAa,mBAA2B;IACxC,aAAa,mBAA2B;IACxC,aAAa,mBAA2B;IAGxC,oBAAoB,iBAAkB;IACtC,oBAAoB,iBAAkB;IACtC,oBAAoB,iBAAkB;IAEtC;;uBAEmB;IAGnB,WAAW,
|
|
1
|
+
{"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../../../src/events/types/enums.ts"],"names":[],"mappings":"AAAA,oBAAY,QAAQ;IAElB;;sBAEkB;IAGlB,mBAAmB,yBAA8B;IACjD,mBAAmB,yBAA8B;IACjD,mBAAmB,yBAA8B;IAGjD,eAAe,qBAA8B;IAC7C,eAAe,qBAA8B;IAC7C,eAAe,qBAA8B;IAG7C,oBAAoB,0BAA8B;IAClD,oBAAoB,0BAA8B;IAClD,oBAAoB,0BAA8B;IAGlD,kBAAkB,wBAA8B;IAChD,kBAAkB,wBAA8B;IAChD,kBAAkB,wBAA8B;IAGhD,uBAAuB,6BAA8B;IACrD,uBAAuB,6BAA8B;IACrD,uBAAuB,6BAA8B;IAGrD,gBAAgB,sBAA8B;IAC9C,gBAAgB,sBAA8B;IAC9C,gBAAgB,sBAA8B;IAE9C;;sBAEkB;IAGlB,iBAAiB,uBAA2B;IAC5C,iBAAiB,uBAA2B;IAC5C,iBAAiB,uBAA2B;IAG5C,cAAc,oBAA2B;IACzC,cAAc,oBAA2B;IACzC,cAAc,oBAA2B;IAGzC,aAAa,mBAA2B;IACxC,aAAa,mBAA2B;IACxC,aAAa,mBAA2B;IAGxC,oBAAoB,iBAAkB;IACtC,oBAAoB,iBAAkB;IACtC,oBAAoB,iBAAkB;IAEtC;;uBAEmB;IAGnB,WAAW,iBAA0B;IACrC,WAAW,iBAA0B;IACrC,WAAW,iBAA0B;IAGrC,mBAAmB,yBAA0B;IAC7C,mBAAmB,yBAA0B;IAC7C,mBAAmB,yBAA0B;IAG7C,oBAAoB,0BAA0B;IAE9C;;uBAEmB;IAGnB,cAAc,oBAA4B;IAC1C,cAAc,oBAA4B;IAC1C,cAAc,oBAA4B;IAG1C,mBAAmB,0BAA6B;IAChD,mBAAmB,0BAA6B;IAChD,mBAAmB,0BAA6B;IAGhD,qBAAqB,4BAA6B;IAClD,qBAAqB,4BAA6B;IAClD,qBAAqB,4BAA6B;IAGlD,oBAAoB,2BAA6B;IACjD,oBAAoB,2BAA6B;IACjD,oBAAoB,2BAA6B;IAGjD,sBAAsB,6BAA6B;IACnD,sBAAsB,6BAA6B;IACnD,sBAAsB,6BAA6B;IAGnD,kBAAkB,yBAA6B;IAC/C,kBAAkB,yBAA6B;IAC/C,kBAAkB,yBAA6B;IAG/C,sBAAsB,6BAA6B;IACnD,sBAAsB,6BAA6B;IACnD,sBAAsB,6BAA6B;IAGnD,qBAAqB,4BAA6B;IAClD,qBAAqB,4BAA6B;IAClD,qBAAqB,4BAA6B;IAElD;;sBAEkB;IAGlB,mBAAmB,0BAA0B;IAC7C,mBAAmB,0BAA0B;IAC7C,mBAAmB,0BAA0B;IAG7C,gBAAgB,uBAA0B;IAC1C,gBAAgB,uBAA0B;IAC1C,gBAAgB,uBAA0B;IAE1C;;sBAEkB;IAOlB;;sBAEkB;IAKlB,aAAa,mBAAyB;CACvC;AAED,oBAAY,cAAc;IACxB,MAAM,iBAAiB;IACvB,MAAM,iBAAiB;IACvB,MAAM,iBAAiB;CACxB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enums.js","sourceRoot":"","sources":["../../../src/events/types/enums.ts"],"names":[],"mappings":";;;AAAA,IAAY,QAwJX;AAxJD,WAAY,QAAQ;IAElB;;sBAEkB;IAElB,gBAAgB;IAChB,wDAAiD,CAAA;IACjD,wDAAiD,CAAA;IACjD,wDAAiD,CAAA;IAEjD,WAAW;IACX,gDAA6C,CAAA;IAC7C,gDAA6C,CAAA;IAC7C,gDAA6C,CAAA;IAE7C,gBAAgB;IAChB,0DAAkD,CAAA;IAClD,0DAAkD,CAAA;IAClD,0DAAkD,CAAA;IAElD,cAAc;IACd,sDAAgD,CAAA;IAChD,sDAAgD,CAAA;IAChD,sDAAgD,CAAA;IAEhD,mBAAmB;IACnB,gEAAqD,CAAA;IACrD,gEAAqD,CAAA;IACrD,gEAAqD,CAAA;IAErD,YAAY;IACZ,kDAA8C,CAAA;IAC9C,kDAA8C,CAAA;IAC9C,kDAA8C,CAAA;IAE9C;;sBAEkB;IAElB,cAAc;IACd,oDAA4C,CAAA;IAC5C,oDAA4C,CAAA;IAC5C,oDAA4C,CAAA;IAE5C,UAAU;IACV,8CAAyC,CAAA;IACzC,8CAAyC,CAAA;IACzC,8CAAyC,CAAA;IAEzC,SAAS;IACT,4CAAwC,CAAA;IACxC,4CAAwC,CAAA;IACxC,4CAAwC,CAAA;IAExC,gBAAgB;IAChB,iDAAsC,CAAA;IACtC,iDAAsC,CAAA;IACtC,iDAAsC,CAAA;IAEtC;;uBAEmB;IAEnB,OAAO;IACP,
|
|
1
|
+
{"version":3,"file":"enums.js","sourceRoot":"","sources":["../../../src/events/types/enums.ts"],"names":[],"mappings":";;;AAAA,IAAY,QAwJX;AAxJD,WAAY,QAAQ;IAElB;;sBAEkB;IAElB,gBAAgB;IAChB,wDAAiD,CAAA;IACjD,wDAAiD,CAAA;IACjD,wDAAiD,CAAA;IAEjD,WAAW;IACX,gDAA6C,CAAA;IAC7C,gDAA6C,CAAA;IAC7C,gDAA6C,CAAA;IAE7C,gBAAgB;IAChB,0DAAkD,CAAA;IAClD,0DAAkD,CAAA;IAClD,0DAAkD,CAAA;IAElD,cAAc;IACd,sDAAgD,CAAA;IAChD,sDAAgD,CAAA;IAChD,sDAAgD,CAAA;IAEhD,mBAAmB;IACnB,gEAAqD,CAAA;IACrD,gEAAqD,CAAA;IACrD,gEAAqD,CAAA;IAErD,YAAY;IACZ,kDAA8C,CAAA;IAC9C,kDAA8C,CAAA;IAC9C,kDAA8C,CAAA;IAE9C;;sBAEkB;IAElB,cAAc;IACd,oDAA4C,CAAA;IAC5C,oDAA4C,CAAA;IAC5C,oDAA4C,CAAA;IAE5C,UAAU;IACV,8CAAyC,CAAA;IACzC,8CAAyC,CAAA;IACzC,8CAAyC,CAAA;IAEzC,SAAS;IACT,4CAAwC,CAAA;IACxC,4CAAwC,CAAA;IACxC,4CAAwC,CAAA;IAExC,gBAAgB;IAChB,iDAAsC,CAAA;IACtC,iDAAsC,CAAA;IACtC,iDAAsC,CAAA;IAEtC;;uBAEmB;IAEnB,OAAO;IACP,wCAAqC,CAAA;IACrC,wCAAqC,CAAA;IACrC,wCAAqC,CAAA;IAErC,eAAe;IACf,wDAA6C,CAAA;IAC7C,wDAA6C,CAAA;IAC7C,wDAA6C,CAAA;IAC7C,+EAA+E;IAC/E,oEAAoE;IACpE,0DAA8C,CAAA;IAE9C;;uBAEmB;IAEnB,UAAU;IACV,8CAA0C,CAAA;IAC1C,8CAA0C,CAAA;IAC1C,8CAA0C,CAAA;IAE1C,kBAAkB;IAClB,yDAAgD,CAAA;IAChD,yDAAgD,CAAA;IAChD,yDAAgD,CAAA;IAEhD,iBAAiB;IACjB,6DAAkD,CAAA;IAClD,6DAAkD,CAAA;IAClD,6DAAkD,CAAA;IAElD,kBAAkB;IAClB,2DAAiD,CAAA;IACjD,2DAAiD,CAAA;IACjD,2DAAiD,CAAA;IAEjD,WAAW;IACX,+DAAmD,CAAA;IACnD,+DAAmD,CAAA;IACnD,+DAAmD,CAAA;IAEnD,eAAe;IACf,uDAA+C,CAAA;IAC/C,uDAA+C,CAAA;IAC/C,uDAA+C,CAAA;IAE/C,WAAW;IACX,+DAAmD,CAAA;IACnD,+DAAmD,CAAA;IACnD,+DAAmD,CAAA;IAEnD,UAAU;IACV,6DAAkD,CAAA;IAClD,6DAAkD,CAAA;IAClD,6DAAkD,CAAA;IAElD;;sBAEkB;IAElB,gBAAgB;IAChB,yDAA6C,CAAA;IAC7C,yDAA6C,CAAA;IAC7C,yDAA6C,CAAA;IAE7C,aAAa;IACb,mDAA0C,CAAA;IAC1C,mDAA0C,CAAA;IAC1C,mDAA0C,CAAA;IAE1C;;sBAEkB;IAElB,mBAAmB;IACnB,2CAA2C;IAC3C,2CAA2C;IAC3C,2CAA2C;IAE3C;;sBAEkB;IAElB,0EAA0E;IAC1E,uEAAuE;IACvE,yDAAyD;IACzD,4CAAsC,CAAA;AACxC,CAAC,EAxJW,QAAQ,wBAAR,QAAQ,QAwJnB;AAED,IAAY,cAIX;AAJD,WAAY,cAAc;IACxB,yCAAuB,CAAA;IACvB,yCAAuB,CAAA;IACvB,yCAAuB,CAAA;AACzB,CAAC,EAJW,cAAc,8BAAd,cAAc,QAIzB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC"}
|
package/build/services/index.js
CHANGED
|
@@ -15,4 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./password"), exports);
|
|
18
|
+
__exportStar(require("./tenant-archive"), exports);
|
|
19
|
+
__exportStar(require("./tenant-archive.interfaces"), exports);
|
|
18
20
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6CAA2B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6CAA2B;AAC3B,mDAAiC;AACjC,8DAA4C"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/*********************************************************************************************
|
|
2
|
+
* *
|
|
3
|
+
* Tenant archive - how a deleted organization's documents are set aside, and put back. *
|
|
4
|
+
* *
|
|
5
|
+
* Deleting an organization spans two services and eleven-plus collections, and no user *
|
|
6
|
+
* confirmation makes an irreversible cascade of that size safe. So nothing is deleted *
|
|
7
|
+
* outright: documents are MOVED to `tenantArchive`, where a TTL index disposes of them once *
|
|
8
|
+
* the recovery window closes. "Retained no longer than necessary" is satisfied by the *
|
|
9
|
+
* deadline, not by the immediacy of the deletion. *
|
|
10
|
+
* *
|
|
11
|
+
* This module owns the MECHANIC only. WHICH collections belong to a tenant is a question *
|
|
12
|
+
* each service answers for itself - auth-srv its identity documents, app-srv its tenant *
|
|
13
|
+
* data - which is also why the model types here are structural (T42). *
|
|
14
|
+
* *
|
|
15
|
+
*********************************************************************************************/
|
|
16
|
+
import { ArchivableModel } from './tenant-archive.interfaces';
|
|
17
|
+
import { ArchiveOutcome } from './tenant-archive.interfaces';
|
|
18
|
+
import { TenantArchiveStore } from './tenant-archive.interfaces';
|
|
19
|
+
/**
|
|
20
|
+
* Computes the instant an archived tenant stops being recoverable.
|
|
21
|
+
*
|
|
22
|
+
* Exported because the caller must compute it ONCE for a whole cascade and pass the
|
|
23
|
+
* same value to every collection: a per-collection deadline would let the TTL index
|
|
24
|
+
* expire half a tenant while the rest is still restorable, and a restore would then
|
|
25
|
+
* bring back an organization whose users have already been swept.
|
|
26
|
+
*
|
|
27
|
+
* @param purgeValidity the configured window (`APP.config.app.tenant.purgeValidity`,
|
|
28
|
+
* e.g. `'30d'`); anything unparseable falls back to 30 days
|
|
29
|
+
* @param from the instant the window starts (defaults to now)
|
|
30
|
+
*/
|
|
31
|
+
export declare function purgeDeadline(purgeValidity?: string | number, from?: Date): Date;
|
|
32
|
+
/**
|
|
33
|
+
* Files documents the caller ALREADY holds, without touching any source collection.
|
|
34
|
+
*
|
|
35
|
+
* The primitive {@link archiveDocuments} is built on, and useful on its own whenever
|
|
36
|
+
* the removal is not ours to perform: the organization record itself is deleted by
|
|
37
|
+
* the framework's `remove` - which is also what publishes `organization.removed` and
|
|
38
|
+
* writes the outbox row - so it can only be archived from the document in hand,
|
|
39
|
+
* before or after that call, never by reading it back.
|
|
40
|
+
*
|
|
41
|
+
* @param archive the shared `tenantArchive` store
|
|
42
|
+
* @param sourceCollection where the documents came from, and where a restore puts them
|
|
43
|
+
* @param documents the documents themselves, archived verbatim
|
|
44
|
+
* @param tenantId the organization being deleted
|
|
45
|
+
* @param purgeAt the shared deadline for the whole cascade
|
|
46
|
+
* @returns how many entries were written
|
|
47
|
+
*/
|
|
48
|
+
export declare function archiveEntries(archive: TenantArchiveStore, sourceCollection: string, documents: Record<string, unknown>[], tenantId: string, purgeAt: Date): Promise<ArchiveOutcome>;
|
|
49
|
+
/**
|
|
50
|
+
* Moves every document matching `filter` out of `source` and into the archive.
|
|
51
|
+
*
|
|
52
|
+
* The order is the whole point: the entries are written FIRST and the originals
|
|
53
|
+
* removed only once that succeeded. The reverse would turn a failed write into data
|
|
54
|
+
* loss, which is exactly what the archive exists to prevent - the same reasoning
|
|
55
|
+
* that puts the organization first in the auth-srv cascade (T29).
|
|
56
|
+
*
|
|
57
|
+
* ⚠️ Not a transaction. A crash between the two steps leaves the documents in BOTH
|
|
58
|
+
* places, which the restore tolerates (it puts back what is already there, by `_id`)
|
|
59
|
+
* whereas the opposite ordering would leave them in NEITHER.
|
|
60
|
+
*
|
|
61
|
+
* The document is archived VERBATIM, `_id` included: relations across collections are
|
|
62
|
+
* by `_id`, so regenerating one would restore a tenant whose relations no longer match.
|
|
63
|
+
*
|
|
64
|
+
* @param source the collection being emptied
|
|
65
|
+
* @param archive the shared `tenantArchive` store
|
|
66
|
+
* @param filter which documents belong to the tenant, as only the caller knows
|
|
67
|
+
* @param tenantId the organization being deleted
|
|
68
|
+
* @param purgeAt the shared deadline for the whole cascade - see {@link purgeDeadline}
|
|
69
|
+
* @returns how many documents were moved
|
|
70
|
+
*/
|
|
71
|
+
export declare function archiveDocuments(source: ArchivableModel, archive: TenantArchiveStore, filter: Record<string, unknown>, tenantId: string, purgeAt: Date): Promise<ArchiveOutcome>;
|
|
72
|
+
/**
|
|
73
|
+
* Puts a tenant's documents back into `source`, and drops their archive entries.
|
|
74
|
+
*
|
|
75
|
+
* Mirrors {@link archiveDocuments}, including the ordering rule: the documents are
|
|
76
|
+
* reinserted FIRST and the archive entries dropped only afterwards, so a failure
|
|
77
|
+
* leaves them recoverable rather than gone.
|
|
78
|
+
*
|
|
79
|
+
* ⚠️ Restores ONLY what this service owns: the caller passes its own model, and the
|
|
80
|
+
* source collection name it carries is what selects the entries. The shared archive
|
|
81
|
+
* is therefore safe to read from both services - each sees only its own rows, and
|
|
82
|
+
* the two sets are disjoint by construction.
|
|
83
|
+
*
|
|
84
|
+
* @param source the collection being refilled
|
|
85
|
+
* @param archive the shared `tenantArchive` store
|
|
86
|
+
* @param tenantId the organization being restored
|
|
87
|
+
* @returns how many documents were put back
|
|
88
|
+
*/
|
|
89
|
+
export declare function restoreDocuments(source: ArchivableModel, archive: TenantArchiveStore, tenantId: string): Promise<ArchiveOutcome>;
|
|
90
|
+
//# sourceMappingURL=tenant-archive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-archive.d.ts","sourceRoot":"","sources":["../../src/services/tenant-archive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;+FAc+F;AAI/F,OAAO,EAAE,eAAe,EAAE,MAAS,6BAA6B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAU,6BAA6B,CAAC;AAEjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAKjE;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,GAAE,IAAiB,GAAG,IAAI,CAG5F;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAW,kBAAkB,EACpC,gBAAgB,EAAE,MAAM,EACxB,SAAS,EAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC3C,QAAQ,EAAU,MAAM,EACxB,OAAO,EAAW,IAAI,GACrB,OAAO,CAAC,cAAc,CAAC,CAWzB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAI,eAAe,EACzB,OAAO,EAAG,kBAAkB,EAC5B,MAAM,EAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAG,IAAI,GACb,OAAO,CAAC,cAAc,CAAC,CAWzB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAI,eAAe,EACzB,OAAO,EAAG,kBAAkB,EAC5B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,CAAC,CAYzB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { TenantArchiveItem } from '../interfaces';
|
|
2
|
+
/**
|
|
3
|
+
* A collection the archive can empty and refill.
|
|
4
|
+
*
|
|
5
|
+
* Structural, like {@link ExistenceCheckModel} and for the same reason: the archive
|
|
6
|
+
* is a MECHANIC, not a rule about a specific entity. auth-srv hands it `users` and
|
|
7
|
+
* `mixin`, app-srv the eleven tenant collections, and a future service whatever it
|
|
8
|
+
* owns - none of which this library knows, nor should.
|
|
9
|
+
*
|
|
10
|
+
* `listNative` is deliberate over `list`: the scoped read applies `beforeGet`, which
|
|
11
|
+
* resolves the caller's tenant, and the documents being archived belong to an
|
|
12
|
+
* organization the caller is precisely in the act of deleting. The same holds for
|
|
13
|
+
* `removeNative` over `remove`.
|
|
14
|
+
*/
|
|
15
|
+
export interface ArchivableModel {
|
|
16
|
+
mgModel: {
|
|
17
|
+
collection: {
|
|
18
|
+
collectionName: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
listNative(filter: Record<string, unknown>): {
|
|
22
|
+
toArray(): Promise<Record<string, unknown>[]>;
|
|
23
|
+
};
|
|
24
|
+
removeNative(filter: Record<string, unknown>): Promise<{
|
|
25
|
+
deletedCount?: number;
|
|
26
|
+
}>;
|
|
27
|
+
insertManyNative(items: any[], options: any): Promise<unknown>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The archive itself, seen by the mechanic that fills and drains it.
|
|
31
|
+
*
|
|
32
|
+
* Kept separate from {@link ArchivableModel} even though the shapes overlap: the
|
|
33
|
+
* archive is never a SOURCE, and typing it as one would make
|
|
34
|
+
* `archiveDocuments(archive, archive, ...)` a legal call.
|
|
35
|
+
*/
|
|
36
|
+
export interface TenantArchiveStore {
|
|
37
|
+
listNative(filter: Record<string, unknown>): {
|
|
38
|
+
toArray(): Promise<Record<string, unknown>[]>;
|
|
39
|
+
};
|
|
40
|
+
removeNative(filter: Record<string, unknown>): Promise<{
|
|
41
|
+
deletedCount?: number;
|
|
42
|
+
}>;
|
|
43
|
+
insertManyNative(items: any[], options: any): Promise<unknown>;
|
|
44
|
+
}
|
|
45
|
+
/** What a single archive or restore actually moved. */
|
|
46
|
+
export interface ArchiveOutcome {
|
|
47
|
+
/** Documents moved out of (or back into) the source collection. */
|
|
48
|
+
moved: number;
|
|
49
|
+
/** The source collection, echoed back so a caller can log the cascade. */
|
|
50
|
+
sourceCollection: string;
|
|
51
|
+
}
|
|
52
|
+
/** An archived entry as it comes back from the store, `_id` of the archive row aside. */
|
|
53
|
+
export type ArchivedEntry = TenantArchiveItem & Record<string, unknown>;
|
|
54
|
+
//# sourceMappingURL=tenant-archive.interfaces.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-archive.interfaces.d.ts","sourceRoot":"","sources":["../../src/services/tenant-archive.interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe;IAE9B,OAAO,EAAE;QACP,UAAU,EAAE;YAAE,cAAc,EAAE,MAAM,CAAA;SAAE,CAAC;KACxC,CAAC;IAEF,UAAU,CAAG,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QAAE,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;KAAE,CAAC;IACjG,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAIlF,gBAAgB,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAChE;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IAEjC,UAAU,CAAO,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QAAE,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;KAAE,CAAC;IACrG,YAAY,CAAK,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtF,gBAAgB,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAChE;AAED,uDAAuD;AACvD,MAAM,WAAW,cAAc;IAE7B,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IAEd,0EAA0E;IAC1E,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,yFAAyF;AACzF,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-archive.interfaces.js","sourceRoot":"","sources":["../../src/services/tenant-archive.interfaces.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*********************************************************************************************
|
|
3
|
+
* *
|
|
4
|
+
* Tenant archive - how a deleted organization's documents are set aside, and put back. *
|
|
5
|
+
* *
|
|
6
|
+
* Deleting an organization spans two services and eleven-plus collections, and no user *
|
|
7
|
+
* confirmation makes an irreversible cascade of that size safe. So nothing is deleted *
|
|
8
|
+
* outright: documents are MOVED to `tenantArchive`, where a TTL index disposes of them once *
|
|
9
|
+
* the recovery window closes. "Retained no longer than necessary" is satisfied by the *
|
|
10
|
+
* deadline, not by the immediacy of the deletion. *
|
|
11
|
+
* *
|
|
12
|
+
* This module owns the MECHANIC only. WHICH collections belong to a tenant is a question *
|
|
13
|
+
* each service answers for itself - auth-srv its identity documents, app-srv its tenant *
|
|
14
|
+
* data - which is also why the model types here are structural (T42). *
|
|
15
|
+
* *
|
|
16
|
+
*********************************************************************************************/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.purgeDeadline = purgeDeadline;
|
|
19
|
+
exports.archiveEntries = archiveEntries;
|
|
20
|
+
exports.archiveDocuments = archiveDocuments;
|
|
21
|
+
exports.restoreDocuments = restoreDocuments;
|
|
22
|
+
const functs_1 = require("../utils/functs");
|
|
23
|
+
/** Fallback window, used when the configured validity is absent or unparseable. */
|
|
24
|
+
const DEFAULT_PURGE_MS = 30 * 86400000;
|
|
25
|
+
/**
|
|
26
|
+
* Computes the instant an archived tenant stops being recoverable.
|
|
27
|
+
*
|
|
28
|
+
* Exported because the caller must compute it ONCE for a whole cascade and pass the
|
|
29
|
+
* same value to every collection: a per-collection deadline would let the TTL index
|
|
30
|
+
* expire half a tenant while the rest is still restorable, and a restore would then
|
|
31
|
+
* bring back an organization whose users have already been swept.
|
|
32
|
+
*
|
|
33
|
+
* @param purgeValidity the configured window (`APP.config.app.tenant.purgeValidity`,
|
|
34
|
+
* e.g. `'30d'`); anything unparseable falls back to 30 days
|
|
35
|
+
* @param from the instant the window starts (defaults to now)
|
|
36
|
+
*/
|
|
37
|
+
function purgeDeadline(purgeValidity, from = new Date()) {
|
|
38
|
+
return new Date(from.getTime() + ((0, functs_1.parseDurationMs)(purgeValidity) ?? DEFAULT_PURGE_MS));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Files documents the caller ALREADY holds, without touching any source collection.
|
|
42
|
+
*
|
|
43
|
+
* The primitive {@link archiveDocuments} is built on, and useful on its own whenever
|
|
44
|
+
* the removal is not ours to perform: the organization record itself is deleted by
|
|
45
|
+
* the framework's `remove` - which is also what publishes `organization.removed` and
|
|
46
|
+
* writes the outbox row - so it can only be archived from the document in hand,
|
|
47
|
+
* before or after that call, never by reading it back.
|
|
48
|
+
*
|
|
49
|
+
* @param archive the shared `tenantArchive` store
|
|
50
|
+
* @param sourceCollection where the documents came from, and where a restore puts them
|
|
51
|
+
* @param documents the documents themselves, archived verbatim
|
|
52
|
+
* @param tenantId the organization being deleted
|
|
53
|
+
* @param purgeAt the shared deadline for the whole cascade
|
|
54
|
+
* @returns how many entries were written
|
|
55
|
+
*/
|
|
56
|
+
async function archiveEntries(archive, sourceCollection, documents, tenantId, purgeAt) {
|
|
57
|
+
if (!documents.length)
|
|
58
|
+
return { moved: 0, sourceCollection };
|
|
59
|
+
const archivedAt = new Date();
|
|
60
|
+
const entries = documents.map((doc) => ({ tenantId, sourceCollection, doc, archivedAt, purgeAt }));
|
|
61
|
+
await archive.insertManyNative(entries, {});
|
|
62
|
+
return { moved: documents.length, sourceCollection };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Moves every document matching `filter` out of `source` and into the archive.
|
|
66
|
+
*
|
|
67
|
+
* The order is the whole point: the entries are written FIRST and the originals
|
|
68
|
+
* removed only once that succeeded. The reverse would turn a failed write into data
|
|
69
|
+
* loss, which is exactly what the archive exists to prevent - the same reasoning
|
|
70
|
+
* that puts the organization first in the auth-srv cascade (T29).
|
|
71
|
+
*
|
|
72
|
+
* ⚠️ Not a transaction. A crash between the two steps leaves the documents in BOTH
|
|
73
|
+
* places, which the restore tolerates (it puts back what is already there, by `_id`)
|
|
74
|
+
* whereas the opposite ordering would leave them in NEITHER.
|
|
75
|
+
*
|
|
76
|
+
* The document is archived VERBATIM, `_id` included: relations across collections are
|
|
77
|
+
* by `_id`, so regenerating one would restore a tenant whose relations no longer match.
|
|
78
|
+
*
|
|
79
|
+
* @param source the collection being emptied
|
|
80
|
+
* @param archive the shared `tenantArchive` store
|
|
81
|
+
* @param filter which documents belong to the tenant, as only the caller knows
|
|
82
|
+
* @param tenantId the organization being deleted
|
|
83
|
+
* @param purgeAt the shared deadline for the whole cascade - see {@link purgeDeadline}
|
|
84
|
+
* @returns how many documents were moved
|
|
85
|
+
*/
|
|
86
|
+
async function archiveDocuments(source, archive, filter, tenantId, purgeAt) {
|
|
87
|
+
const sourceCollection = source.mgModel.collection.collectionName;
|
|
88
|
+
const documents = await source.listNative(filter).toArray();
|
|
89
|
+
const outcome = await archiveEntries(archive, sourceCollection, documents, tenantId, purgeAt);
|
|
90
|
+
if (outcome.moved)
|
|
91
|
+
await source.removeNative(filter);
|
|
92
|
+
return outcome;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Puts a tenant's documents back into `source`, and drops their archive entries.
|
|
96
|
+
*
|
|
97
|
+
* Mirrors {@link archiveDocuments}, including the ordering rule: the documents are
|
|
98
|
+
* reinserted FIRST and the archive entries dropped only afterwards, so a failure
|
|
99
|
+
* leaves them recoverable rather than gone.
|
|
100
|
+
*
|
|
101
|
+
* ⚠️ Restores ONLY what this service owns: the caller passes its own model, and the
|
|
102
|
+
* source collection name it carries is what selects the entries. The shared archive
|
|
103
|
+
* is therefore safe to read from both services - each sees only its own rows, and
|
|
104
|
+
* the two sets are disjoint by construction.
|
|
105
|
+
*
|
|
106
|
+
* @param source the collection being refilled
|
|
107
|
+
* @param archive the shared `tenantArchive` store
|
|
108
|
+
* @param tenantId the organization being restored
|
|
109
|
+
* @returns how many documents were put back
|
|
110
|
+
*/
|
|
111
|
+
async function restoreDocuments(source, archive, tenantId) {
|
|
112
|
+
const sourceCollection = source.mgModel.collection.collectionName;
|
|
113
|
+
const entries = await archive.listNative({ tenantId, sourceCollection }).toArray();
|
|
114
|
+
if (!entries.length)
|
|
115
|
+
return { moved: 0, sourceCollection };
|
|
116
|
+
await source.insertManyNative(entries.map(entry => entry.doc), {});
|
|
117
|
+
await archive.removeNative({ tenantId, sourceCollection });
|
|
118
|
+
return { moved: entries.length, sourceCollection };
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=tenant-archive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-archive.js","sourceRoot":"","sources":["../../src/services/tenant-archive.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;+FAc+F;;AAwB/F,sCAGC;AAkBD,wCAiBC;AAwBD,4CAiBC;AAmBD,4CAgBC;AAxID,4CAAqD;AAOrD,mFAAmF;AACnF,MAAM,gBAAgB,GAAG,EAAE,GAAG,QAAU,CAAC;AAEzC;;;;;;;;;;;GAWG;AACH,SAAgB,aAAa,CAAC,aAA+B,EAAE,OAAa,IAAI,IAAI,EAAE;IAEpF,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,IAAA,wBAAe,EAAC,aAAa,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC;AACzF,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACI,KAAK,UAAU,cAAc,CAClC,OAAoC,EACpC,gBAAwB,EACxB,SAA2C,EAC3C,QAAwB,EACxB,OAAsB;IAGtB,IAAI,CAAC,SAAS,CAAC,MAAM;QACnB,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAExC,MAAM,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAM,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAqB,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IAEzH,MAAM,OAAO,CAAC,gBAAgB,CAAC,OAA+C,EAAE,EAAE,CAAC,CAAC;IAEpF,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACI,KAAK,UAAU,gBAAgB,CACpC,MAAyB,EACzB,OAA4B,EAC5B,MAAiC,EACjC,QAAgB,EAChB,OAAc;IAGd,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC;IAClE,MAAM,SAAS,GAAU,MAAM,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;IAEnE,MAAM,OAAO,GAAY,MAAM,cAAc,CAAC,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAEvG,IAAI,OAAO,CAAC,KAAK;QACf,MAAM,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IAEpC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACI,KAAK,UAAU,gBAAgB,CACpC,MAAyB,EACzB,OAA4B,EAC5B,QAAgB;IAGhB,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC;IAClE,MAAM,OAAO,GAAY,MAAM,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAC,CAAC,OAAO,EAAqB,CAAC;IAE/G,IAAI,CAAC,OAAO,CAAC,MAAM;QACjB,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAExC,MAAM,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,GAA8B,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9F,MAAM,OAAO,CAAC,YAAY,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAE3D,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC;AACrD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.constants.js","sourceRoot":"","sources":["../../src/utils/env.constants.ts"],"names":[],"mappings":";;;AAAA,mDAAyI;AAEzI;;;;;;GAMG;AAEH;;;;;;;GAOG;AACU,QAAA,YAAY,GAA2B;IAClD,QAAQ,
|
|
1
|
+
{"version":3,"file":"env.constants.js","sourceRoot":"","sources":["../../src/utils/env.constants.ts"],"names":[],"mappings":";;;AAAA,mDAAyI;AAEzI;;;;;;GAMG;AAEH;;;;;;;GAOG;AACU,QAAA,YAAY,GAA2B;IAClD,QAAQ,EAAe,OAAO;IAC9B,WAAW,EAAY,MAAM;IAC7B,OAAO,EAAgB,QAAQ;IAC/B,aAAa,EAAU,EAAE;IACzB,aAAa,EAAU,wCAAwB;IAC/C,YAAY,EAAW,uCAAuB,EAAI,gEAAgE;IAClH,iBAAiB,EAAM,yCAAyB,EAAE,gEAAgE;IAClH,gBAAgB,EAAO,wCAAwB,EAAG,KAAK;IACvD,UAAU,EAAa,WAAW;IAClC,UAAU,EAAa,IAAI,EAAuB,sDAAsD;IACxG,UAAU,EAAa,IAAI;IAC3B,SAAS,EAAc,EAAE;IACzB,aAAa,EAAU,EAAE;IACzB,aAAa,EAAU,EAAE;IACzB,QAAQ,EAAe,uBAAuB;IAC9C,mBAAmB,EAAI,GAAG;IAC1B,mBAAmB,EAAI,GAAG;IAC1B,mBAAmB,EAAI,IAAI;IAC3B,qBAAqB,EAAE,KAAK,EAAoB,sDAAsD;IACtG,OAAO,EAAgB,EAAE;IACzB,cAAc,EAAS,EAAE;IACzB,aAAa,EAAU,EAAE;IACzB,aAAa,EAAU,WAAW;IAClC,gBAAgB,EAAO,cAAc;IACrC,iBAAiB,EAAM,eAAe,EAAY,mCAAmC;IACrF,mBAAmB,EAAI,gBAAgB;IACvC,eAAe,EAAQ,MAAM;CAC9B,CAAC;AAEF,mFAAmF;AACtE,QAAA,WAAW,GAAG,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;AAE9D,uFAAuF;AAC1E,QAAA,QAAQ,GAAG,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC"}
|