@futdevpro/nts-dynamo 1.15.256 → 1.15.262
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 -21
- package/README.md +789 -789
- package/build/_modules/ai/_modules/open-ai/_services/oai-llm-chat.service-base.d.ts.map +1 -1
- package/build/_modules/ai/_modules/open-ai/_services/oai-llm-chat.service-base.js.map +1 -1
- package/build/_modules/ai/_modules/open-ai/_services/oai-llm.service-base.d.ts +47 -2
- package/build/_modules/ai/_modules/open-ai/_services/oai-llm.service-base.d.ts.map +1 -1
- package/build/_modules/ai/_modules/open-ai/_services/oai-llm.service-base.js +85 -6
- package/build/_modules/ai/_modules/open-ai/_services/oai-llm.service-base.js.map +1 -1
- package/build/_modules/ai/_modules/open-ai/_services/oai-user-key.control-service.d.ts.map +1 -1
- package/build/_modules/ai/_modules/open-ai/_services/oai-user-key.control-service.js +39 -4
- package/build/_modules/ai/_modules/open-ai/_services/oai-user-key.control-service.js.map +1 -1
- package/build/_modules/media-gen/_collections/media-gen-prompt.util.d.ts +24 -0
- package/build/_modules/media-gen/_collections/media-gen-prompt.util.d.ts.map +1 -0
- package/build/_modules/media-gen/_collections/media-gen-prompt.util.js +55 -0
- package/build/_modules/media-gen/_collections/media-gen-prompt.util.js.map +1 -0
- package/build/_modules/media-gen/_models/media-gen-operation.type.d.ts +9 -0
- package/build/_modules/media-gen/_models/media-gen-operation.type.d.ts.map +1 -0
- package/build/_modules/media-gen/_models/media-gen-operation.type.js +3 -0
- package/build/_modules/media-gen/_models/media-gen-operation.type.js.map +1 -0
- package/build/_modules/media-gen/_services/media-gen.registry.d.ts +35 -0
- package/build/_modules/media-gen/_services/media-gen.registry.d.ts.map +1 -0
- package/build/_modules/media-gen/_services/media-gen.registry.js +71 -0
- package/build/_modules/media-gen/_services/media-gen.registry.js.map +1 -0
- package/build/_modules/media-gen/_services/media-gen.service-base.d.ts +30 -0
- package/build/_modules/media-gen/_services/media-gen.service-base.d.ts.map +1 -0
- package/build/_modules/media-gen/_services/media-gen.service-base.js +43 -0
- package/build/_modules/media-gen/_services/media-gen.service-base.js.map +1 -0
- package/build/_modules/media-gen/index.d.ts +5 -0
- package/build/_modules/media-gen/index.d.ts.map +1 -0
- package/build/_modules/media-gen/index.js +17 -0
- package/build/_modules/media-gen/index.js.map +1 -0
- package/build/_services/server/app.server.d.ts +73 -0
- package/build/_services/server/app.server.d.ts.map +1 -1
- package/build/_services/server/app.server.js +147 -0
- package/build/_services/server/app.server.js.map +1 -1
- package/package.json +12 -3
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DyNTS_MediaGen_Registry = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The two-key registry: provider × operation → adapter.
|
|
6
|
+
*
|
|
7
|
+
* Generalised from the master-prompter `generationProviders` table (`ProviderType × AiModelType`). An instance
|
|
8
|
+
* rather than a module-level constant: a product registers the adapters it ships, and a test builds its own
|
|
9
|
+
* registry without touching global state.
|
|
10
|
+
*/
|
|
11
|
+
class DyNTS_MediaGen_Registry {
|
|
12
|
+
services = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* Registers an adapter under every operation it declares.
|
|
15
|
+
*
|
|
16
|
+
* Refuses an adapter that declares no operation (it could never be resolved), and a second, different adapter
|
|
17
|
+
* for a pair that is already taken - a silent replacement would hide which adapter actually serves the call.
|
|
18
|
+
* Registering the same instance again is a no-op.
|
|
19
|
+
*/
|
|
20
|
+
register(service) {
|
|
21
|
+
if (!service.operations.length) {
|
|
22
|
+
throw new Error(`DyNTS_MediaGen_Registry: ${service.constructor.name} (provider '${service.provider}') declares no operations.`);
|
|
23
|
+
}
|
|
24
|
+
const byOperation = this.services.get(service.provider) ?? new Map();
|
|
25
|
+
for (const operation of service.operations) {
|
|
26
|
+
const existing = byOperation.get(operation);
|
|
27
|
+
if (existing && existing !== service) {
|
|
28
|
+
throw new Error(`DyNTS_MediaGen_Registry: '${operation}' of provider '${service.provider}' is already served by `
|
|
29
|
+
+ `${existing.constructor.name}; registering ${service.constructor.name} would silently replace it.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (const operation of service.operations) {
|
|
33
|
+
byOperation.set(operation, service);
|
|
34
|
+
}
|
|
35
|
+
this.services.set(service.provider, byOperation);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The adapter serving `operation` for `provider`. When there is none, the error says what IS available:
|
|
39
|
+
* the provider's operations if the provider is known, otherwise the providers serving the operation.
|
|
40
|
+
*/
|
|
41
|
+
resolve(provider, operation) {
|
|
42
|
+
const service = this.services.get(provider)?.get(operation);
|
|
43
|
+
if (service) {
|
|
44
|
+
return service;
|
|
45
|
+
}
|
|
46
|
+
const providerOperations = [...(this.services.get(provider)?.keys() ?? [])];
|
|
47
|
+
if (providerOperations.length) {
|
|
48
|
+
throw new Error(`DyNTS_MediaGen_Registry: provider '${provider}' does not serve '${operation}'. `
|
|
49
|
+
+ `It serves: ${providerOperations.join(', ')}.`);
|
|
50
|
+
}
|
|
51
|
+
const operationProviders = this.entries()
|
|
52
|
+
.filter((entry) => entry.operation === operation)
|
|
53
|
+
.map((entry) => entry.provider);
|
|
54
|
+
throw new Error(`DyNTS_MediaGen_Registry: no provider '${provider}' is registered. `
|
|
55
|
+
+ `'${operation}' is served by: ${operationProviders.length ? operationProviders.join(', ') : 'no provider'}.`);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Every registered provider × operation pair, in registration order.
|
|
59
|
+
*/
|
|
60
|
+
entries() {
|
|
61
|
+
const entries = [];
|
|
62
|
+
for (const [provider, byOperation] of this.services) {
|
|
63
|
+
for (const operation of byOperation.keys()) {
|
|
64
|
+
entries.push({ provider: provider, operation: operation });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return entries;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.DyNTS_MediaGen_Registry = DyNTS_MediaGen_Registry;
|
|
71
|
+
//# sourceMappingURL=media-gen.registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-gen.registry.js","sourceRoot":"","sources":["../../../../src/_modules/media-gen/_services/media-gen.registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DyNTS_SingletonService } from '../../../_services/base/singleton.service';
|
|
2
|
+
import { DyNTS_MediaGen_Operation } from '../_models/media-gen-operation.type';
|
|
3
|
+
/**
|
|
4
|
+
* Base of one media-generation provider adapter (OpenAI Images, ElevenLabs, Leonardo, Meshy, Synexa, ...).
|
|
5
|
+
*
|
|
6
|
+
* Generalised from the master-prompter `Generation_ServiceBase`. Two things are left out on purpose:
|
|
7
|
+
* - the flow-run coupling (step connectors, flow nodes) - a request object replaces it;
|
|
8
|
+
* - the token-cost methods - cost accounting stays in the product that sells the credits (K-9).
|
|
9
|
+
*
|
|
10
|
+
* What remains is what every adapter needs: who it is, which operations it serves, the call itself, and
|
|
11
|
+
* prompt assembly from a `Đ{key}` template. Capability is data: `operations` is what the registry indexes,
|
|
12
|
+
* so an adapter that cannot edit simply does not list `image-edit`, and a caller asking for it is told what
|
|
13
|
+
* the adapter CAN do.
|
|
14
|
+
*/
|
|
15
|
+
export declare abstract class DyNTS_MediaGen_ServiceBase<T_Request, T_Response> extends DyNTS_SingletonService {
|
|
16
|
+
/** Stable provider id (e.g. `openai`, `elevenlabs`) - the registry's first key. */
|
|
17
|
+
abstract readonly provider: string;
|
|
18
|
+
/** The operations this adapter serves - the registry's second key. Must match what `generate` handles. */
|
|
19
|
+
abstract readonly operations: readonly DyNTS_MediaGen_Operation[];
|
|
20
|
+
/**
|
|
21
|
+
* Performs one generation.
|
|
22
|
+
*/
|
|
23
|
+
abstract generate(operation: DyNTS_MediaGen_Operation, request: T_Request, issuer: string): Promise<T_Response>;
|
|
24
|
+
/**
|
|
25
|
+
* Assembles the exact prompt from a `Đ{key}` template (see `DyNTS_MediaGen_Prompt_Util.fill`), and reports
|
|
26
|
+
* a failure with this adapter's error settings.
|
|
27
|
+
*/
|
|
28
|
+
protected assemblePrompt(template: string, values: Record<string, unknown>, issuer: string): string;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=media-gen.service-base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-gen.service-base.d.ts","sourceRoot":"","sources":["../../../../src/_modules/media-gen/_services/media-gen.service-base.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AAEnF,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAE/E;;;;;;;;;;;GAWG;AACH,8BAAsB,0BAA0B,CAAC,SAAS,EAAE,UAAU,CAAE,SAAQ,sBAAsB;IAEpG,mFAAmF;IACnF,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAEnC,0GAA0G;IAC1G,QAAQ,CAAC,QAAQ,CAAC,UAAU,EAAE,SAAS,wBAAwB,EAAE,CAAC;IAElE;;OAEG;IACH,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,wBAAwB,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAE/G;;;OAGG;IACH,SAAS,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;CAepG"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DyNTS_MediaGen_ServiceBase = void 0;
|
|
4
|
+
const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
|
|
5
|
+
const global_settings_const_1 = require("../../../_collections/global-settings.const");
|
|
6
|
+
const singleton_service_1 = require("../../../_services/base/singleton.service");
|
|
7
|
+
const media_gen_prompt_util_1 = require("../_collections/media-gen-prompt.util");
|
|
8
|
+
/**
|
|
9
|
+
* Base of one media-generation provider adapter (OpenAI Images, ElevenLabs, Leonardo, Meshy, Synexa, ...).
|
|
10
|
+
*
|
|
11
|
+
* Generalised from the master-prompter `Generation_ServiceBase`. Two things are left out on purpose:
|
|
12
|
+
* - the flow-run coupling (step connectors, flow nodes) - a request object replaces it;
|
|
13
|
+
* - the token-cost methods - cost accounting stays in the product that sells the credits (K-9).
|
|
14
|
+
*
|
|
15
|
+
* What remains is what every adapter needs: who it is, which operations it serves, the call itself, and
|
|
16
|
+
* prompt assembly from a `Đ{key}` template. Capability is data: `operations` is what the registry indexes,
|
|
17
|
+
* so an adapter that cannot edit simply does not list `image-edit`, and a caller asking for it is told what
|
|
18
|
+
* the adapter CAN do.
|
|
19
|
+
*/
|
|
20
|
+
class DyNTS_MediaGen_ServiceBase extends singleton_service_1.DyNTS_SingletonService {
|
|
21
|
+
/**
|
|
22
|
+
* Assembles the exact prompt from a `Đ{key}` template (see `DyNTS_MediaGen_Prompt_Util.fill`), and reports
|
|
23
|
+
* a failure with this adapter's error settings.
|
|
24
|
+
*/
|
|
25
|
+
assemblePrompt(template, values, issuer) {
|
|
26
|
+
try {
|
|
27
|
+
return media_gen_prompt_util_1.DyNTS_MediaGen_Prompt_Util.fill(template, values);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw new fsm_dynamo_1.DyFM_Error({
|
|
31
|
+
...this.getDefaultErrorSettings('assemblePrompt', error, issuer),
|
|
32
|
+
errorCode: `${global_settings_const_1.DyNTS_global_settings.systemShortCodeName}|DyNTS-MG-AP0`,
|
|
33
|
+
additionalContent: {
|
|
34
|
+
provider: this.provider,
|
|
35
|
+
keys: media_gen_prompt_util_1.DyNTS_MediaGen_Prompt_Util.getKeys(template ?? ''),
|
|
36
|
+
providedKeys: Object.keys(values ?? {}),
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.DyNTS_MediaGen_ServiceBase = DyNTS_MediaGen_ServiceBase;
|
|
43
|
+
//# sourceMappingURL=media-gen.service-base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-gen.service-base.js","sourceRoot":"","sources":["../../../../src/_modules/media-gen/_services/media-gen.service-base.ts"],"names":[],"mappings":";;;AAAA,sDAAmD;AAEnD,uFAAoF;AACpF,iFAAmF;AACnF,iFAAmF;AAGnF;;;;;;;;;;;GAWG;AACH,MAAsB,0BAAkD,SAAQ,0CAAsB;IAapG;;;OAGG;IACO,cAAc,CAAC,QAAgB,EAAE,MAA+B,EAAE,MAAc;QACxF,IAAI,CAAC;YACH,OAAO,kDAA0B,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC3D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,uBAAU,CAAC;gBACnB,GAAG,IAAI,CAAC,uBAAuB,CAAC,gBAAgB,EAAE,KAAK,EAAE,MAAM,CAAC;gBAChE,SAAS,EAAE,GAAG,6CAAqB,CAAC,mBAAmB,eAAe;gBACtE,iBAAiB,EAAE;oBACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;oBACvB,IAAI,EAAE,kDAA0B,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;oBACxD,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;iBACxC;aACF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;CACF;AAhCD,gEAgCC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/_modules/media-gen/index.ts"],"names":[],"mappings":"AAUA,cAAc,oCAAoC,CAAC;AAGnD,cAAc,sCAAsC,CAAC;AAGrD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gCAAgC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
//
|
|
3
|
+
// Media generation module (media-generation hyperplan, MP-5)
|
|
4
|
+
//
|
|
5
|
+
// The provider-independent generation engine: an adapter base class and the provider × operation registry.
|
|
6
|
+
// Cost accounting is deliberately NOT here - it stays in the product that sells the credits.
|
|
7
|
+
//
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
const tslib_1 = require("tslib");
|
|
10
|
+
// MODELS
|
|
11
|
+
tslib_1.__exportStar(require("./_models/media-gen-operation.type"), exports);
|
|
12
|
+
// COLLECTIONS
|
|
13
|
+
tslib_1.__exportStar(require("./_collections/media-gen-prompt.util"), exports);
|
|
14
|
+
// SERVICES
|
|
15
|
+
tslib_1.__exportStar(require("./_services/media-gen.service-base"), exports);
|
|
16
|
+
tslib_1.__exportStar(require("./_services/media-gen.registry"), exports);
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/_modules/media-gen/index.ts"],"names":[],"mappings":";AACA,EAAE;AACF,6DAA6D;AAC7D,EAAE;AACF,2GAA2G;AAC3G,6FAA6F;AAC7F,EAAE;;;AAGF,SAAS;AACT,6EAAmD;AAEnD,cAAc;AACd,+EAAqD;AAErD,WAAW;AACX,6EAAmD;AACnD,yEAA+C"}
|
|
@@ -216,6 +216,50 @@ export interface DyNTS_TtlIndexCollection {
|
|
|
216
216
|
* maintenance, not silently at every startup.
|
|
217
217
|
*/
|
|
218
218
|
export type DyNTS_TtlIndexAction = 'created' | 'differs' | 'noop';
|
|
219
|
+
/**
|
|
220
|
+
* FR-BFR-041 / SR-3 — one declared COMPOUND / PARTIAL index, as the startup installer READS it.
|
|
221
|
+
*
|
|
222
|
+
* Declared STRUCTURALLY on purpose — mirrors `DyFM_DeclaredIndex` (`@futdevpro/fsm-dynamo` >= 1.20.38),
|
|
223
|
+
* which is the source of truth for the declaration shape. It is NOT imported, because this package pins
|
|
224
|
+
* `@futdevpro/fsm-dynamo` as an EXACT peer (1.20.37): importing the new symbol would force every consumer
|
|
225
|
+
* onto a new exact peer in the same wave (an unsatisfiable peer set for anyone still on 1.20.37). Reading
|
|
226
|
+
* the field structurally keeps this change strictly additive.
|
|
227
|
+
*/
|
|
228
|
+
export interface DyNTS_DeclaredIndexInfo {
|
|
229
|
+
name: string;
|
|
230
|
+
keys: Record<string, 1 | -1>;
|
|
231
|
+
unique?: boolean;
|
|
232
|
+
partialFilterExpression?: Record<string, unknown>;
|
|
233
|
+
}
|
|
234
|
+
/** FR-BFR-041 / SR-3 — one live index entry from `collection.indexes()` (only the fields the installer compares). */
|
|
235
|
+
export interface DyNTS_DeclaredIndexLiveInfo {
|
|
236
|
+
name?: string;
|
|
237
|
+
key?: Record<string, number>;
|
|
238
|
+
unique?: boolean;
|
|
239
|
+
partialFilterExpression?: Record<string, unknown>;
|
|
240
|
+
}
|
|
241
|
+
/** FR-BFR-041 / SR-3 — the options the installer passes to `createIndex`. */
|
|
242
|
+
export interface DyNTS_DeclaredIndexCreateOptions {
|
|
243
|
+
name: string;
|
|
244
|
+
unique?: boolean;
|
|
245
|
+
partialFilterExpression?: Record<string, unknown>;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* FR-BFR-041 / SR-3 — the minimal native-driver collection surface the declared-index installer needs.
|
|
249
|
+
* Structural (no mongodb type import) so it stays dependency-light and trivially mockable.
|
|
250
|
+
*/
|
|
251
|
+
export interface DyNTS_DeclaredIndexCollection {
|
|
252
|
+
indexes?: () => Promise<DyNTS_DeclaredIndexLiveInfo[]>;
|
|
253
|
+
createIndex: (keys: Record<string, number>, options: DyNTS_DeclaredIndexCreateOptions) => Promise<unknown>;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* FR-BFR-041 / SR-3 — outcome of {@link DyNTS_App.ensureDeclaredIndex}.
|
|
257
|
+
* - `created`: no index with this name existed -> it was created.
|
|
258
|
+
* - `noop`: an index with this name and the SAME keys / uniqueness / partial filter already exists.
|
|
259
|
+
* - `differs`: an index with this name exists but DIFFERS -> left UNTOUCHED, only reported (same boot-safety
|
|
260
|
+
* rule as the retention TTL index: never drop + rebuild a live index inside the startup path).
|
|
261
|
+
*/
|
|
262
|
+
export type DyNTS_DeclaredIndexAction = 'created' | 'differs' | 'noop';
|
|
219
263
|
export declare abstract class DyNTS_App extends DyNTS_SingletonService {
|
|
220
264
|
protected systemControls: DyNTS_AppSystemControls;
|
|
221
265
|
get started(): boolean;
|
|
@@ -338,6 +382,35 @@ export declare abstract class DyNTS_App extends DyNTS_SingletonService {
|
|
|
338
382
|
* CHANGE on an already-indexed collection is a maintenance action, not a silent every-boot rebuild.
|
|
339
383
|
*/
|
|
340
384
|
static ensureRetentionTtlIndex(collection: DyNTS_TtlIndexCollection, ttlSeconds: number): Promise<DyNTS_TtlIndexAction>;
|
|
385
|
+
/**
|
|
386
|
+
* FR-BFR-041 / SR-3 — install the declared COMPOUND / PARTIAL indexes of every registered DB service.
|
|
387
|
+
*
|
|
388
|
+
* Runs in the SAME deferred, fire-and-forget window as the retention TTL installer, with the same guarantees:
|
|
389
|
+
* - **Non-fatal** — every error is reported as a safe diagnostic and NEVER blocks or crashes startup. A
|
|
390
|
+
* unique index build over data that already holds duplicates is rejected by MongoDB — that rejection is
|
|
391
|
+
* caught per index and reported, it does not stop the other indexes.
|
|
392
|
+
* - **Idempotent + boot-safe** — an identical index is a no-op; a DIFFERING index is reported, never rebuilt.
|
|
393
|
+
* - **Archive-aware** — the auto-created `_archived` siblings copy the parent's params (spread), so they
|
|
394
|
+
* would inherit the declaration. A UNIQUE index is SKIPPED on them: an archive may legitimately hold the
|
|
395
|
+
* same key more than once, and a unique index there could make archiving itself fail.
|
|
396
|
+
*/
|
|
397
|
+
private installDeclaredIndexes;
|
|
398
|
+
/**
|
|
399
|
+
* FR-BFR-041 / SR-3 — read and validate the `indexes` declaration of a data-model STRUCTURALLY.
|
|
400
|
+
*
|
|
401
|
+
* Returns only the installable entries (a non-empty `name`, at least one key, every key direction `1` or
|
|
402
|
+
* `-1`, optional `unique` boolean, optional `partialFilterExpression` object). On an archive sibling a
|
|
403
|
+
* `unique` entry is dropped (see {@link DyNTS_App.installDeclaredIndexes}). Pure + side-effect-free.
|
|
404
|
+
*/
|
|
405
|
+
static resolveDeclaredIndexes(dataParams: unknown, isArchive: boolean): DyNTS_DeclaredIndexInfo[];
|
|
406
|
+
/**
|
|
407
|
+
* FR-BFR-041 / SR-3 — ensure ONE declared index. Matching is by NAME (the stable reconciliation key):
|
|
408
|
+
* - no live index with that name -> create it -> `'created'`
|
|
409
|
+
* - same name, same keys (ORDER matters), same uniqueness
|
|
410
|
+
* and same partial filter -> nothing -> `'noop'`
|
|
411
|
+
* - same name, anything else different -> untouched -> `'differs'` (reported, never rebuilt)
|
|
412
|
+
*/
|
|
413
|
+
static ensureDeclaredIndex(collection: DyNTS_DeclaredIndexCollection, declared: DyNTS_DeclaredIndexInfo): Promise<DyNTS_DeclaredIndexAction>;
|
|
341
414
|
/**
|
|
342
415
|
*
|
|
343
416
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.server.d.ts","sourceRoot":"","sources":["../../../src/_services/server/app.server.ts"],"names":[],"mappings":"AACA,OAAO,QAAQ,GAAG,QAAQ,UAAU,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"app.server.d.ts","sourceRoot":"","sources":["../../../src/_services/server/app.server.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.DyNTS_App = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const Mongoose = require("mongoose");
|
|
6
|
+
const archive_util_1 = require("../../_collections/archive.util");
|
|
6
7
|
const Express = require("express");
|
|
7
8
|
/* import Mongoose from 'mongoose';
|
|
8
9
|
import Express from 'express'; */
|
|
@@ -605,8 +606,12 @@ class DyNTS_App extends singleton_service_1.DyNTS_SingletonService {
|
|
|
605
606
|
// cold boot is never slowed by it, then run it detached. `.unref()` keeps it from holding
|
|
606
607
|
// the event loop open. Any failure is logged but never crashes the app. (`'differs'` never
|
|
607
608
|
// rebuilds a live index — see ensureRetentionTtlIndex.)
|
|
609
|
+
// FR-BFR-041 / SR-3 — the declared COMPOUND / PARTIAL indexes ride in the SAME deferred,
|
|
610
|
+
// detached, non-fatal window (a unique index build is heavier than a TTL probe, so it must
|
|
611
|
+
// never compete with boot-readiness either).
|
|
608
612
|
const ttlInstallTimer = setTimeout(() => {
|
|
609
613
|
void this.installRetentionTtlIndexes();
|
|
614
|
+
void this.installDeclaredIndexes();
|
|
610
615
|
}, 30000);
|
|
611
616
|
if (typeof ttlInstallTimer.unref === 'function') {
|
|
612
617
|
ttlInstallTimer.unref();
|
|
@@ -761,6 +766,148 @@ class DyNTS_App extends singleton_service_1.DyNTS_SingletonService {
|
|
|
761
766
|
// boot — only report it. Avoids a heavy drop+recreate competing with boot-readiness on a large collection.
|
|
762
767
|
return 'differs';
|
|
763
768
|
}
|
|
769
|
+
/**
|
|
770
|
+
* FR-BFR-041 / SR-3 — install the declared COMPOUND / PARTIAL indexes of every registered DB service.
|
|
771
|
+
*
|
|
772
|
+
* Runs in the SAME deferred, fire-and-forget window as the retention TTL installer, with the same guarantees:
|
|
773
|
+
* - **Non-fatal** — every error is reported as a safe diagnostic and NEVER blocks or crashes startup. A
|
|
774
|
+
* unique index build over data that already holds duplicates is rejected by MongoDB — that rejection is
|
|
775
|
+
* caught per index and reported, it does not stop the other indexes.
|
|
776
|
+
* - **Idempotent + boot-safe** — an identical index is a no-op; a DIFFERING index is reported, never rebuilt.
|
|
777
|
+
* - **Archive-aware** — the auto-created `_archived` siblings copy the parent's params (spread), so they
|
|
778
|
+
* would inherit the declaration. A UNIQUE index is SKIPPED on them: an archive may legitimately hold the
|
|
779
|
+
* same key more than once, and a unique index there could make archiving itself fail.
|
|
780
|
+
*/
|
|
781
|
+
async installDeclaredIndexes() {
|
|
782
|
+
try {
|
|
783
|
+
const services = global_service_1.DyNTS_GlobalService.getAllDBServices();
|
|
784
|
+
let ensured = 0;
|
|
785
|
+
for (const service of services) {
|
|
786
|
+
const dataName = `${service?.dataParams?.dataName ?? ''}`;
|
|
787
|
+
const declared = DyNTS_App.resolveDeclaredIndexes(service?.dataParams, dataName.includes(archive_util_1.DyNTS_archiveSuffix));
|
|
788
|
+
if (!declared.length) {
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
// The mongoose Model exposes the native driver collection (.indexes / .createIndex).
|
|
792
|
+
const collection = service?.dataModel?.collection;
|
|
793
|
+
if (!collection || typeof collection.createIndex !== 'function') {
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
for (const index of declared) {
|
|
797
|
+
try {
|
|
798
|
+
const action = await DyNTS_App.ensureDeclaredIndex(collection, index);
|
|
799
|
+
if (action === 'created') {
|
|
800
|
+
ensured++;
|
|
801
|
+
if (this.logSetup) {
|
|
802
|
+
fsm_dynamo_1.DyFM_Log.success(`[FR-BFR-041 indexes] declared index "${index.name}" created on "${dataName}"`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
else if (action === 'differs') {
|
|
806
|
+
this.reportSafeDiagnostic('DYNTS-AS0-DECLARED-INDEX-DIFFERS', safe_diagnostic_stage_type_enum_1.DyNTS_SafeDiagnosticStage_Type.databaseRuntime, new Error(`declared-index-differs: ${dataName}.${index.name}`));
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
catch (idxErr) {
|
|
810
|
+
this.reportSafeDiagnostic('DYNTS-AS0-DECLARED-INDEX-FAILED', safe_diagnostic_stage_type_enum_1.DyNTS_SafeDiagnosticStage_Type.databaseRuntime, idxErr);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
if (ensured > 0) {
|
|
815
|
+
fsm_dynamo_1.DyFM_Log.success(`[FR-BFR-041 indexes] ${ensured} declared index(es) ensured.`);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
catch (err) {
|
|
819
|
+
this.reportSafeDiagnostic('DYNTS-AS0-DECLARED-INDEX-INSTALLER-FAILED', safe_diagnostic_stage_type_enum_1.DyNTS_SafeDiagnosticStage_Type.databaseRuntime, err);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* FR-BFR-041 / SR-3 — read and validate the `indexes` declaration of a data-model STRUCTURALLY.
|
|
824
|
+
*
|
|
825
|
+
* Returns only the installable entries (a non-empty `name`, at least one key, every key direction `1` or
|
|
826
|
+
* `-1`, optional `unique` boolean, optional `partialFilterExpression` object). On an archive sibling a
|
|
827
|
+
* `unique` entry is dropped (see {@link DyNTS_App.installDeclaredIndexes}). Pure + side-effect-free.
|
|
828
|
+
*/
|
|
829
|
+
static resolveDeclaredIndexes(dataParams, isArchive) {
|
|
830
|
+
if (!dataParams || typeof dataParams !== 'object') {
|
|
831
|
+
return [];
|
|
832
|
+
}
|
|
833
|
+
const raw = Reflect.get(dataParams, 'indexes');
|
|
834
|
+
if (!Array.isArray(raw)) {
|
|
835
|
+
return [];
|
|
836
|
+
}
|
|
837
|
+
const valid = [];
|
|
838
|
+
for (const entry of raw) {
|
|
839
|
+
if (!entry || typeof entry !== 'object') {
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
const name = Reflect.get(entry, 'name');
|
|
843
|
+
const keys = Reflect.get(entry, 'keys');
|
|
844
|
+
const unique = Reflect.get(entry, 'unique');
|
|
845
|
+
const partial = Reflect.get(entry, 'partialFilterExpression');
|
|
846
|
+
if (typeof name !== 'string' || !name.trim()) {
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
if (!keys || typeof keys !== 'object' || Array.isArray(keys)) {
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
const keyEntries = Object.entries(keys);
|
|
853
|
+
if (!keyEntries.length || keyEntries.some(([, dir]) => dir !== 1 && dir !== -1)) {
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
if (unique !== undefined && typeof unique !== 'boolean') {
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
if (partial !== undefined && (!partial || typeof partial !== 'object' || Array.isArray(partial))) {
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
if (isArchive && unique === true) {
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
const normalizedKeys = {};
|
|
866
|
+
for (const [field, dir] of keyEntries) {
|
|
867
|
+
normalizedKeys[field] = dir === -1 ? -1 : 1;
|
|
868
|
+
}
|
|
869
|
+
const index = { name: name, keys: normalizedKeys };
|
|
870
|
+
// Narrowed here explicitly: the earlier combined guard already rejected non-boolean values, but the
|
|
871
|
+
// compiler does not carry that negative narrowing forward to this assignment.
|
|
872
|
+
if (typeof unique === 'boolean') {
|
|
873
|
+
index.unique = unique;
|
|
874
|
+
}
|
|
875
|
+
if (partial !== undefined) {
|
|
876
|
+
index.partialFilterExpression = Object.fromEntries(Object.entries(partial));
|
|
877
|
+
}
|
|
878
|
+
valid.push(index);
|
|
879
|
+
}
|
|
880
|
+
return valid;
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* FR-BFR-041 / SR-3 — ensure ONE declared index. Matching is by NAME (the stable reconciliation key):
|
|
884
|
+
* - no live index with that name -> create it -> `'created'`
|
|
885
|
+
* - same name, same keys (ORDER matters), same uniqueness
|
|
886
|
+
* and same partial filter -> nothing -> `'noop'`
|
|
887
|
+
* - same name, anything else different -> untouched -> `'differs'` (reported, never rebuilt)
|
|
888
|
+
*/
|
|
889
|
+
static async ensureDeclaredIndex(collection, declared) {
|
|
890
|
+
const liveList = typeof collection.indexes === 'function'
|
|
891
|
+
? await collection.indexes().catch(() => [])
|
|
892
|
+
: [];
|
|
893
|
+
const live = liveList.find((ix) => ix?.name === declared.name);
|
|
894
|
+
if (!live) {
|
|
895
|
+
const options = { name: declared.name };
|
|
896
|
+
if (declared.unique !== undefined) {
|
|
897
|
+
options.unique = declared.unique;
|
|
898
|
+
}
|
|
899
|
+
if (declared.partialFilterExpression !== undefined) {
|
|
900
|
+
options.partialFilterExpression = declared.partialFilterExpression;
|
|
901
|
+
}
|
|
902
|
+
await collection.createIndex(declared.keys, options);
|
|
903
|
+
return 'created';
|
|
904
|
+
}
|
|
905
|
+
// Key ORDER is part of a MongoDB index definition, so the entries are compared in order.
|
|
906
|
+
const sameKeys = JSON.stringify(Object.entries(live.key ?? {})) === JSON.stringify(Object.entries(declared.keys));
|
|
907
|
+
const sameUnique = !!live.unique === !!declared.unique;
|
|
908
|
+
const samePartial = JSON.stringify(live.partialFilterExpression ?? null) === JSON.stringify(declared.partialFilterExpression ?? null);
|
|
909
|
+
return sameKeys && sameUnique && samePartial ? 'noop' : 'differs';
|
|
910
|
+
}
|
|
764
911
|
/**
|
|
765
912
|
*
|
|
766
913
|
*/
|