Created
April 3, 2024 21:29
-
-
Save ali-master/dd67628b7b7f2f133bedb986b1092498 to your computer and use it in GitHub Desktop.
Simple Multi Tenant Manager
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Controller, Get, Inject, Injectable, Module, Scope } from "@nestjs/common"; | |
import { | |
ContextId, ContextIdFactory, ContextIdResolver, ContextIdResolverFn, ContextIdStrategy, | |
HostComponentInfo, ModuleRef, NestFactory, REQUEST | |
} from "@nestjs/core"; | |
import { NestExpressApplication } from "@nestjs/platform-express"; | |
// Context strategy | |
const tenants = new Map<string, ContextId>(); | |
export class TenantContextIdStrategy implements ContextIdStrategy { | |
public attach(contextId: ContextId, request: Request): ContextIdResolverFn | ContextIdResolver | undefined { | |
// request is string in the durable services | |
const tenantId = typeof request == "string" ? request : "example"; | |
let tenantSubTreeId: ContextId; | |
if (tenants.has(tenantId)) { | |
tenantSubTreeId = tenants.get(tenantId) as ContextId; | |
} else { | |
tenantSubTreeId = ContextIdFactory.create(); | |
tenants.set(tenantId, tenantSubTreeId); | |
} | |
return { | |
resolve: (info: HostComponentInfo): ContextId => | |
info.isTreeDurable ? tenantSubTreeId : contextId, | |
payload: tenantId, | |
}; | |
} | |
} | |
ContextIdFactory.apply(new TenantContextIdStrategy()); | |
class DatabaseService { } | |
@Injectable({ scope: Scope.REQUEST, durable: true }) | |
class ExampleService { | |
constructor( | |
protected db: DatabaseService | |
) { } | |
} | |
@Injectable({ scope: Scope.REQUEST, durable: true }) | |
export class TransformationService { | |
public constructor( | |
protected moduleRef: ModuleRef, | |
@Inject(REQUEST) public request: any | |
) { } | |
public async test(): Promise<void> { | |
const allProviders = await this.moduleRef.resolve("exampleToken", ContextIdFactory.getByRequest(this.request), { each: true }); | |
allProviders.forEach((p: any) => { | |
// console.log(p); | |
}); | |
} | |
} | |
@Module({ | |
providers: [TransformationService], | |
imports: [], | |
controllers: [], | |
exports: [TransformationService] | |
}) | |
class TransformationModule { | |
} | |
// STARTUP | |
@Controller("example") | |
class ExampleController { | |
public constructor( | |
public transformationService: TransformationService, | |
// protected service: ExampleService <- if uncommented it forces initialization of ExampleService with correct REQUEST context ie. everything works fine. | |
) {} | |
@Get() | |
public getAccessPoints(): string { | |
this.transformationService.test(); | |
return "example"; | |
} | |
} | |
@Module({ | |
providers: [ | |
ExampleService, | |
TransformationService, | |
{ | |
provide: "exampleToken", | |
useExisting: ExampleService | |
}, | |
{ | |
provide: DatabaseService, | |
scope: Scope.REQUEST, | |
durable: true, | |
inject: [REQUEST], | |
useFactory: (tenantId: string): Promise<DatabaseService> => { | |
// request here is `undefined` when initialized via this.moduleRef.resolve | |
if(typeof tenantId != "string") // REQUEST is string for durable services and request obj otherwise | |
throw new Error("Database can only be requested by services marked as durable"); | |
console.log("INITIALIZED DATABASE FOR " + tenantId); | |
return <any>{}; | |
} | |
} | |
], | |
controllers: [ExampleController], | |
imports: [TransformationModule] | |
}) | |
class ApiModule {} | |
(async(): Promise<void> => { | |
const app = await NestFactory.create<NestExpressApplication>(ApiModule); | |
app | |
.disable("x-powered-by") | |
.set("trust proxy", "loopback, linklocal, uniquelocal"); | |
app.enableCors(); | |
await app.listen(80); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment