@futdevpro/nts-dynamo 1.15.75 → 1.15.80
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/.dynamo/logs/cicd-pipeline/output.log +1952 -1883
- package/.dynamo/logs/cicd-pipeline/status.json +38 -38
- package/build/_collections/global-settings.const.d.ts.map +1 -1
- package/build/_collections/global-settings.const.js +11 -0
- package/build/_collections/global-settings.const.js.map +1 -1
- package/build/_models/interfaces/global-settings.interface.d.ts +18 -0
- package/build/_models/interfaces/global-settings.interface.d.ts.map +1 -1
- package/build/_services/base/data.service.d.ts +8 -0
- package/build/_services/base/data.service.d.ts.map +1 -1
- package/build/_services/base/data.service.js +17 -0
- package/build/_services/base/data.service.js.map +1 -1
- package/build/_services/core/collection-growth-monitor.service.d.ts +77 -0
- package/build/_services/core/collection-growth-monitor.service.d.ts.map +1 -0
- package/build/_services/core/collection-growth-monitor.service.js +147 -0
- package/build/_services/core/collection-growth-monitor.service.js.map +1 -0
- package/build/_services/core/global.service.d.ts +7 -0
- package/build/_services/core/global.service.d.ts.map +1 -1
- package/build/_services/core/global.service.js +10 -0
- package/build/_services/core/global.service.js.map +1 -1
- package/build/_services/server/app.server.d.ts +45 -0
- package/build/_services/server/app.server.d.ts.map +1 -1
- package/build/_services/server/app.server.js +97 -173
- package/build/_services/server/app.server.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/package.json +2 -2
- package/src/_collections/global-settings.const.ts +12 -0
- package/src/_models/interfaces/global-settings.interface.ts +19 -0
- package/src/_services/base/data.service.spec.ts +56 -1
- package/src/_services/base/data.service.ts +23 -2
- package/src/_services/core/collection-growth-monitor.service.spec.ts +67 -0
- package/src/_services/core/collection-growth-monitor.service.ts +211 -0
- package/src/_services/core/global.service.ts +14 -2
- package/src/_services/server/app.server-retention.spec.ts +106 -0
- package/src/_services/server/app.server.ts +137 -3
- package/src/index.ts +1 -0
|
@@ -20,8 +20,9 @@ import {
|
|
|
20
20
|
DyFM_Error,
|
|
21
21
|
DyFM_error_defaults,
|
|
22
22
|
DyFM_Error_Settings,
|
|
23
|
-
DyFM_ErrorLevel,
|
|
23
|
+
DyFM_ErrorLevel,
|
|
24
24
|
DyFM_Log,
|
|
25
|
+
DyFM_resolveRetentionTtlSeconds,
|
|
25
26
|
megabyte,
|
|
26
27
|
second
|
|
27
28
|
} from '@futdevpro/fsm-dynamo';
|
|
@@ -51,8 +52,10 @@ import {
|
|
|
51
52
|
import {
|
|
52
53
|
DyNTS_Cors_Settings
|
|
53
54
|
} from '../../_models/interfaces/cors-settings.interface';
|
|
55
|
+
import { DyNTS_DBService } from '../base/db.service';
|
|
54
56
|
import { DyNTS_SingletonService } from '../base/singleton.service';
|
|
55
57
|
import { DyNTS_GlobalService } from '../core/global.service';
|
|
58
|
+
import { DyNTS_CollectionGrowthMonitor } from '../core/collection-growth-monitor.service';
|
|
56
59
|
import { DyNTS_MemoryGuard } from '../core/memory-guard.service';
|
|
57
60
|
import { DyNTS_RoutingModule } from '../route/routing-module.service';
|
|
58
61
|
import { DyNTS_getStarRoute } from '../../_collections/star.controller';
|
|
@@ -230,8 +233,30 @@ import { DyNTS_getStarRoute } from '../../_collections/star.controller';
|
|
|
230
233
|
*
|
|
231
234
|
*
|
|
232
235
|
* ```
|
|
233
|
-
*
|
|
236
|
+
*
|
|
237
|
+
*/
|
|
238
|
+
|
|
239
|
+
/** FR-258 / SR-2 — one entry from `collection.indexes()` (only the fields the TTL-installer reads). */
|
|
240
|
+
export interface DyNTS_TtlIndexInfo {
|
|
241
|
+
name?: string;
|
|
242
|
+
key?: Record<string, number>;
|
|
243
|
+
expireAfterSeconds?: number;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* FR-258 / SR-2 — the minimal native-driver collection surface the TTL-installer needs. Declared
|
|
248
|
+
* structurally (not importing mongodb types) so it stays dependency-light and is trivially mockable
|
|
249
|
+
* in unit tests.
|
|
234
250
|
*/
|
|
251
|
+
export interface DyNTS_TtlIndexCollection {
|
|
252
|
+
indexes?: () => Promise<DyNTS_TtlIndexInfo[]>;
|
|
253
|
+
createIndex: (keys: Record<string, number>, options: { expireAfterSeconds: number }) => Promise<unknown>;
|
|
254
|
+
dropIndex?: (name: string) => Promise<unknown>;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** FR-258 / SR-2 — outcome of {@link DyNTS_App.ensureRetentionTtlIndex}. */
|
|
258
|
+
export type DyNTS_TtlIndexAction = 'created' | 'updated' | 'noop';
|
|
259
|
+
|
|
235
260
|
export abstract class DyNTS_App extends DyNTS_SingletonService {
|
|
236
261
|
|
|
237
262
|
protected systemControls: DyNTS_AppSystemControls = new DyNTS_AppSystemControls();
|
|
@@ -555,6 +580,17 @@ export abstract class DyNTS_App extends DyNTS_SingletonService {
|
|
|
555
580
|
DyFM_Log.warn('[DyNTS_MemoryGuard] auto-install skipped (non-fatal):', memoryGuardError);
|
|
556
581
|
}
|
|
557
582
|
|
|
583
|
+
// FR-258 / SR-4 — proactive collection-growth monitor (companion to the MemoryGuard). Warns
|
|
584
|
+
// into the Errors-sink when a collection grows unbounded, BEFORE it can be loaded into heap and
|
|
585
|
+
// OOM. Default-on, observation-only, never throws.
|
|
586
|
+
try {
|
|
587
|
+
if (DyNTS_global_settings.collectionGrowthMonitor?.enabled) {
|
|
588
|
+
DyNTS_CollectionGrowthMonitor.getInstance().install();
|
|
589
|
+
}
|
|
590
|
+
} catch (cgmError: unknown) {
|
|
591
|
+
DyFM_Log.warn('[DyNTS_CollectionGrowthMonitor] auto-install skipped (non-fatal):', cgmError);
|
|
592
|
+
}
|
|
593
|
+
|
|
558
594
|
if (!extended) {
|
|
559
595
|
await this.ready();
|
|
560
596
|
|
|
@@ -916,6 +952,11 @@ export abstract class DyNTS_App extends DyNTS_SingletonService {
|
|
|
916
952
|
DyFM_Log.success(`\nConnected to MongoDB (${this._params.dbUri})\n`);
|
|
917
953
|
|
|
918
954
|
resolve();
|
|
955
|
+
|
|
956
|
+
// FR-258 / SR-2 — install declared-retention TTL indexes. Fire-and-forget +
|
|
957
|
+
// fully non-fatal: an index build on a large collection must NOT block startup,
|
|
958
|
+
// and any failure here is logged but never crashes the app.
|
|
959
|
+
void this.installRetentionTtlIndexes();
|
|
919
960
|
})
|
|
920
961
|
.on('error', (error): void => {
|
|
921
962
|
if (!this.systemControls.mongoose.started) {
|
|
@@ -1013,7 +1054,100 @@ export abstract class DyNTS_App extends DyNTS_SingletonService {
|
|
|
1013
1054
|
}
|
|
1014
1055
|
|
|
1015
1056
|
/**
|
|
1016
|
-
*
|
|
1057
|
+
* FR-258 / SR-2 — install MongoDB TTL indexes for every data-model that declared `retention`.
|
|
1058
|
+
*
|
|
1059
|
+
* Runs ONCE right after the DB connection opens. For each registered DB service (including the
|
|
1060
|
+
* auto-created `_archived` siblings, which inherit the parent model's `retention`), it ensures a
|
|
1061
|
+
* TTL index on the `__created` Date field with the resolved `expireAfterSeconds` — so MongoDB
|
|
1062
|
+
* NATIVELY auto-deletes documents older than the configured age. This is the fleet-wide defense
|
|
1063
|
+
* against the unbounded-collection-growth → in-memory-load → GC-thrash/OOM class.
|
|
1064
|
+
*
|
|
1065
|
+
* Guarantees:
|
|
1066
|
+
* - **Non-fatal** — any error is logged but NEVER blocks startup (called fire-and-forget).
|
|
1067
|
+
* - **Idempotent** — an existing index with the same TTL is a no-op; a CHANGED retention (different
|
|
1068
|
+
* `expireAfterSeconds`, or an existing non-TTL `__created` index) is reconciled by drop+recreate
|
|
1069
|
+
* (the field index is restored immediately, so query coverage is preserved).
|
|
1070
|
+
*/
|
|
1071
|
+
private async installRetentionTtlIndexes(): Promise<void> {
|
|
1072
|
+
try {
|
|
1073
|
+
const services: DyNTS_DBService<any>[] = DyNTS_GlobalService.getAllDBServices();
|
|
1074
|
+
let ensured: number = 0;
|
|
1075
|
+
|
|
1076
|
+
for (const service of services) {
|
|
1077
|
+
const ttlSeconds: number | undefined = DyFM_resolveRetentionTtlSeconds(service?.dataParams?.retention);
|
|
1078
|
+
if (!ttlSeconds) { continue; }
|
|
1079
|
+
|
|
1080
|
+
const dataName: string = service.dataParams.dataName;
|
|
1081
|
+
// The mongoose Model exposes the native driver collection (.indexes / .createIndex / .dropIndex).
|
|
1082
|
+
const collection: DyNTS_TtlIndexCollection | undefined =
|
|
1083
|
+
(service?.dataModel as unknown as { collection?: DyNTS_TtlIndexCollection })?.collection;
|
|
1084
|
+
if (!collection || typeof collection.createIndex !== 'function') { continue; }
|
|
1085
|
+
|
|
1086
|
+
try {
|
|
1087
|
+
const action: DyNTS_TtlIndexAction = await DyNTS_App.ensureRetentionTtlIndex(collection, ttlSeconds);
|
|
1088
|
+
if (action === 'created' || action === 'updated') {
|
|
1089
|
+
ensured++;
|
|
1090
|
+
if (this.logSetup) {
|
|
1091
|
+
DyFM_Log.success(
|
|
1092
|
+
`[FR-258 retention] TTL index ${action} on "${dataName}" — ` +
|
|
1093
|
+
`${Math.round(ttlSeconds / 86400)}d (${ttlSeconds}s)`,
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
} catch (idxErr: unknown) {
|
|
1098
|
+
DyFM_Log.warn(
|
|
1099
|
+
`[FR-258 retention] TTL index on "${dataName}" failed (non-fatal): ` +
|
|
1100
|
+
`${idxErr instanceof Error ? idxErr.message : String(idxErr)}`,
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
if (ensured > 0) {
|
|
1106
|
+
DyFM_Log.success(`[FR-258 retention] ${ensured} TTL index(es) ensured (auto-retention active).`);
|
|
1107
|
+
}
|
|
1108
|
+
} catch (err: unknown) {
|
|
1109
|
+
DyFM_Log.warn(
|
|
1110
|
+
`[FR-258 retention] TTL-installer failed (non-fatal): ` +
|
|
1111
|
+
`${err instanceof Error ? err.message : String(err)}`,
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* FR-258 / SR-2 — ensure a single `{ __created: 1 }` TTL index with the given `expireAfterSeconds`.
|
|
1118
|
+
* Extracted as a pure-ish static so it is unit-testable with a mock collection. Idempotent:
|
|
1119
|
+
* - no existing `__created` index → create it → `'created'`
|
|
1120
|
+
* - existing with the SAME TTL → no-op → `'noop'`
|
|
1121
|
+
* - existing with a DIFFERENT TTL / non-TTL → drop + recreate → `'updated'`
|
|
1122
|
+
* Drop+recreate preserves query coverage (the `{__created:1}` index is restored immediately).
|
|
1123
|
+
*/
|
|
1124
|
+
static async ensureRetentionTtlIndex(
|
|
1125
|
+
collection: DyNTS_TtlIndexCollection,
|
|
1126
|
+
ttlSeconds: number,
|
|
1127
|
+
): Promise<DyNTS_TtlIndexAction> {
|
|
1128
|
+
const existingList: DyNTS_TtlIndexInfo[] = typeof collection.indexes === 'function'
|
|
1129
|
+
? await collection.indexes().catch((): DyNTS_TtlIndexInfo[] => [])
|
|
1130
|
+
: [];
|
|
1131
|
+
const existing: DyNTS_TtlIndexInfo | undefined =
|
|
1132
|
+
existingList.find((ix): boolean => !!ix?.key && ix.key.__created === 1);
|
|
1133
|
+
|
|
1134
|
+
if (!existing) {
|
|
1135
|
+
await collection.createIndex({ __created: 1 }, { expireAfterSeconds: ttlSeconds });
|
|
1136
|
+
return 'created';
|
|
1137
|
+
}
|
|
1138
|
+
if (existing.expireAfterSeconds === ttlSeconds) {
|
|
1139
|
+
return 'noop';
|
|
1140
|
+
}
|
|
1141
|
+
// Retention changed, or an existing non-TTL `__created` index → reconcile by drop+recreate.
|
|
1142
|
+
if (existing.name && typeof collection.dropIndex === 'function') {
|
|
1143
|
+
await collection.dropIndex(existing.name).catch((): void => undefined);
|
|
1144
|
+
}
|
|
1145
|
+
await collection.createIndex({ __created: 1 }, { expireAfterSeconds: ttlSeconds });
|
|
1146
|
+
return 'updated';
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
*
|
|
1017
1151
|
*/
|
|
1018
1152
|
private async initExpresses(): Promise<void> {
|
|
1019
1153
|
if (this.fnLogs && this.deepLog) DyFM_Log.log('\nfn:. initExpresses');
|
package/src/index.ts
CHANGED
|
@@ -71,6 +71,7 @@ export * from './_services/core/api.service';
|
|
|
71
71
|
export * from './_services/core/auth.service';
|
|
72
72
|
|
|
73
73
|
export * from './_services/core/email.service';
|
|
74
|
+
export * from './_services/core/collection-growth-monitor.service';
|
|
74
75
|
export * from './_services/core/global.service';
|
|
75
76
|
export * from './_services/core/memory-guard.service';
|
|
76
77
|
|