nestjs-ddd-cli 2.2.1 → 3.2.1
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/README.md +247 -408
- package/ddd.schema.json +111 -0
- package/dist/commands/aggregate-validator.d.ts +9 -0
- package/dist/commands/aggregate-validator.js +953 -0
- package/dist/commands/aggregate-validator.js.map +1 -0
- package/dist/commands/ai-assist.d.ts +8 -0
- package/dist/commands/ai-assist.js +337 -0
- package/dist/commands/ai-assist.js.map +1 -0
- package/dist/commands/api-contracts.d.ts +9 -0
- package/dist/commands/api-contracts.js +1368 -0
- package/dist/commands/api-contracts.js.map +1 -0
- package/dist/commands/api-docs.d.ts +8 -0
- package/dist/commands/api-docs.js +408 -0
- package/dist/commands/api-docs.js.map +1 -0
- package/dist/commands/api-versioning.d.ts +11 -0
- package/dist/commands/api-versioning.js +643 -0
- package/dist/commands/api-versioning.js.map +1 -0
- package/dist/commands/audit-logging.d.ts +9 -0
- package/dist/commands/audit-logging.js +1129 -0
- package/dist/commands/audit-logging.js.map +1 -0
- package/dist/commands/batch-generate.d.ts +10 -0
- package/dist/commands/batch-generate.js +405 -0
- package/dist/commands/batch-generate.js.map +1 -0
- package/dist/commands/caching-strategies.d.ts +9 -0
- package/dist/commands/caching-strategies.js +874 -0
- package/dist/commands/caching-strategies.js.map +1 -0
- package/dist/commands/code-analyzer.d.ts +42 -0
- package/dist/commands/code-analyzer.js +474 -0
- package/dist/commands/code-analyzer.js.map +1 -0
- package/dist/commands/database-seeding.d.ts +6 -0
- package/dist/commands/database-seeding.js +621 -0
- package/dist/commands/database-seeding.js.map +1 -0
- package/dist/commands/db-optimization.d.ts +7 -0
- package/dist/commands/db-optimization.js +687 -0
- package/dist/commands/db-optimization.js.map +1 -0
- package/dist/commands/dependency-graph.d.ts +6 -0
- package/dist/commands/dependency-graph.js +329 -0
- package/dist/commands/dependency-graph.js.map +1 -0
- package/dist/commands/doctor-enhanced.d.ts +22 -0
- package/dist/commands/doctor-enhanced.js +543 -0
- package/dist/commands/doctor-enhanced.js.map +1 -0
- package/dist/commands/doctor.d.ts +4 -0
- package/dist/commands/doctor.js +151 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/env-manager.d.ts +6 -0
- package/dist/commands/env-manager.js +419 -0
- package/dist/commands/env-manager.js.map +1 -0
- package/dist/commands/event-sourcing-full.d.ts +10 -0
- package/dist/commands/event-sourcing-full.js +1107 -0
- package/dist/commands/event-sourcing-full.js.map +1 -0
- package/dist/commands/feature-flags.d.ts +9 -0
- package/dist/commands/feature-flags.js +824 -0
- package/dist/commands/feature-flags.js.map +1 -0
- package/dist/commands/filter-dsl.d.ts +10 -0
- package/dist/commands/filter-dsl.js +1407 -0
- package/dist/commands/filter-dsl.js.map +1 -0
- package/dist/commands/generate-all.js +485 -32
- package/dist/commands/generate-all.js.map +1 -1
- package/dist/commands/generate-deployment.d.ts +8 -0
- package/dist/commands/generate-deployment.js +746 -0
- package/dist/commands/generate-deployment.js.map +1 -0
- package/dist/commands/generate-domain-service.d.ts +14 -0
- package/dist/commands/generate-domain-service.js +796 -0
- package/dist/commands/generate-domain-service.js.map +1 -0
- package/dist/commands/generate-entity.js +82 -24
- package/dist/commands/generate-entity.js.map +1 -1
- package/dist/commands/generate-from-schema.d.ts +56 -0
- package/dist/commands/generate-from-schema.js +222 -0
- package/dist/commands/generate-from-schema.js.map +1 -0
- package/dist/commands/generate-orchestrator.d.ts +14 -0
- package/dist/commands/generate-orchestrator.js +887 -0
- package/dist/commands/generate-orchestrator.js.map +1 -0
- package/dist/commands/generate-repository.d.ts +14 -0
- package/dist/commands/generate-repository.js +1019 -0
- package/dist/commands/generate-repository.js.map +1 -0
- package/dist/commands/generate-shared.d.ts +4 -0
- package/dist/commands/generate-shared.js +388 -0
- package/dist/commands/generate-shared.js.map +1 -0
- package/dist/commands/generate-value-object.d.ts +32 -0
- package/dist/commands/generate-value-object.js +700 -0
- package/dist/commands/generate-value-object.js.map +1 -0
- package/dist/commands/graphql-subscriptions.d.ts +6 -0
- package/dist/commands/graphql-subscriptions.js +607 -0
- package/dist/commands/graphql-subscriptions.js.map +1 -0
- package/dist/commands/graphql-types.d.ts +5 -0
- package/dist/commands/graphql-types.js +423 -0
- package/dist/commands/graphql-types.js.map +1 -0
- package/dist/commands/health-probes-advanced.d.ts +6 -0
- package/dist/commands/health-probes-advanced.js +655 -0
- package/dist/commands/health-probes-advanced.js.map +1 -0
- package/dist/commands/i18n-setup.d.ts +10 -0
- package/dist/commands/i18n-setup.js +677 -0
- package/dist/commands/i18n-setup.js.map +1 -0
- package/dist/commands/init-config.d.ts +6 -0
- package/dist/commands/init-config.js +370 -0
- package/dist/commands/init-config.js.map +1 -0
- package/dist/commands/init-project.js +56 -6
- package/dist/commands/init-project.js.map +1 -1
- package/dist/commands/interactive-scaffold.d.ts +5 -0
- package/dist/commands/interactive-scaffold.js +271 -0
- package/dist/commands/interactive-scaffold.js.map +1 -0
- package/dist/commands/metrics-prometheus.d.ts +6 -0
- package/dist/commands/metrics-prometheus.js +681 -0
- package/dist/commands/metrics-prometheus.js.map +1 -0
- package/dist/commands/migration-engine.d.ts +6 -0
- package/dist/commands/migration-engine.js +446 -0
- package/dist/commands/migration-engine.js.map +1 -0
- package/dist/commands/migration.d.ts +12 -0
- package/dist/commands/migration.js +484 -0
- package/dist/commands/migration.js.map +1 -0
- package/dist/commands/monorepo.d.ts +8 -0
- package/dist/commands/monorepo.js +483 -0
- package/dist/commands/monorepo.js.map +1 -0
- package/dist/commands/multi-database.d.ts +5 -0
- package/dist/commands/multi-database.js +439 -0
- package/dist/commands/multi-database.js.map +1 -0
- package/dist/commands/observability-tracing.d.ts +10 -0
- package/dist/commands/observability-tracing.js +740 -0
- package/dist/commands/observability-tracing.js.map +1 -0
- package/dist/commands/openapi-export.d.ts +8 -0
- package/dist/commands/openapi-export.js +359 -0
- package/dist/commands/openapi-export.js.map +1 -0
- package/dist/commands/perf-analyzer.d.ts +8 -0
- package/dist/commands/perf-analyzer.js +423 -0
- package/dist/commands/perf-analyzer.js.map +1 -0
- package/dist/commands/rate-limiting.d.ts +10 -0
- package/dist/commands/rate-limiting.js +953 -0
- package/dist/commands/rate-limiting.js.map +1 -0
- package/dist/commands/recipe-plugin.d.ts +56 -0
- package/dist/commands/recipe-plugin.js +315 -0
- package/dist/commands/recipe-plugin.js.map +1 -0
- package/dist/commands/recipe.d.ts +6 -0
- package/dist/commands/recipe.js +3941 -0
- package/dist/commands/recipe.js.map +1 -0
- package/dist/commands/recipes/elasticsearch.recipe.d.ts +1 -0
- package/dist/commands/recipes/elasticsearch.recipe.js +761 -0
- package/dist/commands/recipes/elasticsearch.recipe.js.map +1 -0
- package/dist/commands/recipes/event-sourcing.recipe.d.ts +1 -0
- package/dist/commands/recipes/event-sourcing.recipe.js +889 -0
- package/dist/commands/recipes/event-sourcing.recipe.js.map +1 -0
- package/dist/commands/recipes/index.d.ts +7 -0
- package/dist/commands/recipes/index.js +24 -0
- package/dist/commands/recipes/index.js.map +1 -0
- package/dist/commands/recipes/message-queue.recipe.d.ts +1 -0
- package/dist/commands/recipes/message-queue.recipe.js +706 -0
- package/dist/commands/recipes/message-queue.recipe.js.map +1 -0
- package/dist/commands/recipes/middleware.recipe.d.ts +1 -0
- package/dist/commands/recipes/middleware.recipe.js +383 -0
- package/dist/commands/recipes/middleware.recipe.js.map +1 -0
- package/dist/commands/recipes/multi-tenancy.recipe.d.ts +1 -0
- package/dist/commands/recipes/multi-tenancy.recipe.js +520 -0
- package/dist/commands/recipes/multi-tenancy.recipe.js.map +1 -0
- package/dist/commands/recipes/oauth2.recipe.d.ts +1 -0
- package/dist/commands/recipes/oauth2.recipe.js +472 -0
- package/dist/commands/recipes/oauth2.recipe.js.map +1 -0
- package/dist/commands/recipes/websocket.recipe.d.ts +1 -0
- package/dist/commands/recipes/websocket.recipe.js +453 -0
- package/dist/commands/recipes/websocket.recipe.js.map +1 -0
- package/dist/commands/resilience-patterns.d.ts +13 -0
- package/dist/commands/resilience-patterns.js +1029 -0
- package/dist/commands/resilience-patterns.js.map +1 -0
- package/dist/commands/security-patterns.d.ts +11 -0
- package/dist/commands/security-patterns.js +2233 -0
- package/dist/commands/security-patterns.js.map +1 -0
- package/dist/commands/template-debug.d.ts +27 -0
- package/dist/commands/template-debug.js +388 -0
- package/dist/commands/template-debug.js.map +1 -0
- package/dist/commands/test-factory-full.d.ts +9 -0
- package/dist/commands/test-factory-full.js +1570 -0
- package/dist/commands/test-factory-full.js.map +1 -0
- package/dist/commands/test-scaffold.d.ts +7 -0
- package/dist/commands/test-scaffold.js +621 -0
- package/dist/commands/test-scaffold.js.map +1 -0
- package/dist/index.js +1088 -0
- package/dist/index.js.map +1 -1
- package/dist/templates/ai-context/CLAUDE.md.hbs +158 -0
- package/dist/templates/ai-context/conventions.md.hbs +154 -0
- package/dist/templates/command/create-command.hbs +6 -14
- package/dist/templates/command/delete-command.hbs +19 -0
- package/dist/templates/command/update-command.hbs +24 -0
- package/dist/templates/controller/controller.hbs +64 -17
- package/dist/templates/dto/create-dto.hbs +29 -5
- package/dist/templates/dto/filter-dto.hbs +52 -0
- package/dist/templates/dto/filter-query.dto.hbs +148 -0
- package/dist/templates/dto/paginated-response.dto.hbs +29 -0
- package/dist/templates/dto/pagination-query.dto.hbs +30 -0
- package/dist/templates/dto/response-dto.hbs +38 -0
- package/dist/templates/dto/update-dto.hbs +11 -0
- package/dist/templates/entity/entity.hbs +32 -1
- package/dist/templates/event/domain-event.hbs +33 -7
- package/dist/templates/event/event-handler.hbs +40 -0
- package/dist/templates/exception/base-exceptions.hbs +69 -0
- package/dist/templates/exception/entity-not-found.exception.hbs +7 -0
- package/dist/templates/mapper/mapper.hbs +49 -24
- package/dist/templates/module/module.hbs +34 -10
- package/dist/templates/orm-entity/orm-entity.hbs +63 -12
- package/dist/templates/prisma/prisma-mapper.hbs +71 -0
- package/dist/templates/prisma/prisma-repository.hbs +114 -0
- package/dist/templates/prisma/prisma-schema.hbs +20 -0
- package/dist/templates/prisma/prisma-service.hbs +51 -0
- package/dist/templates/query/get-all.query.hbs +50 -0
- package/dist/templates/query/get-by-id.query.hbs +31 -0
- package/dist/templates/repository/repository.hbs +55 -13
- package/dist/templates/resolver/graphql-input.hbs +54 -0
- package/dist/templates/resolver/graphql-type.hbs +58 -0
- package/dist/templates/resolver/pagination-args.hbs +33 -0
- package/dist/templates/resolver/resolver.hbs +62 -0
- package/dist/templates/shared/prisma-query-builder.util.hbs +189 -0
- package/dist/templates/shared/query-builder.util.hbs +218 -0
- package/dist/templates/test/controller.spec.hbs +124 -0
- package/dist/templates/test/repository.spec.hbs +158 -0
- package/dist/templates/test/usecase.spec.hbs +116 -0
- package/dist/templates/usecase/create-usecase.hbs +19 -7
- package/dist/templates/usecase/delete-usecase.hbs +17 -0
- package/dist/templates/usecase/update-usecase.hbs +31 -0
- package/dist/utils/config.utils.d.ts +45 -0
- package/dist/utils/config.utils.js +211 -0
- package/dist/utils/config.utils.js.map +1 -0
- package/dist/utils/error.utils.d.ts +145 -0
- package/dist/utils/error.utils.js +422 -0
- package/dist/utils/error.utils.js.map +1 -0
- package/dist/utils/field.utils.d.ts +54 -0
- package/dist/utils/field.utils.js +389 -0
- package/dist/utils/field.utils.js.map +1 -0
- package/dist/utils/file.utils.d.ts +19 -8
- package/dist/utils/file.utils.js +135 -4
- package/dist/utils/file.utils.js.map +1 -1
- package/dist/utils/idempotency.utils.d.ts +123 -0
- package/dist/utils/idempotency.utils.js +444 -0
- package/dist/utils/idempotency.utils.js.map +1 -0
- package/dist/utils/naming.utils.js +24 -5
- package/dist/utils/naming.utils.js.map +1 -1
- package/dist/utils/performance.utils.d.ts +37 -0
- package/dist/utils/performance.utils.js +158 -0
- package/dist/utils/performance.utils.js.map +1 -0
- package/dist/utils/relation.utils.d.ts +92 -0
- package/dist/utils/relation.utils.js +388 -0
- package/dist/utils/relation.utils.js.map +1 -0
- package/dist/utils/rollback.utils.d.ts +49 -0
- package/dist/utils/rollback.utils.js +306 -0
- package/dist/utils/rollback.utils.js.map +1 -0
- package/dist/utils/schema.utils.d.ts +123 -0
- package/dist/utils/schema.utils.js +419 -0
- package/dist/utils/schema.utils.js.map +1 -0
- package/dist/utils/security.utils.d.ts +57 -0
- package/dist/utils/security.utils.js +315 -0
- package/dist/utils/security.utils.js.map +1 -0
- package/dist/utils/template-engine.utils.d.ts +80 -0
- package/dist/utils/template-engine.utils.js +463 -0
- package/dist/utils/template-engine.utils.js.map +1 -0
- package/dist/utils/validation-registry.utils.d.ts +160 -0
- package/dist/utils/validation-registry.utils.js +526 -0
- package/dist/utils/validation-registry.utils.js.map +1 -0
- package/package.json +3 -1
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.setupDbOptimization = setupDbOptimization;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
43
|
+
const naming_utils_1 = require("../utils/naming.utils");
|
|
44
|
+
async function setupDbOptimization(basePath, options = {}) {
|
|
45
|
+
console.log(chalk_1.default.bold.blue('\n🗄️ Setting up Database Optimization\n'));
|
|
46
|
+
const moduleName = options.module || 'shared';
|
|
47
|
+
const pascalName = (0, naming_utils_1.toPascalCase)(moduleName);
|
|
48
|
+
const camelName = (0, naming_utils_1.toCamelCase)(moduleName);
|
|
49
|
+
const kebabName = (0, naming_utils_1.toKebabCase)(moduleName);
|
|
50
|
+
const orm = options.orm || 'typeorm';
|
|
51
|
+
const includeDataLoader = options.includeDataLoader !== false;
|
|
52
|
+
const includeQueryAnalyzer = options.includeQueryAnalyzer !== false;
|
|
53
|
+
const baseDir = path.join(basePath, 'src', kebabName, 'infrastructure', 'database');
|
|
54
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
55
|
+
// DataLoader for N+1 prevention
|
|
56
|
+
if (includeDataLoader) {
|
|
57
|
+
const dataLoaderContent = `import { Injectable, Scope } from '@nestjs/common';
|
|
58
|
+
import DataLoader from 'dataloader';
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Generic DataLoader factory for preventing N+1 queries
|
|
62
|
+
* Creates request-scoped DataLoader instances
|
|
63
|
+
*/
|
|
64
|
+
@Injectable({ scope: Scope.REQUEST })
|
|
65
|
+
export class DataLoaderFactory {
|
|
66
|
+
private loaders: Map<string, DataLoader<any, any>> = new Map();
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get or create a DataLoader for the specified key
|
|
70
|
+
*/
|
|
71
|
+
getLoader<K, V>(
|
|
72
|
+
key: string,
|
|
73
|
+
batchFn: (keys: readonly K[]) => Promise<(V | Error)[]>,
|
|
74
|
+
options?: DataLoader.Options<K, V>,
|
|
75
|
+
): DataLoader<K, V> {
|
|
76
|
+
if (!this.loaders.has(key)) {
|
|
77
|
+
this.loaders.set(
|
|
78
|
+
key,
|
|
79
|
+
new DataLoader<K, V>(batchFn, {
|
|
80
|
+
cache: true,
|
|
81
|
+
maxBatchSize: 100,
|
|
82
|
+
...options,
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return this.loaders.get(key) as DataLoader<K, V>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Clear all loaders (useful for mutations)
|
|
91
|
+
*/
|
|
92
|
+
clearAll(): void {
|
|
93
|
+
this.loaders.forEach((loader) => loader.clearAll());
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Clear specific loader
|
|
98
|
+
*/
|
|
99
|
+
clear(key: string): void {
|
|
100
|
+
const loader = this.loaders.get(key);
|
|
101
|
+
if (loader) {
|
|
102
|
+
loader.clearAll();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* DataLoader decorator for automatic batching
|
|
109
|
+
*/
|
|
110
|
+
export function BatchLoad(loaderKey: string): MethodDecorator {
|
|
111
|
+
return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
112
|
+
const originalMethod = descriptor.value;
|
|
113
|
+
|
|
114
|
+
descriptor.value = async function (...args: any[]) {
|
|
115
|
+
const dataLoaderFactory = (this as any).dataLoaderFactory as DataLoaderFactory;
|
|
116
|
+
|
|
117
|
+
if (!dataLoaderFactory) {
|
|
118
|
+
// Fallback to original method if no DataLoader available
|
|
119
|
+
return originalMethod.apply(this, args);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const loader = dataLoaderFactory.getLoader(
|
|
123
|
+
loaderKey,
|
|
124
|
+
async (keys: readonly any[]) => {
|
|
125
|
+
// Call original method with all keys
|
|
126
|
+
return originalMethod.call(this, keys);
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// If single ID, use load; if array, use loadMany
|
|
131
|
+
const id = args[0];
|
|
132
|
+
if (Array.isArray(id)) {
|
|
133
|
+
return loader.loadMany(id);
|
|
134
|
+
}
|
|
135
|
+
return loader.load(id);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return descriptor;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* ${pascalName} specific DataLoader service
|
|
144
|
+
*/
|
|
145
|
+
@Injectable({ scope: Scope.REQUEST })
|
|
146
|
+
export class ${pascalName}DataLoader {
|
|
147
|
+
constructor(
|
|
148
|
+
private readonly dataLoaderFactory: DataLoaderFactory,
|
|
149
|
+
// Inject your repository here
|
|
150
|
+
// private readonly ${camelName}Repository: ${pascalName}Repository,
|
|
151
|
+
) {}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Batch load ${camelName} by IDs
|
|
155
|
+
*/
|
|
156
|
+
async loadById(id: string): Promise<any> {
|
|
157
|
+
const loader = this.dataLoaderFactory.getLoader<string, any>(
|
|
158
|
+
'${camelName}ById',
|
|
159
|
+
async (ids: readonly string[]) => {
|
|
160
|
+
// Replace with actual repository call
|
|
161
|
+
// const entities = await this.${camelName}Repository.findByIds([...ids]);
|
|
162
|
+
// return ids.map(id => entities.find(e => e.id === id) || new Error(\`${pascalName} \${id} not found\`));
|
|
163
|
+
return ids.map(() => null);
|
|
164
|
+
},
|
|
165
|
+
);
|
|
166
|
+
return loader.load(id);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Batch load ${camelName} by foreign key
|
|
171
|
+
*/
|
|
172
|
+
async loadByForeignKey(foreignKeyId: string): Promise<any[]> {
|
|
173
|
+
const loader = this.dataLoaderFactory.getLoader<string, any[]>(
|
|
174
|
+
'${camelName}ByForeignKey',
|
|
175
|
+
async (foreignKeyIds: readonly string[]) => {
|
|
176
|
+
// Replace with actual repository call
|
|
177
|
+
// const entities = await this.${camelName}Repository.findByForeignKeys([...foreignKeyIds]);
|
|
178
|
+
// return foreignKeyIds.map(fkId => entities.filter(e => e.foreignKeyId === fkId));
|
|
179
|
+
return foreignKeyIds.map(() => []);
|
|
180
|
+
},
|
|
181
|
+
);
|
|
182
|
+
return loader.load(foreignKeyId);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Prime cache with known data
|
|
187
|
+
*/
|
|
188
|
+
prime(id: string, data: any): void {
|
|
189
|
+
const loader = this.dataLoaderFactory.getLoader<string, any>(
|
|
190
|
+
'${camelName}ById',
|
|
191
|
+
async () => [],
|
|
192
|
+
);
|
|
193
|
+
loader.prime(id, data);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Clear cache after mutations
|
|
198
|
+
*/
|
|
199
|
+
clearCache(id?: string): void {
|
|
200
|
+
if (id) {
|
|
201
|
+
const loader = this.dataLoaderFactory.getLoader<string, any>(
|
|
202
|
+
'${camelName}ById',
|
|
203
|
+
async () => [],
|
|
204
|
+
);
|
|
205
|
+
loader.clear(id);
|
|
206
|
+
} else {
|
|
207
|
+
this.dataLoaderFactory.clear('${camelName}ById');
|
|
208
|
+
this.dataLoaderFactory.clear('${camelName}ByForeignKey');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
`;
|
|
213
|
+
fs.writeFileSync(path.join(baseDir, 'data-loader.ts'), dataLoaderContent);
|
|
214
|
+
}
|
|
215
|
+
// Query Analyzer
|
|
216
|
+
if (includeQueryAnalyzer) {
|
|
217
|
+
const queryAnalyzerContent = `import { Injectable, Logger } from '@nestjs/common';
|
|
218
|
+
${orm === 'typeorm' ? "import { DataSource, QueryRunner } from 'typeorm';" : ''}
|
|
219
|
+
|
|
220
|
+
interface QueryMetrics {
|
|
221
|
+
query: string;
|
|
222
|
+
duration: number;
|
|
223
|
+
rowsAffected?: number;
|
|
224
|
+
timestamp: Date;
|
|
225
|
+
explain?: ExplainResult;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
interface ExplainResult {
|
|
229
|
+
type: string;
|
|
230
|
+
possibleKeys?: string[];
|
|
231
|
+
key?: string;
|
|
232
|
+
rows?: number;
|
|
233
|
+
filtered?: number;
|
|
234
|
+
extra?: string;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
interface SlowQueryReport {
|
|
238
|
+
query: string;
|
|
239
|
+
avgDuration: number;
|
|
240
|
+
maxDuration: number;
|
|
241
|
+
count: number;
|
|
242
|
+
suggestion: string;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
interface IndexSuggestion {
|
|
246
|
+
table: string;
|
|
247
|
+
columns: string[];
|
|
248
|
+
reason: string;
|
|
249
|
+
impact: 'high' | 'medium' | 'low';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Query performance analyzer for identifying slow queries and optimization opportunities
|
|
254
|
+
*/
|
|
255
|
+
@Injectable()
|
|
256
|
+
export class QueryAnalyzerService {
|
|
257
|
+
private readonly logger = new Logger(QueryAnalyzerService.name);
|
|
258
|
+
private readonly queryMetrics: Map<string, QueryMetrics[]> = new Map();
|
|
259
|
+
private readonly slowQueryThreshold: number;
|
|
260
|
+
private readonly maxStoredQueries: number;
|
|
261
|
+
|
|
262
|
+
constructor(
|
|
263
|
+
${orm === 'typeorm' ? ' private readonly dataSource: DataSource,' : ''}
|
|
264
|
+
) {
|
|
265
|
+
this.slowQueryThreshold = parseInt(process.env.SLOW_QUERY_THRESHOLD_MS || '100', 10);
|
|
266
|
+
this.maxStoredQueries = parseInt(process.env.MAX_STORED_QUERIES || '1000', 10);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Record query execution metrics
|
|
271
|
+
*/
|
|
272
|
+
recordQuery(query: string, duration: number, rowsAffected?: number): void {
|
|
273
|
+
const normalizedQuery = this.normalizeQuery(query);
|
|
274
|
+
|
|
275
|
+
if (!this.queryMetrics.has(normalizedQuery)) {
|
|
276
|
+
this.queryMetrics.set(normalizedQuery, []);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const metrics = this.queryMetrics.get(normalizedQuery)!;
|
|
280
|
+
metrics.push({
|
|
281
|
+
query,
|
|
282
|
+
duration,
|
|
283
|
+
rowsAffected,
|
|
284
|
+
timestamp: new Date(),
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Keep only recent queries
|
|
288
|
+
if (metrics.length > this.maxStoredQueries) {
|
|
289
|
+
metrics.shift();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Log slow queries
|
|
293
|
+
if (duration > this.slowQueryThreshold) {
|
|
294
|
+
this.logger.warn(\`Slow query detected (\${duration}ms): \${query.substring(0, 200)}...\`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Normalize query for grouping (remove specific values)
|
|
300
|
+
*/
|
|
301
|
+
private normalizeQuery(query: string): string {
|
|
302
|
+
return query
|
|
303
|
+
.replace(/= \\d+/g, '= ?')
|
|
304
|
+
.replace(/= '[^']*'/g, "= '?'")
|
|
305
|
+
.replace(/IN \\([^)]+\\)/gi, 'IN (?)')
|
|
306
|
+
.replace(/LIMIT \\d+/gi, 'LIMIT ?')
|
|
307
|
+
.replace(/OFFSET \\d+/gi, 'OFFSET ?')
|
|
308
|
+
.trim();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Get slow query report
|
|
313
|
+
*/
|
|
314
|
+
getSlowQueryReport(thresholdMs?: number): SlowQueryReport[] {
|
|
315
|
+
const threshold = thresholdMs || this.slowQueryThreshold;
|
|
316
|
+
const reports: SlowQueryReport[] = [];
|
|
317
|
+
|
|
318
|
+
this.queryMetrics.forEach((metrics, normalizedQuery) => {
|
|
319
|
+
const slowQueries = metrics.filter((m) => m.duration > threshold);
|
|
320
|
+
|
|
321
|
+
if (slowQueries.length > 0) {
|
|
322
|
+
const durations = slowQueries.map((m) => m.duration);
|
|
323
|
+
const avgDuration = durations.reduce((a, b) => a + b, 0) / durations.length;
|
|
324
|
+
const maxDuration = Math.max(...durations);
|
|
325
|
+
|
|
326
|
+
reports.push({
|
|
327
|
+
query: normalizedQuery,
|
|
328
|
+
avgDuration: Math.round(avgDuration),
|
|
329
|
+
maxDuration,
|
|
330
|
+
count: slowQueries.length,
|
|
331
|
+
suggestion: this.generateSuggestion(normalizedQuery, avgDuration),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
return reports.sort((a, b) => b.avgDuration - a.avgDuration);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Generate optimization suggestion for a query
|
|
341
|
+
*/
|
|
342
|
+
private generateSuggestion(query: string, avgDuration: number): string {
|
|
343
|
+
const suggestions: string[] = [];
|
|
344
|
+
const upperQuery = query.toUpperCase();
|
|
345
|
+
|
|
346
|
+
// Check for SELECT *
|
|
347
|
+
if (upperQuery.includes('SELECT *')) {
|
|
348
|
+
suggestions.push('Avoid SELECT *, specify only needed columns');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Check for missing WHERE clause
|
|
352
|
+
if (!upperQuery.includes('WHERE') && (upperQuery.includes('UPDATE') || upperQuery.includes('DELETE'))) {
|
|
353
|
+
suggestions.push('Missing WHERE clause - full table scan');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Check for LIKE with leading wildcard
|
|
357
|
+
if (upperQuery.includes("LIKE '%") || upperQuery.includes("LIKE '?%")) {
|
|
358
|
+
suggestions.push('Leading wildcard in LIKE prevents index usage');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Check for OR conditions
|
|
362
|
+
if (upperQuery.includes(' OR ')) {
|
|
363
|
+
suggestions.push('Consider using UNION instead of OR for better index usage');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Check for NOT IN
|
|
367
|
+
if (upperQuery.includes('NOT IN')) {
|
|
368
|
+
suggestions.push('Consider using NOT EXISTS instead of NOT IN');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Check for functions on indexed columns
|
|
372
|
+
if (/WHERE\\s+\\w+\\s*\\(/.test(query)) {
|
|
373
|
+
suggestions.push('Functions on columns prevent index usage');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Check for ORDER BY without LIMIT
|
|
377
|
+
if (upperQuery.includes('ORDER BY') && !upperQuery.includes('LIMIT')) {
|
|
378
|
+
suggestions.push('ORDER BY without LIMIT may cause full result set sorting');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Duration-based suggestions
|
|
382
|
+
if (avgDuration > 1000) {
|
|
383
|
+
suggestions.push('Consider query restructuring or adding composite index');
|
|
384
|
+
} else if (avgDuration > 500) {
|
|
385
|
+
suggestions.push('Review indexes on filtered/joined columns');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return suggestions.length > 0 ? suggestions.join('; ') : 'Profile query execution plan for detailed analysis';
|
|
389
|
+
}
|
|
390
|
+
${orm === 'typeorm' ? `
|
|
391
|
+
/**
|
|
392
|
+
* Analyze query with EXPLAIN
|
|
393
|
+
*/
|
|
394
|
+
async explainQuery(query: string): Promise<ExplainResult | null> {
|
|
395
|
+
try {
|
|
396
|
+
const result = await this.dataSource.query(\`EXPLAIN \${query}\`);
|
|
397
|
+
if (result && result.length > 0) {
|
|
398
|
+
return {
|
|
399
|
+
type: result[0].type || result[0].select_type,
|
|
400
|
+
possibleKeys: result[0].possible_keys?.split(','),
|
|
401
|
+
key: result[0].key,
|
|
402
|
+
rows: result[0].rows,
|
|
403
|
+
filtered: result[0].filtered,
|
|
404
|
+
extra: result[0].Extra,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
} catch (error) {
|
|
409
|
+
this.logger.error(\`Failed to explain query: \${error}\`);
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
` : ''}
|
|
414
|
+
/**
|
|
415
|
+
* Suggest indexes based on query patterns
|
|
416
|
+
*/
|
|
417
|
+
suggestIndexes(): IndexSuggestion[] {
|
|
418
|
+
const suggestions: IndexSuggestion[] = [];
|
|
419
|
+
const tableColumns: Map<string, Set<string>> = new Map();
|
|
420
|
+
|
|
421
|
+
// Analyze WHERE and JOIN clauses
|
|
422
|
+
this.queryMetrics.forEach((metrics, normalizedQuery) => {
|
|
423
|
+
const avgDuration = metrics.reduce((a, m) => a + m.duration, 0) / metrics.length;
|
|
424
|
+
|
|
425
|
+
if (avgDuration > this.slowQueryThreshold) {
|
|
426
|
+
// Extract table and column from WHERE clauses
|
|
427
|
+
const whereMatches = normalizedQuery.matchAll(/FROM\\s+(\\w+).*?WHERE.*?(\\w+)\\s*=/gi);
|
|
428
|
+
for (const match of whereMatches) {
|
|
429
|
+
const table = match[1];
|
|
430
|
+
const column = match[2];
|
|
431
|
+
|
|
432
|
+
if (!tableColumns.has(table)) {
|
|
433
|
+
tableColumns.set(table, new Set());
|
|
434
|
+
}
|
|
435
|
+
tableColumns.get(table)!.add(column);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Extract JOIN conditions
|
|
439
|
+
const joinMatches = normalizedQuery.matchAll(/JOIN\\s+(\\w+).*?ON.*?(\\w+)\\s*=/gi);
|
|
440
|
+
for (const match of joinMatches) {
|
|
441
|
+
const table = match[1];
|
|
442
|
+
const column = match[2];
|
|
443
|
+
|
|
444
|
+
if (!tableColumns.has(table)) {
|
|
445
|
+
tableColumns.set(table, new Set());
|
|
446
|
+
}
|
|
447
|
+
tableColumns.get(table)!.add(column);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// Generate index suggestions
|
|
453
|
+
tableColumns.forEach((columns, table) => {
|
|
454
|
+
if (columns.size > 0) {
|
|
455
|
+
suggestions.push({
|
|
456
|
+
table,
|
|
457
|
+
columns: Array.from(columns),
|
|
458
|
+
reason: \`Frequently used in WHERE/JOIN conditions on slow queries\`,
|
|
459
|
+
impact: columns.size > 2 ? 'high' : columns.size > 1 ? 'medium' : 'low',
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
return suggestions.sort((a, b) => {
|
|
465
|
+
const impactOrder = { high: 0, medium: 1, low: 2 };
|
|
466
|
+
return impactOrder[a.impact] - impactOrder[b.impact];
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Get query statistics
|
|
472
|
+
*/
|
|
473
|
+
getStatistics(): {
|
|
474
|
+
totalQueries: number;
|
|
475
|
+
uniqueQueries: number;
|
|
476
|
+
slowQueries: number;
|
|
477
|
+
avgDuration: number;
|
|
478
|
+
} {
|
|
479
|
+
let totalQueries = 0;
|
|
480
|
+
let slowQueries = 0;
|
|
481
|
+
let totalDuration = 0;
|
|
482
|
+
|
|
483
|
+
this.queryMetrics.forEach((metrics) => {
|
|
484
|
+
totalQueries += metrics.length;
|
|
485
|
+
metrics.forEach((m) => {
|
|
486
|
+
totalDuration += m.duration;
|
|
487
|
+
if (m.duration > this.slowQueryThreshold) {
|
|
488
|
+
slowQueries++;
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
totalQueries,
|
|
495
|
+
uniqueQueries: this.queryMetrics.size,
|
|
496
|
+
slowQueries,
|
|
497
|
+
avgDuration: totalQueries > 0 ? Math.round(totalDuration / totalQueries) : 0,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Clear collected metrics
|
|
503
|
+
*/
|
|
504
|
+
clearMetrics(): void {
|
|
505
|
+
this.queryMetrics.clear();
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
`;
|
|
509
|
+
fs.writeFileSync(path.join(baseDir, 'query-analyzer.ts'), queryAnalyzerContent);
|
|
510
|
+
// Query interceptor for automatic tracking
|
|
511
|
+
const queryInterceptorContent = `import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
|
512
|
+
import { Observable } from 'rxjs';
|
|
513
|
+
import { tap } from 'rxjs/operators';
|
|
514
|
+
import { QueryAnalyzerService } from './query-analyzer';
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Interceptor to automatically track query execution times
|
|
518
|
+
*/
|
|
519
|
+
@Injectable()
|
|
520
|
+
export class QueryTrackingInterceptor implements NestInterceptor {
|
|
521
|
+
constructor(private readonly queryAnalyzer: QueryAnalyzerService) {}
|
|
522
|
+
|
|
523
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
|
524
|
+
const startTime = Date.now();
|
|
525
|
+
|
|
526
|
+
return next.handle().pipe(
|
|
527
|
+
tap({
|
|
528
|
+
next: () => {
|
|
529
|
+
const duration = Date.now() - startTime;
|
|
530
|
+
const request = context.switchToHttp().getRequest();
|
|
531
|
+
const query = \`\${request.method} \${request.url}\`;
|
|
532
|
+
|
|
533
|
+
// Track as a pseudo-query for endpoint performance
|
|
534
|
+
this.queryAnalyzer.recordQuery(query, duration);
|
|
535
|
+
},
|
|
536
|
+
error: () => {
|
|
537
|
+
const duration = Date.now() - startTime;
|
|
538
|
+
const request = context.switchToHttp().getRequest();
|
|
539
|
+
const query = \`\${request.method} \${request.url} [ERROR]\`;
|
|
540
|
+
|
|
541
|
+
this.queryAnalyzer.recordQuery(query, duration);
|
|
542
|
+
},
|
|
543
|
+
}),
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
`;
|
|
548
|
+
fs.writeFileSync(path.join(baseDir, 'query-tracking.interceptor.ts'), queryInterceptorContent);
|
|
549
|
+
}
|
|
550
|
+
// Connection pool optimizer
|
|
551
|
+
const connectionPoolContent = `import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
|
552
|
+
|
|
553
|
+
interface PoolConfig {
|
|
554
|
+
min: number;
|
|
555
|
+
max: number;
|
|
556
|
+
acquireTimeoutMs: number;
|
|
557
|
+
idleTimeoutMs: number;
|
|
558
|
+
reapIntervalMs: number;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
interface PoolMetrics {
|
|
562
|
+
totalConnections: number;
|
|
563
|
+
activeConnections: number;
|
|
564
|
+
idleConnections: number;
|
|
565
|
+
waitingRequests: number;
|
|
566
|
+
avgAcquireTime: number;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Connection pool optimizer with adaptive sizing
|
|
571
|
+
*/
|
|
572
|
+
@Injectable()
|
|
573
|
+
export class ConnectionPoolOptimizer implements OnModuleInit {
|
|
574
|
+
private readonly logger = new Logger(ConnectionPoolOptimizer.name);
|
|
575
|
+
private metrics: PoolMetrics = {
|
|
576
|
+
totalConnections: 0,
|
|
577
|
+
activeConnections: 0,
|
|
578
|
+
idleConnections: 0,
|
|
579
|
+
waitingRequests: 0,
|
|
580
|
+
avgAcquireTime: 0,
|
|
581
|
+
};
|
|
582
|
+
private acquireTimes: number[] = [];
|
|
583
|
+
private readonly maxAcquireTimeSamples = 100;
|
|
584
|
+
|
|
585
|
+
async onModuleInit(): Promise<void> {
|
|
586
|
+
this.logger.log('Connection pool optimizer initialized');
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Get optimal pool configuration based on workload
|
|
591
|
+
*/
|
|
592
|
+
getOptimalConfig(currentConfig: PoolConfig, cpuCount: number = 4): PoolConfig {
|
|
593
|
+
// Formula: connections = (core_count * 2) + effective_spindle_count
|
|
594
|
+
// For SSDs, effective_spindle_count is typically 1
|
|
595
|
+
const optimalMax = Math.min(cpuCount * 2 + 1, 20);
|
|
596
|
+
const optimalMin = Math.max(Math.floor(optimalMax / 4), 2);
|
|
597
|
+
|
|
598
|
+
return {
|
|
599
|
+
min: optimalMin,
|
|
600
|
+
max: optimalMax,
|
|
601
|
+
acquireTimeoutMs: Math.max(currentConfig.acquireTimeoutMs, 10000),
|
|
602
|
+
idleTimeoutMs: currentConfig.idleTimeoutMs || 30000,
|
|
603
|
+
reapIntervalMs: currentConfig.reapIntervalMs || 1000,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Record connection acquire time
|
|
609
|
+
*/
|
|
610
|
+
recordAcquireTime(timeMs: number): void {
|
|
611
|
+
this.acquireTimes.push(timeMs);
|
|
612
|
+
|
|
613
|
+
if (this.acquireTimes.length > this.maxAcquireTimeSamples) {
|
|
614
|
+
this.acquireTimes.shift();
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
this.metrics.avgAcquireTime =
|
|
618
|
+
this.acquireTimes.reduce((a, b) => a + b, 0) / this.acquireTimes.length;
|
|
619
|
+
|
|
620
|
+
// Warn if acquire time is high
|
|
621
|
+
if (timeMs > 1000) {
|
|
622
|
+
this.logger.warn(\`High connection acquire time: \${timeMs}ms. Consider increasing pool size.\`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Update pool metrics
|
|
628
|
+
*/
|
|
629
|
+
updateMetrics(metrics: Partial<PoolMetrics>): void {
|
|
630
|
+
this.metrics = { ...this.metrics, ...metrics };
|
|
631
|
+
|
|
632
|
+
// Log warnings based on metrics
|
|
633
|
+
if (this.metrics.waitingRequests > 0) {
|
|
634
|
+
this.logger.warn(\`\${this.metrics.waitingRequests} requests waiting for connections\`);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (this.metrics.activeConnections === this.metrics.totalConnections) {
|
|
638
|
+
this.logger.warn('Connection pool exhausted - all connections in use');
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Get current metrics
|
|
644
|
+
*/
|
|
645
|
+
getMetrics(): PoolMetrics {
|
|
646
|
+
return { ...this.metrics };
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Should pool be scaled up?
|
|
651
|
+
*/
|
|
652
|
+
shouldScaleUp(currentMax: number): boolean {
|
|
653
|
+
const utilizationRatio = this.metrics.activeConnections / this.metrics.totalConnections;
|
|
654
|
+
return (
|
|
655
|
+
utilizationRatio > 0.8 ||
|
|
656
|
+
this.metrics.waitingRequests > 0 ||
|
|
657
|
+
this.metrics.avgAcquireTime > 500
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Should pool be scaled down?
|
|
663
|
+
*/
|
|
664
|
+
shouldScaleDown(currentMin: number): boolean {
|
|
665
|
+
const utilizationRatio = this.metrics.activeConnections / this.metrics.totalConnections;
|
|
666
|
+
return (
|
|
667
|
+
utilizationRatio < 0.2 &&
|
|
668
|
+
this.metrics.waitingRequests === 0 &&
|
|
669
|
+
this.metrics.idleConnections > currentMin
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
`;
|
|
674
|
+
fs.writeFileSync(path.join(baseDir, 'connection-pool-optimizer.ts'), connectionPoolContent);
|
|
675
|
+
console.log(chalk_1.default.green(` ✓ Created connection pool optimizer`));
|
|
676
|
+
console.log(chalk_1.default.bold.green(`\n✅ Database optimization setup complete for ${pascalName}`));
|
|
677
|
+
console.log(chalk_1.default.cyan(`Generated files in: ${baseDir}`));
|
|
678
|
+
if (includeDataLoader) {
|
|
679
|
+
console.log(chalk_1.default.gray(' - data-loader.ts (DataLoader factory and decorators)'));
|
|
680
|
+
}
|
|
681
|
+
if (includeQueryAnalyzer) {
|
|
682
|
+
console.log(chalk_1.default.gray(' - query-analyzer.ts (Query performance analysis)'));
|
|
683
|
+
console.log(chalk_1.default.gray(' - query-tracking.interceptor.ts (Automatic query tracking)'));
|
|
684
|
+
}
|
|
685
|
+
console.log(chalk_1.default.gray(' - connection-pool-optimizer.ts (Adaptive pool sizing)'));
|
|
686
|
+
}
|
|
687
|
+
//# sourceMappingURL=db-optimization.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-optimization.js","sourceRoot":"","sources":["../../src/commands/db-optimization.ts"],"names":[],"mappings":"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"}
|