@futdevpro/nts-dynamo 1.15.118 → 1.15.120
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 +1845 -1703
- package/.dynamo/logs/cicd-pipeline/status.json +42 -36
- package/build/_models/control-models/endpoint-params.control-model.d.ts +5 -0
- package/build/_models/control-models/endpoint-params.control-model.d.ts.map +1 -1
- package/build/_models/control-models/endpoint-params.control-model.js +41 -11
- package/build/_models/control-models/endpoint-params.control-model.js.map +1 -1
- package/build/_models/data-models/schema-migration.data-model.d.ts +29 -0
- package/build/_models/data-models/schema-migration.data-model.d.ts.map +1 -0
- package/build/_models/data-models/schema-migration.data-model.js +47 -0
- package/build/_models/data-models/schema-migration.data-model.js.map +1 -0
- package/build/_models/interfaces/migration-entry.interface.d.ts +20 -0
- package/build/_models/interfaces/migration-entry.interface.d.ts.map +1 -0
- package/build/_models/interfaces/migration-entry.interface.js +3 -0
- package/build/_models/interfaces/migration-entry.interface.js.map +1 -0
- package/build/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.d.ts +6 -1
- package/build/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.d.ts.map +1 -1
- package/build/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.js +40 -10
- package/build/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.js.map +1 -1
- package/build/_modules/local-vector-search/_services/lvs-vector-persist.data-service.d.ts +9 -0
- package/build/_modules/local-vector-search/_services/lvs-vector-persist.data-service.d.ts.map +1 -1
- package/build/_modules/local-vector-search/_services/lvs-vector-persist.data-service.js +12 -0
- package/build/_modules/local-vector-search/_services/lvs-vector-persist.data-service.js.map +1 -1
- package/build/_modules/local-vector-search/_services/lvs-vector-pool.control-service.d.ts +12 -2
- package/build/_modules/local-vector-search/_services/lvs-vector-pool.control-service.d.ts.map +1 -1
- package/build/_modules/local-vector-search/_services/lvs-vector-pool.control-service.js +54 -7
- package/build/_modules/local-vector-search/_services/lvs-vector-pool.control-service.js.map +1 -1
- package/build/_modules/server/server-status/server-status.control-service.d.ts +6 -0
- package/build/_modules/server/server-status/server-status.control-service.d.ts.map +1 -1
- package/build/_modules/server/server-status/server-status.control-service.js +10 -0
- package/build/_modules/server/server-status/server-status.control-service.js.map +1 -1
- package/build/_modules/server/server-status/server-status.controller.d.ts.map +1 -1
- package/build/_modules/server/server-status/server-status.controller.js +26 -0
- package/build/_modules/server/server-status/server-status.controller.js.map +1 -1
- package/build/_modules/socket/_models/socket-server-service-params.control-model.d.ts +11 -1
- package/build/_modules/socket/_models/socket-server-service-params.control-model.d.ts.map +1 -1
- package/build/_modules/socket/_models/socket-server-service-params.control-model.js +12 -1
- package/build/_modules/socket/_models/socket-server-service-params.control-model.js.map +1 -1
- package/build/_modules/socket/_services/socket-server.service.d.ts +2 -0
- package/build/_modules/socket/_services/socket-server.service.d.ts.map +1 -1
- package/build/_modules/socket/_services/socket-server.service.js +2 -0
- package/build/_modules/socket/_services/socket-server.service.js.map +1 -1
- package/build/_modules/socket/app-extended.server.d.ts.map +1 -1
- package/build/_modules/socket/app-extended.server.js +13 -8
- package/build/_modules/socket/app-extended.server.js.map +1 -1
- package/build/_services/base/db.service.d.ts +14 -0
- package/build/_services/base/db.service.d.ts.map +1 -1
- package/build/_services/base/db.service.js +27 -0
- package/build/_services/base/db.service.js.map +1 -1
- package/build/_services/core/api.service.d.ts.map +1 -1
- package/build/_services/core/api.service.js +29 -9
- package/build/_services/core/api.service.js.map +1 -1
- package/build/_services/core/db-stats.service.d.ts +34 -0
- package/build/_services/core/db-stats.service.d.ts.map +1 -0
- package/build/_services/core/db-stats.service.js +65 -0
- package/build/_services/core/db-stats.service.js.map +1 -0
- package/build/_services/core/diagnostics.service.d.ts +43 -0
- package/build/_services/core/diagnostics.service.d.ts.map +1 -0
- package/build/_services/core/diagnostics.service.js +122 -0
- package/build/_services/core/diagnostics.service.js.map +1 -0
- package/build/_services/core/migration-runner.service.d.ts +40 -0
- package/build/_services/core/migration-runner.service.d.ts.map +1 -0
- package/build/_services/core/migration-runner.service.js +75 -0
- package/build/_services/core/migration-runner.service.js.map +1 -0
- package/build/index.d.ts +5 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +6 -0
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/_models/control-models/endpoint-params.control-model.ts +44 -16
- package/src/_models/data-models/schema-migration.data-model.ts +50 -0
- package/src/_models/interfaces/migration-entry.interface.ts +19 -0
- package/src/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.spec.ts +16 -0
- package/src/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.ts +50 -14
- package/src/_modules/local-vector-search/_services/lvs-vector-persist.data-service.ts +22 -0
- package/src/_modules/local-vector-search/_services/lvs-vector-pool.control-service.spec.ts +5 -3
- package/src/_modules/local-vector-search/_services/lvs-vector-pool.control-service.ts +62 -11
- package/src/_modules/server/server-status/server-status.control-service.ts +12 -0
- package/src/_modules/server/server-status/server-status.controller.ts +28 -0
- package/src/_modules/socket/_models/socket-server-service-params.control-model.spec.ts +23 -0
- package/src/_modules/socket/_models/socket-server-service-params.control-model.ts +12 -1
- package/src/_modules/socket/_services/socket-server.service.ts +2 -0
- package/src/_modules/socket/app-extended.server.ts +15 -9
- package/src/_services/base/db.service.ts +39 -1
- package/src/_services/core/api.service.spec.ts +47 -0
- package/src/_services/core/api.service.ts +32 -12
- package/src/_services/core/db-stats.service.ts +104 -0
- package/src/_services/core/diagnostics.service.spec.ts +44 -0
- package/src/_services/core/diagnostics.service.ts +158 -0
- package/src/_services/core/migration-runner.service.spec.ts +91 -0
- package/src/_services/core/migration-runner.service.ts +106 -0
- package/src/index.ts +7 -0
|
@@ -261,6 +261,18 @@ export class DyNTS_Endpoint_Params<
|
|
|
261
261
|
return;
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
// BFR-OVERSEER-010: a logResponseContent IMPLEMENTÁLVA (a korábbi "not implemented yet"
|
|
265
|
+
// warn-stub minden requestre spam-elt). A res.send-et wrap-eljük a küldött body
|
|
266
|
+
// elkapásához; a task-ok után csonkolt preview-t logolunk (log-flood ellen).
|
|
267
|
+
let capturedResponseBody: unknown;
|
|
268
|
+
if (this.logRequest && this.logResponseContent) {
|
|
269
|
+
const originalSend: Response['send'] = res.send.bind(res);
|
|
270
|
+
res.send = (body?: unknown): Response => {
|
|
271
|
+
capturedResponseBody = body;
|
|
272
|
+
return originalSend(body);
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
264
276
|
await DyFM_Array.asyncForEach(this.tasks,
|
|
265
277
|
async (
|
|
266
278
|
task: (req: Request, res: Response, issuer?: string) => Promise<void>
|
|
@@ -268,13 +280,11 @@ export class DyNTS_Endpoint_Params<
|
|
|
268
280
|
await task(req, res, issuer);
|
|
269
281
|
}
|
|
270
282
|
);
|
|
271
|
-
|
|
283
|
+
|
|
272
284
|
if (this.logRequest) {
|
|
285
|
+
DyFM_Log.success(` <<<===== '${this.name}' result sent.`);
|
|
273
286
|
if (this.logResponseContent) {
|
|
274
|
-
DyFM_Log.
|
|
275
|
-
DyFM_Log.warn('sorry, the logResponseContent is not implemented yet.');
|
|
276
|
-
} else {
|
|
277
|
-
DyFM_Log.success(` <<<===== '${this.name}' result sent.`);
|
|
287
|
+
DyFM_Log.info(` response: ${DyNTS_Endpoint_Params.previewBody(capturedResponseBody)}`);
|
|
278
288
|
}
|
|
279
289
|
}
|
|
280
290
|
} catch (error) {
|
|
@@ -283,6 +293,28 @@ export class DyNTS_Endpoint_Params<
|
|
|
283
293
|
};
|
|
284
294
|
}
|
|
285
295
|
|
|
296
|
+
/**
|
|
297
|
+
* BFR-OVERSEER-010: csonkolt, safe response-preview a logResponseContent-hez —
|
|
298
|
+
* nem-parseolható/circular body-ra nem dob, hosszú body-t levág (log-flood ellen).
|
|
299
|
+
*/
|
|
300
|
+
private static previewBody(body: unknown): string {
|
|
301
|
+
const maxLength: number = 2000;
|
|
302
|
+
let text: string;
|
|
303
|
+
if (body === undefined) {
|
|
304
|
+
return '-no body captured-';
|
|
305
|
+
}
|
|
306
|
+
if (typeof body === 'string') {
|
|
307
|
+
text = body;
|
|
308
|
+
} else {
|
|
309
|
+
try {
|
|
310
|
+
text = JSON.stringify(body);
|
|
311
|
+
} catch {
|
|
312
|
+
text = String(body);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return text.length > maxLength ? `${text.slice(0, maxLength)}…(truncated ${text.length} chars)` : text;
|
|
316
|
+
}
|
|
317
|
+
|
|
286
318
|
/**
|
|
287
319
|
*
|
|
288
320
|
* @param res
|
|
@@ -492,20 +524,16 @@ export class DyNTS_Endpoint_Params<
|
|
|
492
524
|
// test/dev környezetben TELJES DyFM-leaket hagyott (__localStack + ___systemVersion + issuer).
|
|
493
525
|
// A szabályok (mindig-strip / prod-only additionalContent / kliens-mezők megtartása) SSOT-ja
|
|
494
526
|
// a DyNTS_ClientSafeError_Util.
|
|
495
|
-
|
|
527
|
+
// BFR-OVERSEER-010: a kiküldött client-safe error megőrzése a logResponseContent-hez.
|
|
528
|
+
const clientSafeError: unknown = DyNTS_ClientSafeError_Util.toClientSafe(error);
|
|
529
|
+
res.send(clientSafeError);
|
|
496
530
|
|
|
497
531
|
if (this.logRequest) {
|
|
532
|
+
DyFM_Log.error(
|
|
533
|
+
` <<<===== '${this.name}' error sent: ${(error as DyFM_Error)?._message ?? ''}`
|
|
534
|
+
);
|
|
498
535
|
if (this.logResponseContent) {
|
|
499
|
-
DyFM_Log.error(
|
|
500
|
-
` <<<===== '${this.name}' error sent: ${(error as DyFM_Error)?._message ?? ''}`
|
|
501
|
-
);
|
|
502
|
-
DyFM_Log.error(
|
|
503
|
-
'sorry, the logResponseContent is not implemented yet.'
|
|
504
|
-
);
|
|
505
|
-
} else {
|
|
506
|
-
DyFM_Log.error(
|
|
507
|
-
` <<<===== '${this.name}' error sent: ${(error as DyFM_Error)?._message ?? ''}`
|
|
508
|
-
);
|
|
536
|
+
DyFM_Log.error(` error response: ${DyNTS_Endpoint_Params.previewBody(clientSafeError)}`);
|
|
509
537
|
}
|
|
510
538
|
}
|
|
511
539
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { DyFM_DataModel_Params, DyFM_Metadata } from '@futdevpro/fsm-dynamo';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* **Migráció-nyilvántartó ("server info") LEDGER** — bedrock, újrahasználható az egész flottának.
|
|
5
|
+
* (A token-service app-lokál `SchemaMigration` mintájából emelve bedrock nts-be.)
|
|
6
|
+
*
|
|
7
|
+
* Egy-egy `migrationId` **PONTOSAN EGYSZER** fut le rendszerenként: a `DyNTS_Migration_Runner.runPending()`
|
|
8
|
+
* boot-on (az app `app.server.postProcess()`-éből) lekéri a már-alkalmazott ID-kat, a pending
|
|
9
|
+
* migrációkat lefuttatja, majd egy sort rögzít ide. A `migrationId` **unique index** → race-safe
|
|
10
|
+
* (két párhuzamos instance esetén az egyik `save`-je duplicate-key-re bukik → skip).
|
|
11
|
+
*
|
|
12
|
+
* A ledger a KÖTELEZŐ kanon-adatokat tartja: **mely migrációk futottak** (id + idő) + **mely app-verzió**
|
|
13
|
+
* alkalmazta (a `appliedVersion`-ökből a "mely verziók futottak" levezethető).
|
|
14
|
+
*
|
|
15
|
+
* Az app a saját DB-jében regisztrálja a `dyNTS_schemaMigration_dataParams`-t (`getGlobalServiceCollection
|
|
16
|
+
* ().dbModels`), így per-app saját `dynts_schema_migrations` collection-t kap.
|
|
17
|
+
*/
|
|
18
|
+
export class DyNTS_SchemaMigration extends DyFM_Metadata {
|
|
19
|
+
|
|
20
|
+
/** A migráció egyedi azonosítója (konvenció: `YYYY-MM-DD-NNN-descriptor`). */
|
|
21
|
+
migrationId?: string;
|
|
22
|
+
|
|
23
|
+
/** Alkalmazás időbélyege. */
|
|
24
|
+
appliedAt?: Date;
|
|
25
|
+
|
|
26
|
+
/** Az alkalmazó app-verzió (a `package.json` version-je — audit-trail + "mely verziók futottak"). */
|
|
27
|
+
appliedVersion?: string;
|
|
28
|
+
|
|
29
|
+
/** Diag — pl. `touched=<n>` vagy hiba-részletek. */
|
|
30
|
+
notes?: string;
|
|
31
|
+
|
|
32
|
+
constructor(set?: DyNTS_SchemaMigration) {
|
|
33
|
+
super(set);
|
|
34
|
+
if (set) {
|
|
35
|
+
Object.assign(this, set);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const dyNTS_schemaMigration_dataParams: DyFM_DataModel_Params<DyNTS_SchemaMigration> =
|
|
41
|
+
new DyFM_DataModel_Params<DyNTS_SchemaMigration>({
|
|
42
|
+
dataName: 'dynts_schema_migrations',
|
|
43
|
+
addArchive: false,
|
|
44
|
+
properties: {
|
|
45
|
+
migrationId: { type: 'string', required: true, unique: true, index: true },
|
|
46
|
+
appliedAt: { type: 'date', required: true },
|
|
47
|
+
appliedVersion: { type: 'string' },
|
|
48
|
+
notes: { type: 'string' },
|
|
49
|
+
},
|
|
50
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Egy regisztrált deploy-integrált migráció leírója. Az app egy `DyNTS_MigrationEntry[]` registry-t
|
|
3
|
+
* ad át a `DyNTS_Migration_Runner.runPending(...)`-nek; a runner a pending (még nem alkalmazott)
|
|
4
|
+
* entry-ket **egyszer** futtatja le, sorrendben, majd a `DyNTS_SchemaMigration` ledgerbe rögzíti.
|
|
5
|
+
*/
|
|
6
|
+
export interface DyNTS_MigrationEntry {
|
|
7
|
+
/** Egyedi, monoton bővíthető ID (konvenció: `YYYY-MM-DD-NNN-descriptor`). A ledger ez alapján run-once-ol. */
|
|
8
|
+
id: string;
|
|
9
|
+
|
|
10
|
+
/** Rövid leíró (admin/debug/log). */
|
|
11
|
+
description: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A tényleges migráció-logika. Az app már-csatlakozott Mongo-kapcsolatát használja (a runner a
|
|
15
|
+
* `postProcess`-ből fut, ahol a DB már él). Hibára DOBHAT — a runner catch-eli, a `notes`-ba menti,
|
|
16
|
+
* és NEM állítja meg a boot-ot. Visszaad: `{ touched }` (a módosított dokumentumok száma — count/riport).
|
|
17
|
+
*/
|
|
18
|
+
run: (issuer: string) => Promise<{ touched: number }>;
|
|
19
|
+
}
|
|
@@ -75,6 +75,22 @@ describe('| DyNTS_LVS_PersistentVectorPool_ControlService', () => {
|
|
|
75
75
|
(collectionKey: string) =>
|
|
76
76
|
Promise.resolve(Array.from(store.get(collectionKey)?.values() ?? [])),
|
|
77
77
|
);
|
|
78
|
+
|
|
79
|
+
// BFR-AM-013 (hydration-paging): a batch-elt lean-scan fake-je — a fake-store tartalmát
|
|
80
|
+
// batch-enként adagolja a handler-nek (a valós cursor-viselkedés memóriában modellezve).
|
|
81
|
+
spyOn(DyNTS_LVS_VectorPersist_DataService.prototype, 'findLeanBatchedByCollectionKey').and.callFake(
|
|
82
|
+
async (
|
|
83
|
+
collectionKey: string,
|
|
84
|
+
set: { batchSize?: number; handler: (batch: Partial<DyNTS_LVS_VectorPersist>[]) => Promise<void> | void },
|
|
85
|
+
): Promise<number> => {
|
|
86
|
+
const all: DyNTS_LVS_VectorPersist[] = Array.from(store.get(collectionKey)?.values() ?? []);
|
|
87
|
+
const batchSize: number = set.batchSize && set.batchSize > 0 ? set.batchSize : 500;
|
|
88
|
+
for (let i: number = 0; i < all.length; i += batchSize) {
|
|
89
|
+
await set.handler(all.slice(i, i + batchSize));
|
|
90
|
+
}
|
|
91
|
+
return all.length;
|
|
92
|
+
},
|
|
93
|
+
);
|
|
78
94
|
});
|
|
79
95
|
|
|
80
96
|
/** Egy egységvektor a tengely-irányba (cosine-self = 1) — determinisztikus keresés-ellenőrzéshez. */
|
package/src/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { DyFM_Log } from '@futdevpro/fsm-dynamo';
|
|
2
|
+
import { DyFM_Error, DyFM_Log } from '@futdevpro/fsm-dynamo';
|
|
3
3
|
|
|
4
4
|
import { LVS_Search_Mode } from '../_enums/lvs-search-mode.enum';
|
|
5
5
|
import { LVS_SearchResult } from '../_models/lvs-search-result.interface';
|
|
@@ -97,24 +97,60 @@ export class DyNTS_LVS_PersistentVectorPool_ControlService {
|
|
|
97
97
|
* előbb üríti (idempotens hidratálás). A hiányos (üres `vectorId`/`embedding`) rekordokat átugorja.
|
|
98
98
|
* Visszaadja a betöltött vektorok számát. **NEM** Atlas — saját Mongo + in-memory pool.
|
|
99
99
|
*/
|
|
100
|
-
async hydrateFromMongo(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
async hydrateFromMongo(set?: {
|
|
101
|
+
/** Cursor-batch méret (default 500) — a heap-tranziens felső korlátja egy batch. */
|
|
102
|
+
batchSize?: number,
|
|
103
|
+
/** Opcionális felső korlát a pool-méretre; elérésekor a hidratálás áll + WARN (no-silent-cap). */
|
|
104
|
+
maxVectors?: number,
|
|
105
|
+
}): Promise<number> {
|
|
106
|
+
// BFR-AM-013 (hydration-paging): a korábbi impl a TELJES collection-t egyetlen
|
|
107
|
+
// `findByCollectionKey`-jel (Mongoose-doc-tömb) töltötte memóriába → 32k×2560-dim vektornál
|
|
108
|
+
// RUNTIME-IGAZOLT 93% heap-spike (OOM-közeli). Mostantól LEAN + projektált (vectorId+embedding,
|
|
109
|
+
// a content-szöveg NÉLKÜL) CURSOR-batch — a tranziens felső korlátja EGY batch.
|
|
104
110
|
this.pool.clearPool();
|
|
105
111
|
|
|
106
112
|
let loaded: number = 0;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
let skipped: number = 0;
|
|
114
|
+
let truncated: boolean = false;
|
|
115
|
+
|
|
116
|
+
await this.getPersistService().findLeanBatchedByCollectionKey(this.collectionKey, {
|
|
117
|
+
batchSize: set?.batchSize,
|
|
118
|
+
handler: (batch: Partial<DyNTS_LVS_VectorPersist>[]): void => {
|
|
119
|
+
for (const record of batch) {
|
|
120
|
+
if (truncated) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (set?.maxVectors !== undefined && loaded >= set.maxVectors) {
|
|
124
|
+
truncated = true;
|
|
125
|
+
DyFM_Log.warn(
|
|
126
|
+
`[DyNTS LVS hydrate] '${this.collectionKey}': maxVectors (${set.maxVectors}) elérve — ` +
|
|
127
|
+
'a hidratálás CSONKOLVA (no-silent-cap; a maradék vektor NEM került a poolba).',
|
|
128
|
+
);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (!record.vectorId || !record.embedding?.length) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
this.pool.addVector(record.vectorId, record.embedding);
|
|
136
|
+
loaded++;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
// BFR-AM-013 (NaN/±Inf-guard a hidratálásban): EGY hibás (nem-véges elemű) perzistált
|
|
139
|
+
// vektor NEM boríthatja a teljes boot-hidratálást — skip + warn (nem néma).
|
|
140
|
+
skipped++;
|
|
141
|
+
DyFM_Log.warn(
|
|
142
|
+
`[DyNTS LVS hydrate] '${this.collectionKey}': vektor kihagyva ("${record.vectorId}") — ` +
|
|
143
|
+
`${DyFM_Error.getErrorMessage(error)}`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
});
|
|
115
149
|
|
|
116
150
|
DyFM_Log.log(
|
|
117
|
-
`[DyNTS LVS hydrate] '${this.collectionKey}' pool: ${loaded} vektor betöltve az in-memory pool-ba
|
|
151
|
+
`[DyNTS LVS hydrate] '${this.collectionKey}' pool: ${loaded} vektor betöltve az in-memory pool-ba` +
|
|
152
|
+
(skipped ? ` (${skipped} hibás vektor kihagyva)` : '') +
|
|
153
|
+
(truncated ? ' (CSONKOLT — maxVectors)' : '') + '.',
|
|
118
154
|
);
|
|
119
155
|
|
|
120
156
|
return loaded;
|
|
@@ -48,6 +48,28 @@ export class DyNTS_LVS_VectorPersist_DataService extends DyNTS_DataService<DyNTS
|
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* BFR-AM-013 (hydration-paging): LEAN + projektált (CSAK `vectorId` + `embedding` — a content-szöveg
|
|
53
|
+
* és metadata NÉLKÜL) cursor-batch scan a boot-hidratáláshoz. A heap-tranziens felső korlátja EGY batch
|
|
54
|
+
* (a teljes result-set + Mongoose-doc-boxing kimarad). Soft-delete-elt rekordok kihagyva.
|
|
55
|
+
*/
|
|
56
|
+
async findLeanBatchedByCollectionKey(
|
|
57
|
+
collectionKey: string,
|
|
58
|
+
set: {
|
|
59
|
+
batchSize?: number,
|
|
60
|
+
handler: (batch: Partial<DyNTS_LVS_VectorPersist>[]) => Promise<void> | void,
|
|
61
|
+
}
|
|
62
|
+
): Promise<number> {
|
|
63
|
+
return this.dataDBService.findLeanBatched(
|
|
64
|
+
{ collectionKey: collectionKey, _deleted: undefined },
|
|
65
|
+
{
|
|
66
|
+
projection: { vectorId: 1, embedding: 1 },
|
|
67
|
+
batchSize: set.batchSize,
|
|
68
|
+
handler: set.handler,
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
51
73
|
/**
|
|
52
74
|
* Egy vektor ATOMIKUS upsert-je a `(collectionKey, vectorId)` logikai-kulcson. MEGLÉVŐ rekordnál
|
|
53
75
|
* `$set` (a Mixed `metadata` silent-drop ellen — memory: mongoose_mixed_atomic_write); ÚJ rekordnál
|
|
@@ -374,9 +374,11 @@ describe('| LVS_VectorPool_ControlService', () => {
|
|
|
374
374
|
expect(vec1Result!.score).toBeCloseTo(1, 10);
|
|
375
375
|
});
|
|
376
376
|
|
|
377
|
-
it('| should produce cosine scores
|
|
377
|
+
it('| should produce cosine scores EQUIVALENT to static cosineSimilarity (precomputed-normalized + Float32 storage, BFR-AM-013)', () => {
|
|
378
378
|
// Regression-guard: a search optimalizalt (precomputed-normalized + alloc-mentes) cosine-ja
|
|
379
|
-
// viselkedes-
|
|
379
|
+
// viselkedes-EKVIVALENS a regi per-osszehasonlitas `cosineSimilarity(query, vector)`-ral.
|
|
380
|
+
// Tolerancia 6 tizedes (nem 10): a pool BELSŐ tárolása Float32Array (BFR-AM-013 Tier-1,
|
|
381
|
+
// ~2-4× memória) → ~1e-7 kerekítési eltérés — a similarity-RANGSORT nem érinti.
|
|
380
382
|
const query: number[] = [0.3, 0.7, 0.2];
|
|
381
383
|
const results: LVS_SearchResult[] = vectorPool.search(query, 4, LVS_Search_Mode.cosineSimilarity);
|
|
382
384
|
|
|
@@ -386,7 +388,7 @@ describe('| LVS_VectorPool_ControlService', () => {
|
|
|
386
388
|
const original: number[] | undefined = originals.get(result.id);
|
|
387
389
|
expect(original).toBeDefined();
|
|
388
390
|
const expected: number = LVS_VectorPool_ControlService.cosineSimilarity(query, original!);
|
|
389
|
-
expect(result.score).toBeCloseTo(expected,
|
|
391
|
+
expect(result.score).toBeCloseTo(expected, 6);
|
|
390
392
|
}
|
|
391
393
|
});
|
|
392
394
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DyFM_Log } from '@futdevpro/fsm-dynamo';
|
|
2
|
+
|
|
1
3
|
import { LVS_Search_Mode } from '../_enums/lvs-search-mode.enum';
|
|
2
4
|
import { LVS_SearchResult } from '../_models/lvs-search-result.interface';
|
|
3
5
|
|
|
@@ -10,14 +12,19 @@ export class LVS_VectorPool_ControlService {
|
|
|
10
12
|
/**
|
|
11
13
|
* In-memory vektor pool tárolás
|
|
12
14
|
* Key: vektor ID, Value: vektor értékek
|
|
15
|
+
*
|
|
16
|
+
* BFR-AM-013 (Tier-1 memória): a belső tárolás `Float32Array` (4 byte/elem, boxing nélkül) a
|
|
17
|
+
* korábbi `number[]` (8 byte + array-boxing) helyett → ~2–4× kisebb PERSISTENS pool-footprint
|
|
18
|
+
* (2560-dim × 32k vektornál a heap érdemi része). A cosine/l2 index-alapú loopjai változatlanul
|
|
19
|
+
* futnak rajta; a publikus `getAll()` kontraktja (number[]) konverzióval megőrzött.
|
|
13
20
|
*/
|
|
14
|
-
private vectorPool: Map<string,
|
|
21
|
+
private vectorPool: Map<string, Float32Array> = new Map<string, Float32Array>();
|
|
15
22
|
|
|
16
23
|
/**
|
|
17
24
|
* Normalizált vektor pool tárolás (L2 normalizált másolatok)
|
|
18
|
-
* Key: vektor ID, Value: L2 normalizált vektor értékek
|
|
25
|
+
* Key: vektor ID, Value: L2 normalizált vektor értékek (Float32Array — l. fent)
|
|
19
26
|
*/
|
|
20
|
-
private normalizedVectorPool: Map<string,
|
|
27
|
+
private normalizedVectorPool: Map<string, Float32Array> = new Map<string, Float32Array>();
|
|
21
28
|
|
|
22
29
|
/**
|
|
23
30
|
* `normalizedOnly` mód: ha `true`, a pool NEM tárolja a nyers vektorokat (`vectorPool`) — CSAK a
|
|
@@ -52,13 +59,13 @@ export class LVS_VectorPool_ControlService {
|
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
// Tároljuk az eredeti vektort — KIVÉVE normalizedOnly módban (akkor a nyers pool kihagyva, ~50% memória).
|
|
62
|
+
// Float32Array-ként (BFR-AM-013 Tier-1) — a number[]-boxing nélkül.
|
|
55
63
|
if (!this.normalizedOnly) {
|
|
56
|
-
this.vectorPool.set(id, vector);
|
|
64
|
+
this.vectorPool.set(id, Float32Array.from(vector));
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
// L2 normalizált másolatot is tárolunk a cosine similarity számításokhoz
|
|
60
|
-
|
|
61
|
-
this.normalizedVectorPool.set(id, normalized);
|
|
68
|
+
this.normalizedVectorPool.set(id, LVS_VectorPool_ControlService.l2NormalizeFloat32(vector));
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
/**
|
|
@@ -102,7 +109,33 @@ export class LVS_VectorPool_ControlService {
|
|
|
102
109
|
* Debugging és export céljára
|
|
103
110
|
*/
|
|
104
111
|
getAll(): Map<string, number[]> {
|
|
105
|
-
|
|
112
|
+
// A publikus kontrakt (number[]) megőrzése — a belső Float32Array-ből konvertálva.
|
|
113
|
+
const result: Map<string, number[]> = new Map<string, number[]>();
|
|
114
|
+
for (const [ id, vector ] of this.vectorPool.entries()) {
|
|
115
|
+
result.set(id, Array.from(vector));
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* BFR-AM-013 (Tier-1): L2-normalizálás Float32Array-be, explicit loopokkal (allokáció-minimális) —
|
|
122
|
+
* a pool BELSŐ tárolási formátumához. Nullvektor → csupa-0 Float32Array.
|
|
123
|
+
*/
|
|
124
|
+
private static l2NormalizeFloat32(vector: ArrayLike<number>): Float32Array {
|
|
125
|
+
let sumSquares: number = 0;
|
|
126
|
+
for (let i: number = 0; i < vector.length; i++) {
|
|
127
|
+
sumSquares += vector[i] * vector[i];
|
|
128
|
+
}
|
|
129
|
+
const magnitude: number = Math.sqrt(sumSquares);
|
|
130
|
+
|
|
131
|
+
const normalized: Float32Array = new Float32Array(vector.length);
|
|
132
|
+
if (magnitude === 0) {
|
|
133
|
+
return normalized;
|
|
134
|
+
}
|
|
135
|
+
for (let i: number = 0; i < vector.length; i++) {
|
|
136
|
+
normalized[i] = vector[i] / magnitude;
|
|
137
|
+
}
|
|
138
|
+
return normalized;
|
|
106
139
|
}
|
|
107
140
|
|
|
108
141
|
/**
|
|
@@ -156,7 +189,7 @@ export class LVS_VectorPool_ControlService {
|
|
|
156
189
|
* Eredmény: 0 vagy pozitív érték, ahol 0 a legkisebb távolság
|
|
157
190
|
* Statikus metódus, mivel nincs szüksége instance state-re
|
|
158
191
|
*/
|
|
159
|
-
static l2Distance(a: number
|
|
192
|
+
static l2Distance(a: ArrayLike<number>, b: ArrayLike<number>): number {
|
|
160
193
|
if (a.length !== b.length) {
|
|
161
194
|
throw new Error(
|
|
162
195
|
`Vectors must have the same dimension. Got ${a.length} and ${b.length}`
|
|
@@ -200,6 +233,14 @@ export class LVS_VectorPool_ControlService {
|
|
|
200
233
|
throw new Error('Query vector must be a non-empty array');
|
|
201
234
|
}
|
|
202
235
|
|
|
236
|
+
// BFR-AM-013 (NaN/±Inf-guard): nem-véges query-elem NÉMÁN mérgezné a TELJES rangsort
|
|
237
|
+
// (minden dot-product NaN → a valódi találatok kiesnek, hiba nélkül) → fail-closed throw.
|
|
238
|
+
for (let i: number = 0; i < query.length; i++) {
|
|
239
|
+
if (!Number.isFinite(query[i])) {
|
|
240
|
+
throw new Error(`Query vector must contain only finite numbers (non-finite at index ${i})`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
203
244
|
if (k <= 0) {
|
|
204
245
|
throw new Error('k must be a positive number');
|
|
205
246
|
}
|
|
@@ -217,9 +258,9 @@ export class LVS_VectorPool_ControlService {
|
|
|
217
258
|
|
|
218
259
|
// Validáljuk, hogy minden vektor ugyanolyan dimenziójú-e. A referencia-pool mód-függő: l2 a nyers,
|
|
219
260
|
// cosine a normalizált (azonos dimenzió, de `normalizedOnly`-ban a nyers pool üres).
|
|
220
|
-
const referencePool: Map<string,
|
|
261
|
+
const referencePool: Map<string, Float32Array> =
|
|
221
262
|
mode === LVS_Search_Mode.l2Distance ? this.vectorPool : this.normalizedVectorPool;
|
|
222
|
-
const firstVector:
|
|
263
|
+
const firstVector: Float32Array | undefined = referencePool.values().next().value;
|
|
223
264
|
if (firstVector && firstVector.length !== query.length) {
|
|
224
265
|
throw new Error(
|
|
225
266
|
`Query vector dimension (${query.length}) does not match pool vector ` +
|
|
@@ -239,7 +280,10 @@ export class LVS_VectorPool_ControlService {
|
|
|
239
280
|
// (2) a per-összehasonlítás dot-product ALLOKÁCIÓ-MENTES (a régi `l2Normalize` hívásonként KÉT új
|
|
240
281
|
// tömböt allokált → egy 24k-vektoros keresés ~48k tömb-allokáció + GC-spike-ot okozott).
|
|
241
282
|
// A drágán eltárolt `normalizedVectorPool` korábban SOHA nem volt használva a keresésben.
|
|
242
|
-
const queryNormalized:
|
|
283
|
+
const queryNormalized: Float32Array = LVS_VectorPool_ControlService.l2NormalizeFloat32(query);
|
|
284
|
+
// BFR-AM-013 (NaN-guard, defensive): ha egy tárolt vektor mégis NaN-score-t adna (elvileg az
|
|
285
|
+
// addVector-guard kizárja), a találat KIESIK — nem mérgezi a rangsort — és warn-t adunk (nem néma).
|
|
286
|
+
let nanScoreCount: number = 0;
|
|
243
287
|
for (const [id, normalizedVector] of this.normalizedVectorPool.entries()) {
|
|
244
288
|
if (candidateIds && !candidateIds.has(id)) {
|
|
245
289
|
continue;
|
|
@@ -248,8 +292,15 @@ export class LVS_VectorPool_ControlService {
|
|
|
248
292
|
for (let i: number = 0; i < queryNormalized.length; i++) {
|
|
249
293
|
dotProduct += queryNormalized[i] * normalizedVector[i];
|
|
250
294
|
}
|
|
295
|
+
if (!Number.isFinite(dotProduct)) {
|
|
296
|
+
nanScoreCount++;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
251
299
|
results.push({ id, score: dotProduct });
|
|
252
300
|
}
|
|
301
|
+
if (nanScoreCount) {
|
|
302
|
+
DyFM_Log.warn(`[LVS search] ${nanScoreCount} vector(s) produced non-finite scores and were skipped.`);
|
|
303
|
+
}
|
|
253
304
|
// Cosine similarity: csökkenő sorrend (legnagyobb hasonlóság először)
|
|
254
305
|
results.sort((a: LVS_SearchResult, b: LVS_SearchResult) => b.score - a.score);
|
|
255
306
|
} else if (mode === LVS_Search_Mode.l2Distance) {
|
|
@@ -14,6 +14,8 @@ import * as mongoose from 'mongoose';
|
|
|
14
14
|
|
|
15
15
|
import { version as _serverVersion } from '../../../../package.json';
|
|
16
16
|
import { DyNTS_SingletonService } from '../../../_services/base/singleton.service';
|
|
17
|
+
import { DyNTS_DbStats, DyNTS_DbStats_Util } from '../../../_services/core/db-stats.service';
|
|
18
|
+
import { DyNTS_Diagnostics, DyNTS_DoctorReport } from '../../../_services/core/diagnostics.service';
|
|
17
19
|
import { DyNTS_global_settings } from '../../../_collections/global-settings.const';
|
|
18
20
|
import { DyNTS_Errors_ControlService } from '../errors/errors.control-service';
|
|
19
21
|
import { DyNTS_ServerStatusSnapshot_ControlService } from './server-status-snapshot.control-service';
|
|
@@ -221,6 +223,16 @@ export abstract class DyNTS_ServerStatus_ControlService<
|
|
|
221
223
|
* A mongoose default-connection. Külön metódus = teszt-seam (spy-jal felülírható, hogy a
|
|
222
224
|
* {@link checkDbReadiness} DB nélkül is tesztelhető legyen).
|
|
223
225
|
*/
|
|
226
|
+
/** BFR-OVERSEER-004 — stats-only DB-diagnosztika a `/server/db-stats` endpointnak (metaadat-only). */
|
|
227
|
+
async getDbStats(): Promise<DyNTS_DbStats> {
|
|
228
|
+
return DyNTS_DbStats_Util.collect(this.getMongooseConnection());
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** BFR-AM-015 — aggregált doctor-riport a `/server/doctor` endpointnak (sosem dob). */
|
|
232
|
+
async runDoctor(): Promise<DyNTS_DoctorReport> {
|
|
233
|
+
return DyNTS_Diagnostics.runDoctor({ connection: this.getMongooseConnection() });
|
|
234
|
+
}
|
|
235
|
+
|
|
224
236
|
protected getMongooseConnection(): mongoose.Connection {
|
|
225
237
|
// FIX (2026-07-22) — a `mongoose.connection` NEM feltetlenul az a handle, amit a data-layer hasznal.
|
|
226
238
|
//
|
|
@@ -252,6 +252,34 @@ export abstract class DyNTS_ServerStatus_Controller<
|
|
|
252
252
|
],
|
|
253
253
|
}),
|
|
254
254
|
|
|
255
|
+
// BFR-OVERSEER-004 — stats-only DB-diagnosztika (per-collection count + méret + index-méret;
|
|
256
|
+
// metaadat-only, SOHA nem dokumentum-tartalom). App-auth-tal gate-elhető (lazyPreProcessesFor).
|
|
257
|
+
new DyNTS_Endpoint_Params({
|
|
258
|
+
name: 'getDbStats',
|
|
259
|
+
type: DyFM_HttpCallType.get,
|
|
260
|
+
endpoint: '/db-stats',
|
|
261
|
+
preProcesses: this.lazyPreProcessesFor('getDbStats'),
|
|
262
|
+
tasks: [
|
|
263
|
+
async (req: Request, res: Response): Promise<void> => {
|
|
264
|
+
res.send(await this.server_CS.getDbStats());
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
}),
|
|
268
|
+
|
|
269
|
+
// BFR-AM-015 — aggregált doctor (mongo-probe + memory + event-loop + db-stats + app-checkek;
|
|
270
|
+
// health: ok|warn|fail + per-check fixHint). App-auth-tal gate-elhető.
|
|
271
|
+
new DyNTS_Endpoint_Params({
|
|
272
|
+
name: 'getDoctor',
|
|
273
|
+
type: DyFM_HttpCallType.get,
|
|
274
|
+
endpoint: '/doctor',
|
|
275
|
+
preProcesses: this.lazyPreProcessesFor('getDoctor'),
|
|
276
|
+
tasks: [
|
|
277
|
+
async (req: Request, res: Response): Promise<void> => {
|
|
278
|
+
res.send(await this.server_CS.runDoctor());
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
}),
|
|
282
|
+
|
|
255
283
|
// Readiness-probe: a /health-tel (liveness) szemben a DB-elérhetőséget is ellenőrzi.
|
|
256
284
|
// Public (mint a /health) — orchestrátor/LB hívja; 503 ha a DB nem ready → no traffic.
|
|
257
285
|
new DyNTS_Endpoint_Params({
|
|
@@ -43,4 +43,27 @@ describe('| DyNTS_SocketServerService_Params', () => {
|
|
|
43
43
|
expect(instance.port).toBeUndefined();
|
|
44
44
|
expect(instance.security).toBe(DyNTS_SocketSecurity.open);
|
|
45
45
|
});
|
|
46
|
+
|
|
47
|
+
// BFR-MASTERPROMPTER-025 — az attach-mód EXPLICIT opt-in, a port önmagában NEM diszkriminátor
|
|
48
|
+
it('| dedicatedPort default FALSE (shared) — port-label önmagában nem vált dedikáltra', () => {
|
|
49
|
+
const instance = new DyNTS_SocketServerService_Params({
|
|
50
|
+
name: 'LegacyPortLabel',
|
|
51
|
+
port: 39047,
|
|
52
|
+
security: DyNTS_SocketSecurity.open,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(instance.dedicatedPort).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('| dedicatedPort: true explicit opt-in megőrződik', () => {
|
|
59
|
+
const instance = new DyNTS_SocketServerService_Params({
|
|
60
|
+
name: 'DedicatedNotif',
|
|
61
|
+
port: 39157,
|
|
62
|
+
dedicatedPort: true,
|
|
63
|
+
security: DyNTS_SocketSecurity.open,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(instance.dedicatedPort).toBe(true);
|
|
67
|
+
expect(instance.port).toBe(39157);
|
|
68
|
+
});
|
|
46
69
|
});
|
|
@@ -4,12 +4,22 @@ import { DyNTS_SocketSecurity } from '../_enums/socket-security.enum';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Socket service paraméterek.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
|
+
* **BFR-MASTERPROMPTER-025 (attach-mód):** a socket alapból a fő HTTP/HTTPS szerverre attacholódik
|
|
9
|
+
* (SHARED — unified-port modell). Dedikált saját listener CSAK explicit `dedicatedPort: true`-val kérhető
|
|
10
|
+
* (+ `port` kötelező és ≠ fő port). A korábbi implicit inferencia (port≠httpPort → dedikált) csendben
|
|
11
|
+
* regresszálta azokat a unified-appokat, amelyek legacy-label okból adtak portot — ezért az attach-mód
|
|
12
|
+
* mostantól KÓD-EXPLICIT, a `port` önmagában NEM diszkriminátor.
|
|
8
13
|
*/
|
|
9
14
|
export class DyNTS_SocketServerService_Params<T = any> {
|
|
10
15
|
name: string;
|
|
11
16
|
/** Opcionális: ha nincs megadva, az extended app a HTTP/HTTPS portot használja (same port). */
|
|
12
17
|
port?: number;
|
|
18
|
+
/**
|
|
19
|
+
* EXPLICIT opt-in a dedikált saját listenerre (a fő szerver HELYETT). Default: false = shared.
|
|
20
|
+
* `true` esetén a `port` kötelező és el kell térnie a fő HTTP-porttól.
|
|
21
|
+
*/
|
|
22
|
+
dedicatedPort?: boolean;
|
|
13
23
|
security?: DyNTS_SocketSecurity;
|
|
14
24
|
|
|
15
25
|
constructor(
|
|
@@ -17,6 +27,7 @@ export class DyNTS_SocketServerService_Params<T = any> {
|
|
|
17
27
|
) {
|
|
18
28
|
this.name = set.name;
|
|
19
29
|
this.port = set.port;
|
|
30
|
+
this.dedicatedPort = set.dedicatedPort ?? false;
|
|
20
31
|
this.security = set.security ? set.security : DyNTS_global_settings.defaultSocketSecurity;
|
|
21
32
|
}
|
|
22
33
|
}
|
|
@@ -51,6 +51,8 @@ export abstract class DyNTS_SocketServerService<
|
|
|
51
51
|
protected params: DyNTS_SocketServerService_Params;
|
|
52
52
|
get name(): string { return this.params.name; }
|
|
53
53
|
get port(): number | undefined { return this.params.port; }
|
|
54
|
+
/** BFR-MASTERPROMPTER-025: explicit dedikált-listener opt-in (default false = shared). */
|
|
55
|
+
get dedicatedPort(): boolean { return this.params.dedicatedPort ?? false; }
|
|
54
56
|
get security(): DyNTS_SocketSecurity { return this.params.security; }
|
|
55
57
|
get activeSubscriptionsCount(): number { return this.presences.length; }
|
|
56
58
|
|
|
@@ -512,15 +512,21 @@ export abstract class DyNTS_AppExtended extends DyNTS_App {
|
|
|
512
512
|
if (service.security === DyNTS_SocketSecurity.open) {
|
|
513
513
|
this.systemControlsExt.httpSocketServer.init = true;
|
|
514
514
|
|
|
515
|
-
// BFR-
|
|
516
|
-
//
|
|
517
|
-
//
|
|
518
|
-
//
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
515
|
+
// BFR-MASTERPROMPTER-025 (a BFR-FDPTOKENSERVICE-007 3a2c381 utó-fix-e): az attach-mód
|
|
516
|
+
// EXPLICIT opt-in (`dedicatedPort: true`), NEM a port-érték implicit inferenciája. A
|
|
517
|
+
// korábbi `port≠httpPort → dedikált` inferencia CSENDBEN regresszálta a unified-shared
|
|
518
|
+
// appokat, amelyek legacy-label/notif-konvenció okból adtak portot (MP live-igazolt:
|
|
519
|
+
// realtime DEAD az nts-bump után; adventor/ccap/warbots latens). Mostantól:
|
|
520
|
+
// - default (dedicatedPort hiányzik/false) → SHARED (a fő szerverre attach, a port-label irreleváns);
|
|
521
|
+
// - dedicatedPort: true → saját Http.Server + listen a service.port-on (a bizonyított
|
|
522
|
+
// non-base útvonal tükre) — pl. a token-service szándékosan-dedikált notif-portja.
|
|
523
|
+
if (service.dedicatedPort && (service.port === undefined || service.port === this.portSettings.httpPort)) {
|
|
524
|
+
throw new Error(
|
|
525
|
+
`SOCKET CONFIG ERROR: "${service.name}" dedicatedPort:true requires an explicit ` +
|
|
526
|
+
`port that differs from the main httpPort (${this.portSettings.httpPort}).`
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
const attachToMain: boolean = useBaseHttpServer && !service.dedicatedPort;
|
|
524
530
|
const serverForOpen: Http.Server = attachToMain
|
|
525
531
|
? this.httpServer
|
|
526
532
|
: (useBaseHttpServer
|