Skip to content

Instantly share code, notes, and snippets.

@ali-master
Created April 3, 2024 21:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ali-master/dd67628b7b7f2f133bedb986b1092498 to your computer and use it in GitHub Desktop.
Save ali-master/dd67628b7b7f2f133bedb986b1092498 to your computer and use it in GitHub Desktop.
Simple Multi Tenant Manager
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