nestjs-ddd-cli 2.2.0 → 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 +26 -3
- 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,520 @@
|
|
|
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.applyMultiTenancyRecipe = applyMultiTenancyRecipe;
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const file_utils_1 = require("../../utils/file.utils");
|
|
43
|
+
async function applyMultiTenancyRecipe(basePath) {
|
|
44
|
+
const sharedPath = path.join(basePath, 'src/shared');
|
|
45
|
+
const tenantPath = path.join(sharedPath, 'tenancy');
|
|
46
|
+
await (0, file_utils_1.ensureDir)(tenantPath);
|
|
47
|
+
await (0, file_utils_1.ensureDir)(path.join(tenantPath, 'middleware'));
|
|
48
|
+
await (0, file_utils_1.ensureDir)(path.join(tenantPath, 'guards'));
|
|
49
|
+
await (0, file_utils_1.ensureDir)(path.join(tenantPath, 'decorators'));
|
|
50
|
+
// Tenant context
|
|
51
|
+
const tenantContextContent = `import { AsyncLocalStorage } from "async_hooks";
|
|
52
|
+
|
|
53
|
+
export interface TenantContext {
|
|
54
|
+
tenantId: string;
|
|
55
|
+
tenantSlug?: string;
|
|
56
|
+
tenantName?: string;
|
|
57
|
+
config?: TenantConfig;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface TenantConfig {
|
|
61
|
+
features: string[];
|
|
62
|
+
limits: {
|
|
63
|
+
maxUsers?: number;
|
|
64
|
+
maxStorage?: number;
|
|
65
|
+
[key: string]: any;
|
|
66
|
+
};
|
|
67
|
+
settings: Record<string, any>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const tenantStorage = new AsyncLocalStorage<TenantContext>();
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get current tenant context
|
|
74
|
+
*/
|
|
75
|
+
export function getCurrentTenant(): TenantContext | undefined {
|
|
76
|
+
return tenantStorage.getStore();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get current tenant ID
|
|
81
|
+
*/
|
|
82
|
+
export function getCurrentTenantId(): string | undefined {
|
|
83
|
+
return tenantStorage.getStore()?.tenantId;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Run code within tenant context
|
|
88
|
+
*/
|
|
89
|
+
export function runWithTenant<T>(tenant: TenantContext, fn: () => T): T {
|
|
90
|
+
return tenantStorage.run(tenant, fn);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check if feature is enabled for current tenant
|
|
95
|
+
*/
|
|
96
|
+
export function hasFeature(feature: string): boolean {
|
|
97
|
+
const tenant = getCurrentTenant();
|
|
98
|
+
return tenant?.config?.features?.includes(feature) ?? false;
|
|
99
|
+
}
|
|
100
|
+
`;
|
|
101
|
+
await (0, file_utils_1.writeFile)(path.join(tenantPath, 'tenant.context.ts'), tenantContextContent);
|
|
102
|
+
// Tenant middleware
|
|
103
|
+
const tenantMiddlewareContent = `import { Injectable, NestMiddleware, NotFoundException } from "@nestjs/common";
|
|
104
|
+
import { Request, Response, NextFunction } from "express";
|
|
105
|
+
import { tenantStorage, TenantContext } from "../tenant.context";
|
|
106
|
+
|
|
107
|
+
export interface TenantResolver {
|
|
108
|
+
resolve(req: Request): Promise<TenantContext | null>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Resolve tenant from subdomain: tenant1.example.com
|
|
113
|
+
*/
|
|
114
|
+
@Injectable()
|
|
115
|
+
export class SubdomainTenantResolver implements TenantResolver {
|
|
116
|
+
async resolve(req: Request): Promise<TenantContext | null> {
|
|
117
|
+
const host = req.hostname;
|
|
118
|
+
const parts = host.split(".");
|
|
119
|
+
|
|
120
|
+
if (parts.length < 2) return null;
|
|
121
|
+
|
|
122
|
+
const subdomain = parts[0];
|
|
123
|
+
if (subdomain === "www" || subdomain === "api") return null;
|
|
124
|
+
|
|
125
|
+
// Look up tenant by subdomain (implement your own lookup)
|
|
126
|
+
return this.lookupTenant(subdomain);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private async lookupTenant(slug: string): Promise<TenantContext | null> {
|
|
130
|
+
// TODO: Implement actual tenant lookup from database
|
|
131
|
+
// This is a placeholder
|
|
132
|
+
return {
|
|
133
|
+
tenantId: slug,
|
|
134
|
+
tenantSlug: slug,
|
|
135
|
+
tenantName: slug,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Resolve tenant from header: X-Tenant-ID
|
|
142
|
+
*/
|
|
143
|
+
@Injectable()
|
|
144
|
+
export class HeaderTenantResolver implements TenantResolver {
|
|
145
|
+
async resolve(req: Request): Promise<TenantContext | null> {
|
|
146
|
+
const tenantId = req.headers["x-tenant-id"] as string;
|
|
147
|
+
if (!tenantId) return null;
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
tenantId,
|
|
151
|
+
tenantSlug: tenantId,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Resolve tenant from URL path: /tenant/:tenantId/...
|
|
158
|
+
*/
|
|
159
|
+
@Injectable()
|
|
160
|
+
export class PathTenantResolver implements TenantResolver {
|
|
161
|
+
async resolve(req: Request): Promise<TenantContext | null> {
|
|
162
|
+
const match = req.path.match(/^\\/tenant\\/([^/]+)/);
|
|
163
|
+
if (!match) return null;
|
|
164
|
+
|
|
165
|
+
const tenantId = match[1];
|
|
166
|
+
return {
|
|
167
|
+
tenantId,
|
|
168
|
+
tenantSlug: tenantId,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Resolve tenant from JWT token
|
|
175
|
+
*/
|
|
176
|
+
@Injectable()
|
|
177
|
+
export class JwtTenantResolver implements TenantResolver {
|
|
178
|
+
async resolve(req: Request): Promise<TenantContext | null> {
|
|
179
|
+
const user = (req as any).user;
|
|
180
|
+
if (!user?.tenantId) return null;
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
tenantId: user.tenantId,
|
|
184
|
+
tenantSlug: user.tenantSlug,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Main tenant middleware
|
|
191
|
+
*/
|
|
192
|
+
@Injectable()
|
|
193
|
+
export class TenantMiddleware implements NestMiddleware {
|
|
194
|
+
constructor(private resolver: TenantResolver) {}
|
|
195
|
+
|
|
196
|
+
async use(req: Request, res: Response, next: NextFunction) {
|
|
197
|
+
const tenant = await this.resolver.resolve(req);
|
|
198
|
+
|
|
199
|
+
if (!tenant) {
|
|
200
|
+
throw new NotFoundException("Tenant not found");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Attach tenant to request
|
|
204
|
+
(req as any).tenant = tenant;
|
|
205
|
+
(req as any).tenantId = tenant.tenantId;
|
|
206
|
+
|
|
207
|
+
// Run remaining middleware in tenant context
|
|
208
|
+
tenantStorage.run(tenant, () => next());
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
`;
|
|
212
|
+
await (0, file_utils_1.writeFile)(path.join(tenantPath, 'middleware/tenant.middleware.ts'), tenantMiddlewareContent);
|
|
213
|
+
// Tenant guard
|
|
214
|
+
const tenantGuardContent = `import {
|
|
215
|
+
Injectable,
|
|
216
|
+
CanActivate,
|
|
217
|
+
ExecutionContext,
|
|
218
|
+
ForbiddenException,
|
|
219
|
+
} from "@nestjs/common";
|
|
220
|
+
import { Reflector } from "@nestjs/core";
|
|
221
|
+
import { getCurrentTenant, hasFeature } from "../tenant.context";
|
|
222
|
+
|
|
223
|
+
export const TENANT_FEATURE_KEY = "tenant_feature";
|
|
224
|
+
export const RequireFeature = (...features: string[]) =>
|
|
225
|
+
Reflect.metadata(TENANT_FEATURE_KEY, features);
|
|
226
|
+
|
|
227
|
+
@Injectable()
|
|
228
|
+
export class TenantFeatureGuard implements CanActivate {
|
|
229
|
+
constructor(private reflector: Reflector) {}
|
|
230
|
+
|
|
231
|
+
canActivate(context: ExecutionContext): boolean {
|
|
232
|
+
const requiredFeatures = this.reflector.getAllAndOverride<string[]>(
|
|
233
|
+
TENANT_FEATURE_KEY,
|
|
234
|
+
[context.getHandler(), context.getClass()]
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
if (!requiredFeatures || requiredFeatures.length === 0) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const tenant = getCurrentTenant();
|
|
242
|
+
if (!tenant) {
|
|
243
|
+
throw new ForbiddenException("No tenant context");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
for (const feature of requiredFeatures) {
|
|
247
|
+
if (!hasFeature(feature)) {
|
|
248
|
+
throw new ForbiddenException(
|
|
249
|
+
\`Feature '\${feature}' is not enabled for this tenant\`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@Injectable()
|
|
259
|
+
export class TenantIsolationGuard implements CanActivate {
|
|
260
|
+
canActivate(context: ExecutionContext): boolean {
|
|
261
|
+
const request = context.switchToHttp().getRequest();
|
|
262
|
+
const tenant = getCurrentTenant();
|
|
263
|
+
const user = request.user;
|
|
264
|
+
|
|
265
|
+
if (!tenant) {
|
|
266
|
+
throw new ForbiddenException("No tenant context");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Ensure user belongs to current tenant
|
|
270
|
+
if (user && user.tenantId !== tenant.tenantId) {
|
|
271
|
+
throw new ForbiddenException("Access denied: tenant mismatch");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
`;
|
|
278
|
+
await (0, file_utils_1.writeFile)(path.join(tenantPath, 'guards/tenant.guard.ts'), tenantGuardContent);
|
|
279
|
+
// Tenant-aware repository
|
|
280
|
+
const tenantRepositoryContent = `import { Repository, SelectQueryBuilder, DeepPartial } from "typeorm";
|
|
281
|
+
import { getCurrentTenantId } from "./tenant.context";
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Base interface for tenant-aware entities
|
|
285
|
+
*/
|
|
286
|
+
export interface TenantEntity {
|
|
287
|
+
tenantId: string;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Tenant-aware repository that automatically filters by tenant
|
|
292
|
+
*/
|
|
293
|
+
export class TenantAwareRepository<T extends TenantEntity> {
|
|
294
|
+
constructor(protected readonly repository: Repository<T>) {}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Get current tenant ID or throw
|
|
298
|
+
*/
|
|
299
|
+
protected getTenantId(): string {
|
|
300
|
+
const tenantId = getCurrentTenantId();
|
|
301
|
+
if (!tenantId) {
|
|
302
|
+
throw new Error("No tenant context available");
|
|
303
|
+
}
|
|
304
|
+
return tenantId;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Create query builder with tenant filter
|
|
309
|
+
*/
|
|
310
|
+
createQueryBuilder(alias: string): SelectQueryBuilder<T> {
|
|
311
|
+
return this.repository
|
|
312
|
+
.createQueryBuilder(alias)
|
|
313
|
+
.where(\`\${alias}.tenantId = :tenantId\`, { tenantId: this.getTenantId() });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Find all entities for current tenant
|
|
318
|
+
*/
|
|
319
|
+
async findAll(): Promise<T[]> {
|
|
320
|
+
return this.repository.find({
|
|
321
|
+
where: { tenantId: this.getTenantId() } as any,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Find one entity by ID for current tenant
|
|
327
|
+
*/
|
|
328
|
+
async findById(id: string): Promise<T | null> {
|
|
329
|
+
return this.repository.findOne({
|
|
330
|
+
where: { id, tenantId: this.getTenantId() } as any,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Create entity with tenant ID
|
|
336
|
+
*/
|
|
337
|
+
async create(data: DeepPartial<T>): Promise<T> {
|
|
338
|
+
const entity = this.repository.create({
|
|
339
|
+
...data,
|
|
340
|
+
tenantId: this.getTenantId(),
|
|
341
|
+
} as DeepPartial<T>);
|
|
342
|
+
return this.repository.save(entity);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Update entity ensuring tenant isolation
|
|
347
|
+
*/
|
|
348
|
+
async update(id: string, data: DeepPartial<T>): Promise<T | null> {
|
|
349
|
+
const existing = await this.findById(id);
|
|
350
|
+
if (!existing) return null;
|
|
351
|
+
|
|
352
|
+
await this.repository.update(
|
|
353
|
+
{ id, tenantId: this.getTenantId() } as any,
|
|
354
|
+
data as any
|
|
355
|
+
);
|
|
356
|
+
return this.findById(id);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Delete entity ensuring tenant isolation
|
|
361
|
+
*/
|
|
362
|
+
async delete(id: string): Promise<boolean> {
|
|
363
|
+
const result = await this.repository.delete({
|
|
364
|
+
id,
|
|
365
|
+
tenantId: this.getTenantId(),
|
|
366
|
+
} as any);
|
|
367
|
+
return (result.affected || 0) > 0;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Count entities for current tenant
|
|
372
|
+
*/
|
|
373
|
+
async count(): Promise<number> {
|
|
374
|
+
return this.repository.count({
|
|
375
|
+
where: { tenantId: this.getTenantId() } as any,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
`;
|
|
380
|
+
await (0, file_utils_1.writeFile)(path.join(tenantPath, 'tenant-aware.repository.ts'), tenantRepositoryContent);
|
|
381
|
+
// Tenant decorator
|
|
382
|
+
const tenantDecoratorContent = `import { createParamDecorator, ExecutionContext, SetMetadata } from "@nestjs/common";
|
|
383
|
+
import { getCurrentTenant, TenantContext } from "../tenant.context";
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Get current tenant from request
|
|
387
|
+
*/
|
|
388
|
+
export const CurrentTenant = createParamDecorator(
|
|
389
|
+
(data: keyof TenantContext | undefined, ctx: ExecutionContext) => {
|
|
390
|
+
const request = ctx.switchToHttp().getRequest();
|
|
391
|
+
const tenant = request.tenant || getCurrentTenant();
|
|
392
|
+
|
|
393
|
+
if (!tenant) return null;
|
|
394
|
+
return data ? tenant[data] : tenant;
|
|
395
|
+
}
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Get current tenant ID
|
|
400
|
+
*/
|
|
401
|
+
export const TenantId = createParamDecorator(
|
|
402
|
+
(_data: unknown, ctx: ExecutionContext) => {
|
|
403
|
+
const request = ctx.switchToHttp().getRequest();
|
|
404
|
+
return request.tenantId || getCurrentTenant()?.tenantId;
|
|
405
|
+
}
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Mark endpoint as requiring specific tenant features
|
|
410
|
+
*/
|
|
411
|
+
export const TENANT_FEATURES = "tenant:features";
|
|
412
|
+
export const RequireTenantFeatures = (...features: string[]) =>
|
|
413
|
+
SetMetadata(TENANT_FEATURES, features);
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Mark endpoint as tenant-isolated
|
|
417
|
+
*/
|
|
418
|
+
export const TENANT_ISOLATED = "tenant:isolated";
|
|
419
|
+
export const TenantIsolated = () => SetMetadata(TENANT_ISOLATED, true);
|
|
420
|
+
`;
|
|
421
|
+
await (0, file_utils_1.writeFile)(path.join(tenantPath, 'decorators/tenant.decorator.ts'), tenantDecoratorContent);
|
|
422
|
+
// Tenant module
|
|
423
|
+
const tenantModuleContent = `import { Module, Global, DynamicModule, MiddlewareConsumer, NestModule } from "@nestjs/common";
|
|
424
|
+
import {
|
|
425
|
+
TenantMiddleware,
|
|
426
|
+
SubdomainTenantResolver,
|
|
427
|
+
HeaderTenantResolver,
|
|
428
|
+
PathTenantResolver,
|
|
429
|
+
TenantResolver,
|
|
430
|
+
} from "./middleware/tenant.middleware";
|
|
431
|
+
import { TenantFeatureGuard, TenantIsolationGuard } from "./guards/tenant.guard";
|
|
432
|
+
|
|
433
|
+
export type TenantStrategy = "subdomain" | "header" | "path" | "jwt";
|
|
434
|
+
|
|
435
|
+
export interface TenantModuleOptions {
|
|
436
|
+
strategy: TenantStrategy;
|
|
437
|
+
excludePaths?: string[];
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
@Global()
|
|
441
|
+
@Module({})
|
|
442
|
+
export class TenantModule implements NestModule {
|
|
443
|
+
static options: TenantModuleOptions;
|
|
444
|
+
|
|
445
|
+
static forRoot(options: TenantModuleOptions): DynamicModule {
|
|
446
|
+
TenantModule.options = options;
|
|
447
|
+
|
|
448
|
+
const resolverProvider = {
|
|
449
|
+
provide: TenantResolver,
|
|
450
|
+
useClass: this.getResolverClass(options.strategy),
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
return {
|
|
454
|
+
module: TenantModule,
|
|
455
|
+
providers: [
|
|
456
|
+
resolverProvider,
|
|
457
|
+
TenantMiddleware,
|
|
458
|
+
TenantFeatureGuard,
|
|
459
|
+
TenantIsolationGuard,
|
|
460
|
+
],
|
|
461
|
+
exports: [
|
|
462
|
+
TenantMiddleware,
|
|
463
|
+
TenantFeatureGuard,
|
|
464
|
+
TenantIsolationGuard,
|
|
465
|
+
],
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private static getResolverClass(strategy: TenantStrategy) {
|
|
470
|
+
switch (strategy) {
|
|
471
|
+
case "subdomain":
|
|
472
|
+
return SubdomainTenantResolver;
|
|
473
|
+
case "header":
|
|
474
|
+
return HeaderTenantResolver;
|
|
475
|
+
case "path":
|
|
476
|
+
return PathTenantResolver;
|
|
477
|
+
default:
|
|
478
|
+
return HeaderTenantResolver;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
configure(consumer: MiddlewareConsumer) {
|
|
483
|
+
const excludePaths = TenantModule.options?.excludePaths || [
|
|
484
|
+
"/health",
|
|
485
|
+
"/docs",
|
|
486
|
+
"/swagger",
|
|
487
|
+
];
|
|
488
|
+
|
|
489
|
+
consumer
|
|
490
|
+
.apply(TenantMiddleware)
|
|
491
|
+
.exclude(...excludePaths)
|
|
492
|
+
.forRoutes("*");
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
`;
|
|
496
|
+
await (0, file_utils_1.writeFile)(path.join(tenantPath, 'tenant.module.ts'), tenantModuleContent);
|
|
497
|
+
// Index exports
|
|
498
|
+
await (0, file_utils_1.writeFile)(path.join(tenantPath, 'index.ts'), `export * from "./tenant.context";
|
|
499
|
+
export * from "./tenant-aware.repository";
|
|
500
|
+
export * from "./tenant.module";
|
|
501
|
+
export * from "./middleware/tenant.middleware";
|
|
502
|
+
export * from "./guards/tenant.guard";
|
|
503
|
+
export * from "./decorators/tenant.decorator";
|
|
504
|
+
`);
|
|
505
|
+
await (0, file_utils_1.writeFile)(path.join(tenantPath, 'middleware/index.ts'), `export * from "./tenant.middleware";
|
|
506
|
+
`);
|
|
507
|
+
await (0, file_utils_1.writeFile)(path.join(tenantPath, 'guards/index.ts'), `export * from "./tenant.guard";
|
|
508
|
+
`);
|
|
509
|
+
await (0, file_utils_1.writeFile)(path.join(tenantPath, 'decorators/index.ts'), `export * from "./tenant.decorator";
|
|
510
|
+
`);
|
|
511
|
+
console.log(chalk_1.default.green(' ✓ Tenant context with AsyncLocalStorage'));
|
|
512
|
+
console.log(chalk_1.default.green(' ✓ Multiple tenant resolvers (subdomain, header, path, JWT)'));
|
|
513
|
+
console.log(chalk_1.default.green(' ✓ Tenant middleware'));
|
|
514
|
+
console.log(chalk_1.default.green(' ✓ Tenant feature guard'));
|
|
515
|
+
console.log(chalk_1.default.green(' ✓ Tenant isolation guard'));
|
|
516
|
+
console.log(chalk_1.default.green(' ✓ Tenant-aware repository base class'));
|
|
517
|
+
console.log(chalk_1.default.green(' ✓ Tenant decorators (@CurrentTenant, @TenantId)'));
|
|
518
|
+
console.log(chalk_1.default.green(' ✓ Tenant module with strategy selection'));
|
|
519
|
+
}
|
|
520
|
+
//# sourceMappingURL=multi-tenancy.recipe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-tenancy.recipe.js","sourceRoot":"","sources":["../../../src/commands/recipes/multi-tenancy.recipe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function applyOAuth2Recipe(basePath: string): Promise<void>;
|