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,874 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Advanced Caching Strategies Generator
|
|
4
|
+
* Cache-aside, write-through, invalidation patterns
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.setupCachingStrategies = setupCachingStrategies;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
47
|
+
async function setupCachingStrategies(basePath, options = {}) {
|
|
48
|
+
console.log(chalk_1.default.bold.blue('\n💾 Setting up Advanced Caching Strategies\n'));
|
|
49
|
+
const sharedPath = path.join(basePath, 'src/shared/caching');
|
|
50
|
+
if (!fs.existsSync(sharedPath)) {
|
|
51
|
+
fs.mkdirSync(sharedPath, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
fs.writeFileSync(path.join(sharedPath, 'cache.service.ts'), generateCacheService());
|
|
54
|
+
console.log(chalk_1.default.green(` ✓ Created cache service`));
|
|
55
|
+
fs.writeFileSync(path.join(sharedPath, 'cache-strategies.ts'), generateCacheStrategies());
|
|
56
|
+
console.log(chalk_1.default.green(` ✓ Created cache strategies`));
|
|
57
|
+
fs.writeFileSync(path.join(sharedPath, 'cache-invalidation.ts'), generateCacheInvalidation());
|
|
58
|
+
console.log(chalk_1.default.green(` ✓ Created cache invalidation`));
|
|
59
|
+
fs.writeFileSync(path.join(sharedPath, 'cache.decorator.ts'), generateCacheDecorator());
|
|
60
|
+
console.log(chalk_1.default.green(` ✓ Created cache decorator`));
|
|
61
|
+
fs.writeFileSync(path.join(sharedPath, 'distributed-cache.ts'), generateDistributedCache());
|
|
62
|
+
console.log(chalk_1.default.green(` ✓ Created distributed cache`));
|
|
63
|
+
fs.writeFileSync(path.join(sharedPath, 'cache.module.ts'), generateCacheModule());
|
|
64
|
+
console.log(chalk_1.default.green(` ✓ Created cache module`));
|
|
65
|
+
console.log(chalk_1.default.bold.green('\n✅ Caching strategies ready!\n'));
|
|
66
|
+
}
|
|
67
|
+
function generateCacheService() {
|
|
68
|
+
return `/**
|
|
69
|
+
* Cache Service
|
|
70
|
+
* Unified caching interface with multiple backends
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
74
|
+
|
|
75
|
+
export interface CacheEntry<T> {
|
|
76
|
+
value: T;
|
|
77
|
+
expiresAt: number;
|
|
78
|
+
tags?: string[];
|
|
79
|
+
metadata?: Record<string, any>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface CacheOptions {
|
|
83
|
+
ttl?: number;
|
|
84
|
+
tags?: string[];
|
|
85
|
+
refreshAhead?: boolean;
|
|
86
|
+
staleWhileRevalidate?: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@Injectable()
|
|
90
|
+
export class CacheService {
|
|
91
|
+
private readonly logger = new Logger(CacheService.name);
|
|
92
|
+
private readonly cache = new Map<string, CacheEntry<any>>();
|
|
93
|
+
private readonly tagIndex = new Map<string, Set<string>>();
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get value from cache
|
|
97
|
+
*/
|
|
98
|
+
async get<T>(key: string): Promise<T | null> {
|
|
99
|
+
const entry = this.cache.get(key);
|
|
100
|
+
|
|
101
|
+
if (!entry) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (Date.now() > entry.expiresAt) {
|
|
106
|
+
this.cache.delete(key);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return entry.value;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Set value in cache
|
|
115
|
+
*/
|
|
116
|
+
async set<T>(key: string, value: T, options: CacheOptions = {}): Promise<void> {
|
|
117
|
+
const ttl = options.ttl || 3600000; // 1 hour default
|
|
118
|
+
const entry: CacheEntry<T> = {
|
|
119
|
+
value,
|
|
120
|
+
expiresAt: Date.now() + ttl,
|
|
121
|
+
tags: options.tags,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
this.cache.set(key, entry);
|
|
125
|
+
|
|
126
|
+
// Index by tags
|
|
127
|
+
if (options.tags) {
|
|
128
|
+
for (const tag of options.tags) {
|
|
129
|
+
let keys = this.tagIndex.get(tag);
|
|
130
|
+
if (!keys) {
|
|
131
|
+
keys = new Set();
|
|
132
|
+
this.tagIndex.set(tag, keys);
|
|
133
|
+
}
|
|
134
|
+
keys.add(key);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Delete value from cache
|
|
141
|
+
*/
|
|
142
|
+
async delete(key: string): Promise<void> {
|
|
143
|
+
const entry = this.cache.get(key);
|
|
144
|
+
if (entry?.tags) {
|
|
145
|
+
for (const tag of entry.tags) {
|
|
146
|
+
this.tagIndex.get(tag)?.delete(key);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
this.cache.delete(key);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Delete all values with a tag
|
|
154
|
+
*/
|
|
155
|
+
async deleteByTag(tag: string): Promise<number> {
|
|
156
|
+
const keys = this.tagIndex.get(tag);
|
|
157
|
+
if (!keys) return 0;
|
|
158
|
+
|
|
159
|
+
let count = 0;
|
|
160
|
+
for (const key of keys) {
|
|
161
|
+
this.cache.delete(key);
|
|
162
|
+
count++;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this.tagIndex.delete(tag);
|
|
166
|
+
return count;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Check if key exists
|
|
171
|
+
*/
|
|
172
|
+
async has(key: string): Promise<boolean> {
|
|
173
|
+
const entry = this.cache.get(key);
|
|
174
|
+
if (!entry) return false;
|
|
175
|
+
if (Date.now() > entry.expiresAt) {
|
|
176
|
+
this.cache.delete(key);
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get or set (cache-aside pattern)
|
|
184
|
+
*/
|
|
185
|
+
async getOrSet<T>(
|
|
186
|
+
key: string,
|
|
187
|
+
factory: () => Promise<T>,
|
|
188
|
+
options: CacheOptions = {},
|
|
189
|
+
): Promise<T> {
|
|
190
|
+
const cached = await this.get<T>(key);
|
|
191
|
+
if (cached !== null) {
|
|
192
|
+
return cached;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const value = await factory();
|
|
196
|
+
await this.set(key, value, options);
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Clear all cache
|
|
202
|
+
*/
|
|
203
|
+
async clear(): Promise<void> {
|
|
204
|
+
this.cache.clear();
|
|
205
|
+
this.tagIndex.clear();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get cache stats
|
|
210
|
+
*/
|
|
211
|
+
getStats(): CacheStats {
|
|
212
|
+
let expired = 0;
|
|
213
|
+
let valid = 0;
|
|
214
|
+
const now = Date.now();
|
|
215
|
+
|
|
216
|
+
for (const entry of this.cache.values()) {
|
|
217
|
+
if (now > entry.expiresAt) {
|
|
218
|
+
expired++;
|
|
219
|
+
} else {
|
|
220
|
+
valid++;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
size: this.cache.size,
|
|
226
|
+
valid,
|
|
227
|
+
expired,
|
|
228
|
+
tags: this.tagIndex.size,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export interface CacheStats {
|
|
234
|
+
size: number;
|
|
235
|
+
valid: number;
|
|
236
|
+
expired: number;
|
|
237
|
+
tags: number;
|
|
238
|
+
}
|
|
239
|
+
`;
|
|
240
|
+
}
|
|
241
|
+
function generateCacheStrategies() {
|
|
242
|
+
return `/**
|
|
243
|
+
* Cache Strategies
|
|
244
|
+
* Different caching patterns for various use cases
|
|
245
|
+
*/
|
|
246
|
+
|
|
247
|
+
import { CacheService, CacheOptions } from './cache.service';
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Cache-Aside (Lazy Loading)
|
|
251
|
+
*/
|
|
252
|
+
export class CacheAsideStrategy<T> {
|
|
253
|
+
constructor(
|
|
254
|
+
private readonly cache: CacheService,
|
|
255
|
+
private readonly keyPrefix: string,
|
|
256
|
+
) {}
|
|
257
|
+
|
|
258
|
+
async get(id: string, loader: () => Promise<T>, options?: CacheOptions): Promise<T> {
|
|
259
|
+
const key = \`\${this.keyPrefix}:\${id}\`;
|
|
260
|
+
return this.cache.getOrSet(key, loader, options);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async invalidate(id: string): Promise<void> {
|
|
264
|
+
const key = \`\${this.keyPrefix}:\${id}\`;
|
|
265
|
+
await this.cache.delete(key);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Write-Through
|
|
271
|
+
*/
|
|
272
|
+
export class WriteThroughStrategy<T> {
|
|
273
|
+
constructor(
|
|
274
|
+
private readonly cache: CacheService,
|
|
275
|
+
private readonly keyPrefix: string,
|
|
276
|
+
private readonly writer: (id: string, value: T) => Promise<void>,
|
|
277
|
+
) {}
|
|
278
|
+
|
|
279
|
+
async write(id: string, value: T, options?: CacheOptions): Promise<void> {
|
|
280
|
+
// Write to storage first
|
|
281
|
+
await this.writer(id, value);
|
|
282
|
+
// Then update cache
|
|
283
|
+
const key = \`\${this.keyPrefix}:\${id}\`;
|
|
284
|
+
await this.cache.set(key, value, options);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async get(id: string): Promise<T | null> {
|
|
288
|
+
const key = \`\${this.keyPrefix}:\${id}\`;
|
|
289
|
+
return this.cache.get(key);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Write-Behind (Write-Back)
|
|
295
|
+
*/
|
|
296
|
+
export class WriteBehindStrategy<T> {
|
|
297
|
+
private readonly pending = new Map<string, { value: T; timer: NodeJS.Timeout }>();
|
|
298
|
+
|
|
299
|
+
constructor(
|
|
300
|
+
private readonly cache: CacheService,
|
|
301
|
+
private readonly keyPrefix: string,
|
|
302
|
+
private readonly writer: (id: string, value: T) => Promise<void>,
|
|
303
|
+
private readonly delay: number = 5000,
|
|
304
|
+
) {}
|
|
305
|
+
|
|
306
|
+
async write(id: string, value: T, options?: CacheOptions): Promise<void> {
|
|
307
|
+
const key = \`\${this.keyPrefix}:\${id}\`;
|
|
308
|
+
|
|
309
|
+
// Update cache immediately
|
|
310
|
+
await this.cache.set(key, value, options);
|
|
311
|
+
|
|
312
|
+
// Cancel pending write
|
|
313
|
+
const pending = this.pending.get(id);
|
|
314
|
+
if (pending) {
|
|
315
|
+
clearTimeout(pending.timer);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Schedule write
|
|
319
|
+
const timer = setTimeout(async () => {
|
|
320
|
+
await this.writer(id, value);
|
|
321
|
+
this.pending.delete(id);
|
|
322
|
+
}, this.delay);
|
|
323
|
+
|
|
324
|
+
this.pending.set(id, { value, timer });
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async flush(): Promise<void> {
|
|
328
|
+
const writes = Array.from(this.pending.entries()).map(async ([id, { value, timer }]) => {
|
|
329
|
+
clearTimeout(timer);
|
|
330
|
+
await this.writer(id, value);
|
|
331
|
+
});
|
|
332
|
+
await Promise.all(writes);
|
|
333
|
+
this.pending.clear();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Read-Through
|
|
339
|
+
*/
|
|
340
|
+
export class ReadThroughStrategy<T> {
|
|
341
|
+
constructor(
|
|
342
|
+
private readonly cache: CacheService,
|
|
343
|
+
private readonly keyPrefix: string,
|
|
344
|
+
private readonly loader: (id: string) => Promise<T | null>,
|
|
345
|
+
private readonly defaultOptions?: CacheOptions,
|
|
346
|
+
) {}
|
|
347
|
+
|
|
348
|
+
async get(id: string, options?: CacheOptions): Promise<T | null> {
|
|
349
|
+
const key = \`\${this.keyPrefix}:\${id}\`;
|
|
350
|
+
|
|
351
|
+
const cached = await this.cache.get<T>(key);
|
|
352
|
+
if (cached !== null) {
|
|
353
|
+
return cached;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const value = await this.loader(id);
|
|
357
|
+
if (value !== null) {
|
|
358
|
+
await this.cache.set(key, value, options || this.defaultOptions);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return value;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Refresh-Ahead
|
|
367
|
+
*/
|
|
368
|
+
export class RefreshAheadStrategy<T> {
|
|
369
|
+
private readonly refreshing = new Set<string>();
|
|
370
|
+
|
|
371
|
+
constructor(
|
|
372
|
+
private readonly cache: CacheService,
|
|
373
|
+
private readonly keyPrefix: string,
|
|
374
|
+
private readonly loader: (id: string) => Promise<T>,
|
|
375
|
+
private readonly refreshThreshold: number = 0.8, // Refresh when 80% of TTL passed
|
|
376
|
+
) {}
|
|
377
|
+
|
|
378
|
+
async get(id: string, ttl: number): Promise<T> {
|
|
379
|
+
const key = \`\${this.keyPrefix}:\${id}\`;
|
|
380
|
+
|
|
381
|
+
const cached = await this.cache.get<T>(key);
|
|
382
|
+
if (cached !== null) {
|
|
383
|
+
// Check if we should refresh
|
|
384
|
+
this.maybeRefresh(id, key, ttl);
|
|
385
|
+
return cached;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const value = await this.loader(id);
|
|
389
|
+
await this.cache.set(key, value, { ttl });
|
|
390
|
+
return value;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private async maybeRefresh(id: string, key: string, ttl: number): Promise<void> {
|
|
394
|
+
if (this.refreshing.has(key)) return;
|
|
395
|
+
|
|
396
|
+
// Refresh in background (implementation would check actual TTL remaining)
|
|
397
|
+
this.refreshing.add(key);
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
const value = await this.loader(id);
|
|
401
|
+
await this.cache.set(key, value, { ttl });
|
|
402
|
+
} finally {
|
|
403
|
+
this.refreshing.delete(key);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
`;
|
|
408
|
+
}
|
|
409
|
+
function generateCacheInvalidation() {
|
|
410
|
+
return `/**
|
|
411
|
+
* Cache Invalidation
|
|
412
|
+
* Event-driven cache invalidation patterns
|
|
413
|
+
*/
|
|
414
|
+
|
|
415
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
416
|
+
import { OnEvent } from '@nestjs/event-emitter';
|
|
417
|
+
import { CacheService } from './cache.service';
|
|
418
|
+
|
|
419
|
+
export interface InvalidationRule {
|
|
420
|
+
event: string;
|
|
421
|
+
pattern?: string;
|
|
422
|
+
tags?: string[];
|
|
423
|
+
handler?: (payload: any) => string[];
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
@Injectable()
|
|
427
|
+
export class CacheInvalidationService {
|
|
428
|
+
private readonly logger = new Logger(CacheInvalidationService.name);
|
|
429
|
+
private readonly rules: InvalidationRule[] = [];
|
|
430
|
+
|
|
431
|
+
constructor(private readonly cache: CacheService) {}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Register invalidation rule
|
|
435
|
+
*/
|
|
436
|
+
registerRule(rule: InvalidationRule): void {
|
|
437
|
+
this.rules.push(rule);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Handle entity created event
|
|
442
|
+
*/
|
|
443
|
+
@OnEvent('entity.created')
|
|
444
|
+
async onEntityCreated(payload: { type: string; id: string }): Promise<void> {
|
|
445
|
+
await this.invalidateByTag(\`\${payload.type}:list\`);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Handle entity updated event
|
|
450
|
+
*/
|
|
451
|
+
@OnEvent('entity.updated')
|
|
452
|
+
async onEntityUpdated(payload: { type: string; id: string }): Promise<void> {
|
|
453
|
+
await this.invalidateByTag(\`\${payload.type}:\${payload.id}\`);
|
|
454
|
+
await this.invalidateByTag(\`\${payload.type}:list\`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Handle entity deleted event
|
|
459
|
+
*/
|
|
460
|
+
@OnEvent('entity.deleted')
|
|
461
|
+
async onEntityDeleted(payload: { type: string; id: string }): Promise<void> {
|
|
462
|
+
await this.invalidateByTag(\`\${payload.type}:\${payload.id}\`);
|
|
463
|
+
await this.invalidateByTag(\`\${payload.type}:list\`);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Invalidate by tag
|
|
468
|
+
*/
|
|
469
|
+
async invalidateByTag(tag: string): Promise<number> {
|
|
470
|
+
const count = await this.cache.deleteByTag(tag);
|
|
471
|
+
this.logger.debug(\`Invalidated \${count} entries with tag: \${tag}\`);
|
|
472
|
+
return count;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Invalidate by pattern
|
|
477
|
+
*/
|
|
478
|
+
async invalidateByPattern(pattern: string): Promise<void> {
|
|
479
|
+
// For memory cache, iterate and match
|
|
480
|
+
// For Redis, use SCAN with pattern
|
|
481
|
+
this.logger.debug(\`Invalidating pattern: \${pattern}\`);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Process invalidation rules for an event
|
|
486
|
+
*/
|
|
487
|
+
async processEvent(event: string, payload: any): Promise<void> {
|
|
488
|
+
for (const rule of this.rules) {
|
|
489
|
+
if (rule.event !== event) continue;
|
|
490
|
+
|
|
491
|
+
if (rule.tags) {
|
|
492
|
+
for (const tag of rule.tags) {
|
|
493
|
+
await this.invalidateByTag(tag);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (rule.handler) {
|
|
498
|
+
const keys = rule.handler(payload);
|
|
499
|
+
for (const key of keys) {
|
|
500
|
+
await this.cache.delete(key);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Cache invalidation decorator
|
|
509
|
+
*/
|
|
510
|
+
export function InvalidatesCache(tags: string[]): MethodDecorator {
|
|
511
|
+
return function (
|
|
512
|
+
target: any,
|
|
513
|
+
propertyKey: string | symbol,
|
|
514
|
+
descriptor: PropertyDescriptor,
|
|
515
|
+
) {
|
|
516
|
+
const originalMethod = descriptor.value;
|
|
517
|
+
|
|
518
|
+
descriptor.value = async function (...args: any[]) {
|
|
519
|
+
const result = await originalMethod.apply(this, args);
|
|
520
|
+
|
|
521
|
+
// Get cache service from this context
|
|
522
|
+
const cacheService = (this as any).cacheService;
|
|
523
|
+
if (cacheService) {
|
|
524
|
+
for (const tag of tags) {
|
|
525
|
+
await cacheService.deleteByTag(tag);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return result;
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
return descriptor;
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
`;
|
|
536
|
+
}
|
|
537
|
+
function generateCacheDecorator() {
|
|
538
|
+
return `/**
|
|
539
|
+
* Cache Decorators
|
|
540
|
+
* Method-level caching with decorators
|
|
541
|
+
*/
|
|
542
|
+
|
|
543
|
+
import { SetMetadata } from '@nestjs/common';
|
|
544
|
+
|
|
545
|
+
export const CACHE_KEY = 'cache_key';
|
|
546
|
+
export const CACHE_TTL = 'cache_ttl';
|
|
547
|
+
export const CACHE_TAGS = 'cache_tags';
|
|
548
|
+
|
|
549
|
+
export interface CacheDecoratorOptions {
|
|
550
|
+
key?: string | ((...args: any[]) => string);
|
|
551
|
+
ttl?: number;
|
|
552
|
+
tags?: string[];
|
|
553
|
+
condition?: (...args: any[]) => boolean;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Cache method result
|
|
558
|
+
*/
|
|
559
|
+
export function Cacheable(options: CacheDecoratorOptions = {}): MethodDecorator {
|
|
560
|
+
return function (
|
|
561
|
+
target: any,
|
|
562
|
+
propertyKey: string | symbol,
|
|
563
|
+
descriptor: PropertyDescriptor,
|
|
564
|
+
) {
|
|
565
|
+
const originalMethod = descriptor.value;
|
|
566
|
+
const methodName = String(propertyKey);
|
|
567
|
+
const cache = new Map<string, { value: any; expiresAt: number }>();
|
|
568
|
+
|
|
569
|
+
descriptor.value = async function (...args: any[]) {
|
|
570
|
+
// Check condition
|
|
571
|
+
if (options.condition && !options.condition(...args)) {
|
|
572
|
+
return originalMethod.apply(this, args);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Generate key
|
|
576
|
+
const cacheKey = typeof options.key === 'function'
|
|
577
|
+
? options.key(...args)
|
|
578
|
+
: options.key || \`\${target.constructor.name}:\${methodName}:\${JSON.stringify(args)}\`;
|
|
579
|
+
|
|
580
|
+
// Check cache
|
|
581
|
+
const cached = cache.get(cacheKey);
|
|
582
|
+
if (cached && Date.now() < cached.expiresAt) {
|
|
583
|
+
return cached.value;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Execute and cache
|
|
587
|
+
const result = await originalMethod.apply(this, args);
|
|
588
|
+
const ttl = options.ttl || 60000;
|
|
589
|
+
|
|
590
|
+
cache.set(cacheKey, {
|
|
591
|
+
value: result,
|
|
592
|
+
expiresAt: Date.now() + ttl,
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
return result;
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
return descriptor;
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Evict cache on method execution
|
|
604
|
+
*/
|
|
605
|
+
export function CacheEvict(options: { key?: string; tags?: string[]; allEntries?: boolean }): MethodDecorator {
|
|
606
|
+
return function (
|
|
607
|
+
target: any,
|
|
608
|
+
propertyKey: string | symbol,
|
|
609
|
+
descriptor: PropertyDescriptor,
|
|
610
|
+
) {
|
|
611
|
+
const originalMethod = descriptor.value;
|
|
612
|
+
|
|
613
|
+
descriptor.value = async function (...args: any[]) {
|
|
614
|
+
const result = await originalMethod.apply(this, args);
|
|
615
|
+
|
|
616
|
+
// Evict logic would be handled by interceptor or injected service
|
|
617
|
+
const cacheService = (this as any).cacheService;
|
|
618
|
+
if (cacheService) {
|
|
619
|
+
if (options.allEntries) {
|
|
620
|
+
await cacheService.clear();
|
|
621
|
+
} else if (options.key) {
|
|
622
|
+
await cacheService.delete(options.key);
|
|
623
|
+
} else if (options.tags) {
|
|
624
|
+
for (const tag of options.tags) {
|
|
625
|
+
await cacheService.deleteByTag(tag);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return result;
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
return descriptor;
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Cache with automatic refresh
|
|
639
|
+
*/
|
|
640
|
+
export function CacheRefresh(options: CacheDecoratorOptions & { refreshInterval: number }): MethodDecorator {
|
|
641
|
+
return function (
|
|
642
|
+
target: any,
|
|
643
|
+
propertyKey: string | symbol,
|
|
644
|
+
descriptor: PropertyDescriptor,
|
|
645
|
+
) {
|
|
646
|
+
const originalMethod = descriptor.value;
|
|
647
|
+
let cachedValue: any = null;
|
|
648
|
+
let lastRefresh: number = 0;
|
|
649
|
+
|
|
650
|
+
// Background refresh
|
|
651
|
+
setInterval(async () => {
|
|
652
|
+
try {
|
|
653
|
+
cachedValue = await originalMethod.apply(target);
|
|
654
|
+
lastRefresh = Date.now();
|
|
655
|
+
} catch (error) {
|
|
656
|
+
// Keep stale value on error
|
|
657
|
+
}
|
|
658
|
+
}, options.refreshInterval);
|
|
659
|
+
|
|
660
|
+
descriptor.value = async function (...args: any[]) {
|
|
661
|
+
if (cachedValue !== null) {
|
|
662
|
+
return cachedValue;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
cachedValue = await originalMethod.apply(this, args);
|
|
666
|
+
lastRefresh = Date.now();
|
|
667
|
+
return cachedValue;
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
return descriptor;
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
`;
|
|
674
|
+
}
|
|
675
|
+
function generateDistributedCache() {
|
|
676
|
+
return `/**
|
|
677
|
+
* Distributed Cache
|
|
678
|
+
* Multi-node cache synchronization
|
|
679
|
+
*/
|
|
680
|
+
|
|
681
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
682
|
+
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
683
|
+
|
|
684
|
+
export interface DistributedCacheOptions {
|
|
685
|
+
nodeId: string;
|
|
686
|
+
syncInterval?: number;
|
|
687
|
+
maxNodes?: number;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
@Injectable()
|
|
691
|
+
export class DistributedCacheService {
|
|
692
|
+
private readonly logger = new Logger(DistributedCacheService.name);
|
|
693
|
+
private readonly localCache = new Map<string, any>();
|
|
694
|
+
private readonly versions = new Map<string, number>();
|
|
695
|
+
private readonly nodeId: string;
|
|
696
|
+
|
|
697
|
+
constructor(
|
|
698
|
+
private readonly eventEmitter: EventEmitter2,
|
|
699
|
+
private readonly options: DistributedCacheOptions,
|
|
700
|
+
) {
|
|
701
|
+
this.nodeId = options.nodeId;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Set value with distributed sync
|
|
706
|
+
*/
|
|
707
|
+
async set(key: string, value: any, ttl?: number): Promise<void> {
|
|
708
|
+
const version = (this.versions.get(key) || 0) + 1;
|
|
709
|
+
this.versions.set(key, version);
|
|
710
|
+
|
|
711
|
+
this.localCache.set(key, {
|
|
712
|
+
value,
|
|
713
|
+
version,
|
|
714
|
+
expiresAt: ttl ? Date.now() + ttl : undefined,
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
// Broadcast to other nodes
|
|
718
|
+
this.eventEmitter.emit('cache.sync', {
|
|
719
|
+
type: 'set',
|
|
720
|
+
key,
|
|
721
|
+
value,
|
|
722
|
+
version,
|
|
723
|
+
ttl,
|
|
724
|
+
nodeId: this.nodeId,
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Get value
|
|
730
|
+
*/
|
|
731
|
+
async get<T>(key: string): Promise<T | null> {
|
|
732
|
+
const entry = this.localCache.get(key);
|
|
733
|
+
|
|
734
|
+
if (!entry) return null;
|
|
735
|
+
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
736
|
+
this.localCache.delete(key);
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
return entry.value;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Delete value with distributed sync
|
|
745
|
+
*/
|
|
746
|
+
async delete(key: string): Promise<void> {
|
|
747
|
+
this.localCache.delete(key);
|
|
748
|
+
this.versions.delete(key);
|
|
749
|
+
|
|
750
|
+
this.eventEmitter.emit('cache.sync', {
|
|
751
|
+
type: 'delete',
|
|
752
|
+
key,
|
|
753
|
+
nodeId: this.nodeId,
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Handle sync from other nodes
|
|
759
|
+
*/
|
|
760
|
+
handleSync(message: CacheSyncMessage): void {
|
|
761
|
+
if (message.nodeId === this.nodeId) return;
|
|
762
|
+
|
|
763
|
+
const currentVersion = this.versions.get(message.key) || 0;
|
|
764
|
+
|
|
765
|
+
if (message.type === 'set' && message.version > currentVersion) {
|
|
766
|
+
this.localCache.set(message.key, {
|
|
767
|
+
value: message.value,
|
|
768
|
+
version: message.version,
|
|
769
|
+
expiresAt: message.ttl ? Date.now() + message.ttl : undefined,
|
|
770
|
+
});
|
|
771
|
+
this.versions.set(message.key, message.version);
|
|
772
|
+
} else if (message.type === 'delete') {
|
|
773
|
+
this.localCache.delete(message.key);
|
|
774
|
+
this.versions.delete(message.key);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Get cache size
|
|
780
|
+
*/
|
|
781
|
+
size(): number {
|
|
782
|
+
return this.localCache.size;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
interface CacheSyncMessage {
|
|
787
|
+
type: 'set' | 'delete';
|
|
788
|
+
key: string;
|
|
789
|
+
value?: any;
|
|
790
|
+
version?: number;
|
|
791
|
+
ttl?: number;
|
|
792
|
+
nodeId: string;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Two-tier cache (L1 local + L2 distributed)
|
|
797
|
+
*/
|
|
798
|
+
export class TwoTierCache {
|
|
799
|
+
constructor(
|
|
800
|
+
private readonly l1: Map<string, any>,
|
|
801
|
+
private readonly l2: DistributedCacheService,
|
|
802
|
+
private readonly l1Ttl: number = 10000,
|
|
803
|
+
) {}
|
|
804
|
+
|
|
805
|
+
async get<T>(key: string): Promise<T | null> {
|
|
806
|
+
// Check L1
|
|
807
|
+
const l1Entry = this.l1.get(key);
|
|
808
|
+
if (l1Entry && Date.now() < l1Entry.expiresAt) {
|
|
809
|
+
return l1Entry.value;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Check L2
|
|
813
|
+
const value = await this.l2.get<T>(key);
|
|
814
|
+
if (value !== null) {
|
|
815
|
+
// Populate L1
|
|
816
|
+
this.l1.set(key, {
|
|
817
|
+
value,
|
|
818
|
+
expiresAt: Date.now() + this.l1Ttl,
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
return value;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
async set(key: string, value: any, ttl?: number): Promise<void> {
|
|
826
|
+
// Set in both tiers
|
|
827
|
+
this.l1.set(key, {
|
|
828
|
+
value,
|
|
829
|
+
expiresAt: Date.now() + this.l1Ttl,
|
|
830
|
+
});
|
|
831
|
+
await this.l2.set(key, value, ttl);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
`;
|
|
835
|
+
}
|
|
836
|
+
function generateCacheModule() {
|
|
837
|
+
return `import { Module, Global, DynamicModule } from '@nestjs/common';
|
|
838
|
+
import { CacheService } from './cache.service';
|
|
839
|
+
import { CacheInvalidationService } from './cache-invalidation';
|
|
840
|
+
import { DistributedCacheService, DistributedCacheOptions } from './distributed-cache';
|
|
841
|
+
|
|
842
|
+
export interface CacheModuleOptions {
|
|
843
|
+
provider?: 'memory' | 'redis';
|
|
844
|
+
distributed?: DistributedCacheOptions;
|
|
845
|
+
defaultTtl?: number;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
@Global()
|
|
849
|
+
@Module({})
|
|
850
|
+
export class CacheModule {
|
|
851
|
+
static forRoot(options: CacheModuleOptions = {}): DynamicModule {
|
|
852
|
+
return {
|
|
853
|
+
module: CacheModule,
|
|
854
|
+
providers: [
|
|
855
|
+
{
|
|
856
|
+
provide: 'CACHE_OPTIONS',
|
|
857
|
+
useValue: options,
|
|
858
|
+
},
|
|
859
|
+
CacheService,
|
|
860
|
+
CacheInvalidationService,
|
|
861
|
+
...(options.distributed ? [{
|
|
862
|
+
provide: DistributedCacheService,
|
|
863
|
+
useFactory: (eventEmitter: any) =>
|
|
864
|
+
new DistributedCacheService(eventEmitter, options.distributed!),
|
|
865
|
+
inject: ['EventEmitter2'],
|
|
866
|
+
}] : []),
|
|
867
|
+
],
|
|
868
|
+
exports: [CacheService, CacheInvalidationService],
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
`;
|
|
873
|
+
}
|
|
874
|
+
//# sourceMappingURL=caching-strategies.js.map
|