Merge branch 'master' into v12.0.0

# Conflicts:
#	integration/injector/e2e/injector.spec.ts
#	integration/microservices/e2e/math-grpc.spec.ts
#	integration/scopes/e2e/request-modules-scope.spec.ts
#	integration/scopes/e2e/request-scope.spec.ts
#	lerna.json
#	package-lock.json
#	package.json
#	packages/core/injector/injector.ts
#	packages/core/test/errors/test/messages.spec.ts
#	packages/core/test/injector/injector.spec.ts
#	sample/01-cats-app/package.json
#	sample/02-gateways/package.json
#	sample/03-microservices/package.json
#	sample/04-grpc/package.json
#	sample/05-sql-typeorm/package.json
#	sample/06-mongoose/package.json
#	sample/07-sequelize/package.json
#	sample/08-webpack/package.json
#	sample/09-babel-example/package.json
#	sample/10-fastify/package.json
#	sample/11-swagger/package.json
#	sample/12-graphql-schema-first/package.json
#	sample/13-mongo-typeorm/package.json
#	sample/14-mongoose-base/package.json
#	sample/15-mvc/package.json
#	sample/16-gateways-ws/package.json
#	sample/17-mvc-fastify/package.json
#	sample/18-context/package.json
#	sample/19-auth-jwt/package.json
#	sample/20-cache/package.json
#	sample/21-serializer/package.json
#	sample/22-graphql-prisma/package.json
#	sample/23-graphql-code-first/package.json
#	sample/24-serve-static/package.json
#	sample/25-dynamic-modules/package.json
#	sample/26-queues/package.json
#	sample/27-scheduling/package.json
#	sample/28-sse/package.json
#	sample/29-file-upload/package.json
#	sample/30-event-emitter/package.json
#	sample/31-graphql-federation-code-first/gateway/package.json
#	sample/31-graphql-federation-code-first/posts-application/package.json
#	sample/31-graphql-federation-code-first/users-application/package.json
#	sample/32-graphql-federation-schema-first/gateway/package.json
#	sample/32-graphql-federation-schema-first/posts-application/package.json
#	sample/32-graphql-federation-schema-first/users-application/package.json
#	sample/33-graphql-mercurius/package.json
#	sample/36-hmr-esm/package.json
This commit is contained in:
Kamil Myśliwiec committed 2026-05-22 09:58:02 +02:00
commit c470ddf882
44 files changed
+1710 -719

No files matched your search

+1
View File
@@ -129,6 +129,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<tr>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://memory2.co/" target="_blank"><img src="https://images.opencollective.com/memory-squared/bbe37f5/avatar/256.png?height=50" width="50" valign="middle" /></a></td>
</tr>
</table>
+2 -2
View File
@@ -51,7 +51,7 @@ services:
zookeeper:
container_name: test-zookeeper
hostname: zookeeper
image: confluentinc/cp-zookeeper:7.9.6
image: confluentinc/cp-zookeeper:7.9.7
ports:
- "2181:2181"
environment:
@@ -60,7 +60,7 @@ services:
kafka:
container_name: test-kafka
hostname: kafka
image: confluentinc/cp-kafka:8.2.0
image: confluentinc/cp-kafka:8.2.1
depends_on:
- zookeeper
ports:
+61
View File
@@ -1,6 +1,8 @@
import { Global, Injectable, Module, Scope } from '@nestjs/common';
import { RuntimeException } from '@nestjs/core/errors/exceptions/runtime.exception.js';
import { UnknownDependenciesException } from '@nestjs/core/errors/exceptions/unknown-dependencies.exception.js';
import { UnknownExportException } from '@nestjs/core/errors/exceptions/unknown-export.exception.js';
import { NestFactory } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import {
DYNAMIC_TOKEN,
@@ -17,6 +19,65 @@ import {
} from '../src/self-injection/self-injection-provider.module.js';
describe('Injector', () => {
describe('when the same provider class is declared in multiple modules', () => {
it('should bootstrap when module-local provider instances have non-static dependency trees', async () => {
@Injectable()
class FirstGlobalDependency {}
@Injectable()
class SecondGlobalDependency {}
@Injectable({ scope: Scope.REQUEST })
class RequestScopedDependency {}
@Global()
@Module({
providers: [FirstGlobalDependency, SecondGlobalDependency],
exports: [FirstGlobalDependency, SecondGlobalDependency],
})
class GlobalDepsModule {}
@Injectable()
class SharedService {
constructor(
public readonly firstDependency: FirstGlobalDependency,
public readonly secondDependency: SecondGlobalDependency,
public readonly requestScopedDependency: RequestScopedDependency,
) {}
}
@Module({
providers: [SharedService, RequestScopedDependency],
exports: [SharedService],
})
class ModuleA {}
@Module({
providers: [SharedService, RequestScopedDependency],
})
class ModuleB {}
@Module({
imports: [GlobalDepsModule, ModuleA, ModuleB],
})
class AppModule {}
let timeout: NodeJS.Timeout;
const app = await Promise.race([
NestFactory.create(AppModule, { logger: false }),
new Promise<never>((_, reject) => {
timeout = setTimeout(
() => reject(new Error('Application initialization timed out')),
1000,
);
}),
]);
clearTimeout(timeout!);
await app.close();
});
});
describe('when "providers" and "exports" properties are inconsistent', () => {
it(`should fail with "UnknownExportException"`, async () => {
const builder = Test.createTestingModule({
@@ -0,0 +1,246 @@
import {
Controller,
Get,
Inject,
Injectable,
Module,
Scope,
} from '@nestjs/common';
import { REQUEST, createContextId } from '@nestjs/core';
import { Test, TestingModule } from '@nestjs/testing';
import { expect } from 'chai';
describe('Parallel request-scoped resolution', () => {
const ASYNC_REQUEST_SCOPED = 'ASYNC_REQUEST_SCOPED';
const SAME_CONTEXT_REQUEST_SCOPED = 'SAME_CONTEXT_REQUEST_SCOPED';
const OVERLAP_REQUEST_COUNT = 1000;
@Injectable()
class ResolveInflightGate {
private arrivalCount = 0;
private released = false;
private waitingResolver?: () => void;
async waitForOverlap() {
if (this.released) {
return;
}
this.arrivalCount++;
if (this.arrivalCount === 1) {
await new Promise<void>(resolve => {
this.waitingResolver = resolve;
});
return;
}
this.released = true;
this.waitingResolver?.();
this.waitingResolver = undefined;
}
async yieldOneTurn() {
await new Promise<void>(resolve => setImmediate(resolve));
}
}
@Injectable({ scope: Scope.REQUEST })
class RequestReader {
constructor(
@Inject(REQUEST)
public readonly request: {
requestId: string;
},
) {}
}
const AsyncRequestScopedProvider = {
provide: ASYNC_REQUEST_SCOPED,
scope: Scope.REQUEST,
inject: [RequestReader, ResolveInflightGate],
useFactory: async (
requestReader: RequestReader,
inflightGate: ResolveInflightGate,
) => {
await inflightGate.waitForOverlap();
return {
requestId: requestReader.request.requestId,
};
},
};
const SameContextRequestScopedProvider = {
provide: SAME_CONTEXT_REQUEST_SCOPED,
scope: Scope.REQUEST,
inject: [RequestReader, ResolveInflightGate],
useFactory: async (
requestReader: RequestReader,
inflightGate: ResolveInflightGate,
) => {
await inflightGate.yieldOneTurn();
return {
requestId: requestReader.request.requestId,
};
},
};
@Injectable()
class BubbledProvider {
constructor(
@Inject(ASYNC_REQUEST_SCOPED)
public readonly payload: {
requestId: string;
},
public readonly requestReader: RequestReader,
) {}
}
@Injectable()
class SameContextBubbledProvider {
constructor(
@Inject(SAME_CONTEXT_REQUEST_SCOPED)
public readonly payload: {
requestId: string;
},
public readonly requestReader: RequestReader,
) {}
}
@Controller({ path: 'parallel', scope: Scope.REQUEST })
class ParallelController {
constructor(
@Inject(ASYNC_REQUEST_SCOPED)
public readonly payload: {
requestId: string;
},
public readonly requestReader: RequestReader,
) {}
@Get()
getPayload() {
return this.payload;
}
}
@Module({
controllers: [ParallelController],
providers: [
ResolveInflightGate,
RequestReader,
AsyncRequestScopedProvider,
SameContextRequestScopedProvider,
BubbledProvider,
SameContextBubbledProvider,
],
})
class ParallelModule {}
let testingModule: TestingModule;
const prepareContexts = (moduleRef: TestingModule, count: number) =>
Array.from({ length: count }, (_, index) => {
const contextId = createContextId();
const request = { requestId: `request-${index}` };
moduleRef.registerRequestByContextId(request, contextId);
return { contextId, request };
});
beforeEach(async () => {
testingModule = await Test.createTestingModule({
imports: [ParallelModule],
}).compile();
});
it('should isolate request-scoped factory providers across overlapping context resolutions', async function () {
this.timeout(20000);
const contexts = prepareContexts(testingModule, OVERLAP_REQUEST_COUNT);
const payloads = await Promise.all(
contexts.map(({ contextId }) =>
testingModule.resolve<{ requestId: string }>(
ASYNC_REQUEST_SCOPED,
contextId,
),
),
);
expect(payloads).to.deep.equal(
contexts.map(({ request }) => ({ requestId: request.requestId })),
);
expect(new Set(payloads).size).to.equal(contexts.length);
});
it('should bubble singleton providers to request scope during overlapping context resolutions', async function () {
this.timeout(20000);
const contexts = prepareContexts(testingModule, OVERLAP_REQUEST_COUNT);
const providers = await Promise.all(
contexts.map(({ contextId }) =>
testingModule.resolve(BubbledProvider, contextId),
),
);
expect(new Set(providers).size).to.equal(contexts.length);
expect(providers.map(provider => provider.payload)).to.deep.equal(
contexts.map(({ request }) => ({ requestId: request.requestId })),
);
expect(
providers.map(provider => provider.requestReader.request.requestId),
).to.deep.equal(contexts.map(({ request }) => request.requestId));
});
it('should reuse a request-scoped factory provider for overlapping resolutions in the same context', async function () {
this.timeout(20000);
const [{ contextId, request }] = prepareContexts(testingModule, 1);
const providers = await Promise.all(
Array.from({ length: OVERLAP_REQUEST_COUNT }, () =>
testingModule.resolve<{ requestId: string }>(
SAME_CONTEXT_REQUEST_SCOPED,
contextId,
),
),
);
expect(new Set(providers).size).to.equal(1);
expect(providers[0]).to.deep.equal({ requestId: request.requestId });
});
it('should reuse a bubbled provider for overlapping resolutions in the same context', async function () {
this.timeout(20000);
const [{ contextId, request }] = prepareContexts(testingModule, 1);
const providers = await Promise.all(
Array.from({ length: OVERLAP_REQUEST_COUNT }, () =>
testingModule.resolve(SameContextBubbledProvider, contextId),
),
);
expect(new Set(providers).size).to.equal(1);
expect(providers[0].payload).to.deep.equal({
requestId: request.requestId,
});
expect(providers[0].requestReader.request).to.equal(request);
});
it('should isolate request-scoped controllers during overlapping context resolutions', async function () {
this.timeout(20000);
const contexts = prepareContexts(testingModule, OVERLAP_REQUEST_COUNT);
const controllers = await Promise.all(
contexts.map(({ contextId }) =>
testingModule.resolve(ParallelController, contextId),
),
);
expect(new Set(controllers).size).to.equal(contexts.length);
expect(controllers.map(controller => controller.payload)).to.deep.equal(
contexts.map(({ request }) => ({ requestId: request.requestId })),
);
expect(
controllers.map(controller => controller.requestReader.request.requestId),
).to.deep.equal(contexts.map(({ request }) => request.requestId));
});
});
@@ -0,0 +1,235 @@
import {
ArgumentsHost,
Catch,
Controller,
ExceptionFilter,
Get,
Inject,
Injectable,
Module,
Scope,
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import type { FastifyRequest } from 'fastify';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
describe('Request-scoped factory provider', () => {
const REQUEST_SCOPED_FACTORY = 'REQUEST_SCOPED_FACTORY';
const REQUEST_COUNT = 1000;
@Catch()
class RecordingFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const response = host.switchToHttp().getResponse<{
status: (code: number) => unknown;
send: (body: unknown) => unknown;
}>();
response.status(500);
response.send({
statusCode: 500,
message:
exception instanceof Error
? exception.message
: 'Internal server error',
});
}
}
class AuthenticatedTenant {
constructor(
public readonly tenantName: string,
public readonly clientName: string,
) {}
}
@Injectable()
class JwtStrategyService {
validate(request: FastifyRequest) {
const tenantId = (request.headers['x-tenant-id'] as string) ?? 'tenant-1';
const clientId = (request.headers['x-client-id'] as string) ?? 'client-1';
return {
claim: {
tenantId,
clientId,
},
isValid: true,
};
}
}
@Injectable()
class ConfigService {
get(_key: string) {
return 'config-value';
}
}
@Injectable()
class InflightGate {
private arrivalCount = 0;
private released = false;
private waitingResolver?: () => void;
async waitForOverlap() {
if (this.released) {
return;
}
this.arrivalCount++;
if (this.arrivalCount === 1) {
await new Promise<void>(resolve => {
this.waitingResolver = resolve;
});
return;
}
this.released = true;
this.waitingResolver?.();
this.waitingResolver = undefined;
}
}
const AuthenticatedTenantProvider = {
provide: AuthenticatedTenant,
inject: [JwtStrategyService, REQUEST, ConfigService],
scope: Scope.REQUEST,
useFactory: async (
strategy: JwtStrategyService,
request: FastifyRequest,
_config: ConfigService,
) => {
const verification = strategy.validate(request);
return new AuthenticatedTenant(
verification.claim.tenantId,
verification.claim.clientId,
);
},
};
@Injectable({ scope: Scope.REQUEST })
class ActorService {
constructor(@Inject(REQUEST) private readonly request: FastifyRequest) {}
public getActor() {
return (this.request.headers['x-actor-id'] as string) ?? 'system';
}
}
@Module({
providers: [JwtStrategyService, ConfigService, AuthenticatedTenantProvider],
exports: [AuthenticatedTenant],
})
class AuthModule {}
@Module({
providers: [ActorService],
exports: [ActorService],
})
class ActorModule {}
@Controller()
class TestController {
constructor(
@Inject(REQUEST_SCOPED_FACTORY)
private readonly requestScopedFactory: {
actorId: string;
tenantId: string;
},
) {}
@Get('/posts')
public getValue() {
return this.requestScopedFactory;
}
}
@Module({
imports: [AuthModule, ActorModule],
controllers: [TestController],
providers: [
InflightGate,
{
provide: REQUEST_SCOPED_FACTORY,
scope: Scope.REQUEST,
inject: [AuthenticatedTenant, ActorService, InflightGate],
useFactory: async (
authenticatedTenant: AuthenticatedTenant,
actorService: ActorService,
inflightGate: InflightGate,
) => {
await inflightGate.waitForOverlap();
return {
actorId: actorService.getActor() ?? '',
tenantId: authenticatedTenant.tenantName,
};
},
},
],
})
class TestModule {}
let app: NestFastifyApplication;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [TestModule],
}).compile();
app = moduleRef.createNestApplication<NestFastifyApplication>(
new FastifyAdapter(),
{ logger: false },
);
app.useGlobalFilters(new RecordingFilter());
await app.init();
});
afterEach(async () => {
await app.close();
});
it('should resolve all injected dependencies across overlapping requests', async function () {
this.timeout(20000);
const responses = await Promise.all(
Array.from({ length: REQUEST_COUNT }, (_, index) =>
app.inject({
method: 'GET',
url: '/posts',
headers: {
'x-actor-id': `actor-${index}`,
'x-client-id': `client-${index}`,
'x-tenant-id': `tenant-${index}`,
},
}),
),
);
const failures = responses.flatMap((response, index) => {
if (response.statusCode !== 200) {
return [
{
body: response.body,
index,
statusCode: response.statusCode,
},
];
}
return [];
});
expect(failures).to.deep.equal([]);
const payloads = responses.map(response => JSON.parse(response.body));
expect(payloads).to.deep.equal(
Array.from({ length: REQUEST_COUNT }, (_, index) => ({
actorId: `actor-${index}`,
tenantId: `tenant-${index}`,
})),
);
});
});
@@ -13,6 +13,7 @@ import { TransientService } from '../src/scoped/transient.service.js';
import { Transient3Service } from '../src/scoped/transient3.service.js';
describe('Scoped Instances', () => {
const OVERLAP_REQUEST_COUNT = 1000;
let testingModule: TestingModule;
beforeEach(async () => {
@@ -72,6 +73,46 @@ describe('Scoped Instances', () => {
expect(request3.request).toBe(requestProvider);
});
it('should isolate request-scoped providers across parallel context resolutions', async () => {
const contexts = Array.from(
{ length: OVERLAP_REQUEST_COUNT },
(_, index) => {
const contextId = createContextId();
const requestProvider = { host: `parallel-host-${index}` };
testingModule.registerRequestByContextId(requestProvider, contextId);
return { contextId, requestProvider };
},
);
const providers = await Promise.all(
contexts.map(({ contextId }) =>
testingModule.resolve(ScopedService, contextId),
),
);
expect(new Set(providers).size).toBe(contexts.length);
expect(providers.map(provider => provider.request)).toEqual(
contexts.map(({ requestProvider }) => requestProvider),
);
}, 20000);
it('should reuse request-scoped providers for parallel resolutions in the same context', async () => {
const contextId = createContextId();
const requestProvider = { host: 'shared-host' };
testingModule.registerRequestByContextId(requestProvider, contextId);
const providers = await Promise.all(
Array.from({ length: OVERLAP_REQUEST_COUNT }, () =>
testingModule.resolve(ScopedService, contextId),
),
);
expect(new Set(providers).size).toBe(1);
expect(providers[0].request).toBe(requestProvider);
}, 20000);
it('should dynamically resolve request-scoped controller', async () => {
const request1 = await testingModule.resolve(ScopedController);
const request2 = await testingModule.resolve(ScopedController);
@@ -82,6 +123,20 @@ describe('Scoped Instances', () => {
expect(request3).not.toBe(request2);
});
it('should isolate request-scoped controllers across parallel context resolutions', async () => {
const contexts = Array.from({ length: OVERLAP_REQUEST_COUNT }, () =>
createContextId(),
);
const controllers = await Promise.all(
contexts.map(contextId =>
testingModule.resolve(ScopedController, contextId),
),
);
expect(new Set(controllers).size).toBe(contexts.length);
}, 20000);
it('should throw an exception when "get()" method is used for scoped providers', () => {
expect(() => testingModule.get(ScopedController)).toThrow(
InvalidClassScopeException,
@@ -13,8 +13,7 @@
"transient": false,
"exported": false,
"token": "LoggingInterceptor",
"subtype": "interceptor",
"initTime": 0
"subtype": "interceptor"
}
},
"55510001": {
@@ -30,8 +29,7 @@
"scope": 2,
"transient": false,
"exported": false,
"token": "UsersService",
"initTime": 0
"token": "UsersService"
}
},
"91819333": {
@@ -306,8 +304,7 @@
"scope": 2,
"transient": false,
"exported": true,
"token": "HelperService",
"initTime": 0
"token": "HelperService"
}
},
"941957564": {
@@ -356,8 +353,7 @@
"scope": 2,
"transient": false,
"exported": false,
"token": "META",
"initTime": 0
"token": "META"
}
},
"1010833816": {
@@ -542,7 +538,6 @@
"transient": false,
"exported": false,
"token": "HelloController",
"initTime": 0,
"enhancers": [
{
"id": "-2010060263",
@@ -692,8 +687,7 @@
"transient": false,
"exported": false,
"token": "UserByIdPipe",
"subtype": "pipe",
"initTime": 0
"subtype": "pipe"
}
},
"1919157847": {
@@ -709,8 +703,7 @@
"scope": 2,
"transient": false,
"exported": true,
"token": "REQUEST",
"initTime": 0
"token": "REQUEST"
}
},
"2018972420": {
@@ -786,8 +779,7 @@
"scope": 1,
"transient": true,
"exported": true,
"token": "INQUIRER",
"initTime": 0
"token": "INQUIRER"
}
},
"-26938366": {
@@ -1103,8 +1095,7 @@
"scope": 0,
"transient": false,
"exported": false,
"token": "HelloModule",
"initTime": 0
"token": "HelloModule"
}
},
"-1166465801": {
@@ -1152,8 +1143,7 @@
"scope": 2,
"transient": false,
"exported": false,
"token": "HelloService",
"initTime": 0
"token": "HelloService"
}
},
"-2010060263": {
@@ -1246,8 +1236,7 @@
"scope": 2,
"transient": false,
"exported": false,
"token": "DurableService",
"initTime": 0
"token": "DurableService"
}
},
"-848516688": {
@@ -1262,8 +1251,7 @@
"static": false,
"transient": false,
"exported": false,
"token": "DurableController",
"initTime": 0
"token": "DurableController"
}
},
"-1096226358": {
@@ -1557,8 +1545,7 @@
"static": false,
"transient": false,
"exported": false,
"token": "RequestChainService",
"initTime": 0
"token": "RequestChainService"
}
},
"-1927427604": {
@@ -1574,7 +1561,6 @@
"transient": false,
"exported": false,
"token": "RequestChainController",
"initTime": 0,
"enhancers": [
{
"id": "18347652",
@@ -2031,22 +2017,6 @@
},
"id": "1059069749"
},
"1142343545": {
"source": "-1758187461",
"target": "1004276345",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "HelloModule",
"sourceClassName": "HelloService",
"targetClassName": "Meta",
"sourceClassToken": "HelloService",
"targetClassToken": "META",
"targetModuleName": "HelloModule",
"keyOrIndex": 0,
"injectionType": "constructor"
},
"id": "1142343545"
},
"1178436405": {
"source": "1893794611",
"target": "55510001",
@@ -2098,22 +2068,6 @@
},
"id": "1325313063"
},
"1497653915": {
"source": "55510001",
"target": "1004276345",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "HelloModule",
"sourceClassName": "UsersService",
"targetClassName": "Meta",
"sourceClassToken": "UsersService",
"targetClassToken": "META",
"targetModuleName": "HelloModule",
"keyOrIndex": 0,
"injectionType": "constructor"
},
"id": "1497653915"
},
"1615322974": {
"source": "-1469176692",
"target": "555176277",
@@ -2213,6 +2167,22 @@
},
"id": "-21463590"
},
"-2146943494": {
"source": "-234035039",
"target": "928565345",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "RequestChainModule",
"sourceClassName": "RequestChainService",
"targetClassName": "HelperService",
"sourceClassToken": "RequestChainService",
"targetClassToken": "HelperService",
"targetModuleName": "HelperModule",
"keyOrIndex": 0,
"injectionType": "constructor"
},
"id": "-2146943494"
},
"-2105726668": {
"source": "-1803759743",
"target": "1010833816",
@@ -2229,40 +2199,6 @@
},
"id": "-2105726668"
},
"-1657371464": {
"source": "-1673986099",
"target": "1919157847",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "DurableModule",
"sourceClassName": "DurableService",
"targetClassName": "REQUEST",
"sourceClassToken": "DurableService",
"targetClassToken": "REQUEST",
"targetModuleName": "InternalCoreModule",
"keyOrIndex": 0,
"injectionType": "constructor",
"internal": true
},
"id": "-1657371464"
},
"-1303681274": {
"source": "928565345",
"target": "1919157847",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "HelperModule",
"sourceClassName": "HelperService",
"targetClassName": "REQUEST",
"sourceClassToken": "HelperService",
"targetClassToken": "REQUEST",
"targetModuleName": "InternalCoreModule",
"keyOrIndex": 0,
"injectionType": "constructor",
"internal": true
},
"id": "-1303681274"
},
"-831049991": {
"source": "594986539",
"target": "-1721730431",
@@ -2279,6 +2215,22 @@
},
"id": "-831049991"
},
"-1816180282": {
"source": "-848516688",
"target": "-1673986099",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "DurableModule",
"sourceClassName": "DurableController",
"targetClassName": "DurableService",
"sourceClassToken": "DurableController",
"targetClassToken": "DurableService",
"targetModuleName": "DurableModule",
"keyOrIndex": 0,
"injectionType": "constructor"
},
"id": "-1816180282"
},
"-886102564": {
"source": "208171089",
"target": "671882984",
@@ -2296,22 +2248,6 @@
},
"id": "-886102564"
},
"-2146943494": {
"source": "-234035039",
"target": "928565345",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "RequestChainModule",
"sourceClassName": "RequestChainService",
"targetClassName": "HelperService",
"sourceClassToken": "RequestChainService",
"targetClassToken": "HelperService",
"targetModuleName": "HelperModule",
"keyOrIndex": 0,
"injectionType": "constructor"
},
"id": "-2146943494"
},
"-2003045613": {
"source": "-377928898",
"target": "-616397055",
@@ -2344,22 +2280,6 @@
},
"id": "-881420795"
},
"-1816180282": {
"source": "-848516688",
"target": "-1673986099",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "DurableModule",
"sourceClassName": "DurableController",
"targetClassName": "DurableService",
"sourceClassToken": "DurableController",
"targetClassToken": "DurableService",
"targetModuleName": "DurableModule",
"keyOrIndex": 0,
"injectionType": "constructor"
},
"id": "-1816180282"
},
"-180350082": {
"source": "-1550892030",
"target": "-593595711",
@@ -13,8 +13,7 @@
"transient": false,
"exported": false,
"token": "LoggingInterceptor",
"subtype": "interceptor",
"initTime": 0
"subtype": "interceptor"
}
},
"55510001": {
@@ -30,8 +29,7 @@
"scope": 2,
"transient": false,
"exported": false,
"token": "UsersService",
"initTime": 0
"token": "UsersService"
}
},
"91819333": {
@@ -306,8 +304,7 @@
"scope": 2,
"transient": false,
"exported": true,
"token": "HelperService",
"initTime": 0
"token": "HelperService"
}
},
"941957564": {
@@ -356,8 +353,7 @@
"scope": 2,
"transient": false,
"exported": false,
"token": "META",
"initTime": 0
"token": "META"
}
},
"1010833816": {
@@ -542,7 +538,6 @@
"transient": false,
"exported": false,
"token": "HelloController",
"initTime": 0,
"enhancers": [
{
"id": "-2010060263",
@@ -676,8 +671,7 @@
"transient": false,
"exported": false,
"token": "UserByIdPipe",
"subtype": "pipe",
"initTime": 0
"subtype": "pipe"
}
},
"1919157847": {
@@ -693,8 +687,7 @@
"scope": 2,
"transient": false,
"exported": true,
"token": "REQUEST",
"initTime": 0
"token": "REQUEST"
}
},
"2018972420": {
@@ -770,8 +763,7 @@
"scope": 1,
"transient": true,
"exported": true,
"token": "INQUIRER",
"initTime": 0
"token": "INQUIRER"
}
},
"-26938366": {
@@ -1087,8 +1079,7 @@
"scope": 0,
"transient": false,
"exported": false,
"token": "HelloModule",
"initTime": 0
"token": "HelloModule"
}
},
"-1166465801": {
@@ -1136,8 +1127,7 @@
"scope": 2,
"transient": false,
"exported": false,
"token": "HelloService",
"initTime": 0
"token": "HelloService"
}
},
"-2010060263": {
@@ -1230,8 +1220,7 @@
"scope": 2,
"transient": false,
"exported": false,
"token": "DurableService",
"initTime": 0
"token": "DurableService"
}
},
"-848516688": {
@@ -1246,8 +1235,7 @@
"static": false,
"transient": false,
"exported": false,
"token": "DurableController",
"initTime": 0
"token": "DurableController"
}
},
"-1096226358": {
@@ -1541,8 +1529,7 @@
"static": false,
"transient": false,
"exported": false,
"token": "RequestChainService",
"initTime": 0
"token": "RequestChainService"
}
},
"-1927427604": {
@@ -1558,7 +1545,6 @@
"transient": false,
"exported": false,
"token": "RequestChainController",
"initTime": 0,
"enhancers": [
{
"id": "18347652",
@@ -2015,22 +2001,6 @@
},
"id": "1059069749"
},
"1142343545": {
"source": "-1758187461",
"target": "1004276345",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "HelloModule",
"sourceClassName": "HelloService",
"targetClassName": "Meta",
"sourceClassToken": "HelloService",
"targetClassToken": "META",
"targetModuleName": "HelloModule",
"keyOrIndex": 0,
"injectionType": "constructor"
},
"id": "1142343545"
},
"1178436405": {
"source": "1893794611",
"target": "55510001",
@@ -2082,22 +2052,6 @@
},
"id": "1325313063"
},
"1497653915": {
"source": "55510001",
"target": "1004276345",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "HelloModule",
"sourceClassName": "UsersService",
"targetClassName": "Meta",
"sourceClassToken": "UsersService",
"targetClassToken": "META",
"targetModuleName": "HelloModule",
"keyOrIndex": 0,
"injectionType": "constructor"
},
"id": "1497653915"
},
"1615322974": {
"source": "-1469176692",
"target": "555176277",
@@ -2197,6 +2151,22 @@
},
"id": "-21463590"
},
"-2146943494": {
"source": "-234035039",
"target": "928565345",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "RequestChainModule",
"sourceClassName": "RequestChainService",
"targetClassName": "HelperService",
"sourceClassToken": "RequestChainService",
"targetClassToken": "HelperService",
"targetModuleName": "HelperModule",
"keyOrIndex": 0,
"injectionType": "constructor"
},
"id": "-2146943494"
},
"-2105726668": {
"source": "-1803759743",
"target": "1010833816",
@@ -2213,40 +2183,6 @@
},
"id": "-2105726668"
},
"-1657371464": {
"source": "-1673986099",
"target": "1919157847",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "DurableModule",
"sourceClassName": "DurableService",
"targetClassName": "REQUEST",
"sourceClassToken": "DurableService",
"targetClassToken": "REQUEST",
"targetModuleName": "InternalCoreModule",
"keyOrIndex": 0,
"injectionType": "constructor",
"internal": true
},
"id": "-1657371464"
},
"-1303681274": {
"source": "928565345",
"target": "1919157847",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "HelperModule",
"sourceClassName": "HelperService",
"targetClassName": "REQUEST",
"sourceClassToken": "HelperService",
"targetClassToken": "REQUEST",
"targetModuleName": "InternalCoreModule",
"keyOrIndex": 0,
"injectionType": "constructor",
"internal": true
},
"id": "-1303681274"
},
"-831049991": {
"source": "594986539",
"target": "-1721730431",
@@ -2263,6 +2199,22 @@
},
"id": "-831049991"
},
"-1816180282": {
"source": "-848516688",
"target": "-1673986099",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "DurableModule",
"sourceClassName": "DurableController",
"targetClassName": "DurableService",
"sourceClassToken": "DurableController",
"targetClassToken": "DurableService",
"targetModuleName": "DurableModule",
"keyOrIndex": 0,
"injectionType": "constructor"
},
"id": "-1816180282"
},
"-886102564": {
"source": "208171089",
"target": "671882984",
@@ -2280,22 +2232,6 @@
},
"id": "-886102564"
},
"-2146943494": {
"source": "-234035039",
"target": "928565345",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "RequestChainModule",
"sourceClassName": "RequestChainService",
"targetClassName": "HelperService",
"sourceClassToken": "RequestChainService",
"targetClassToken": "HelperService",
"targetModuleName": "HelperModule",
"keyOrIndex": 0,
"injectionType": "constructor"
},
"id": "-2146943494"
},
"-2003045613": {
"source": "-377928898",
"target": "-616397055",
@@ -2328,22 +2264,6 @@
},
"id": "-881420795"
},
"-1816180282": {
"source": "-848516688",
"target": "-1673986099",
"metadata": {
"type": "class-to-class",
"sourceModuleName": "DurableModule",
"sourceClassName": "DurableController",
"targetClassName": "DurableService",
"sourceClassToken": "DurableController",
"targetClassToken": "DurableService",
"targetModuleName": "DurableModule",
"keyOrIndex": 0,
"injectionType": "constructor"
},
"id": "-1816180282"
},
"-180350082": {
"source": "-1550892030",
"target": "-593595711",
@@ -152,7 +152,7 @@ describe('GRPC transport', () => {
});
});
it(`GRPC with backpressure control`, async function () {
it(`GRPC with backpressure control`, async () => {
// This test hit the gRPC server with 1000 messages, but the server
// has to process large (> 1MB) messages, so it will definitely hit
// issues where writing to the stream needs to be paused until a drain
@@ -169,7 +169,7 @@ describe('GRPC transport', () => {
});
expect(receivedIds).toEqual(expectedIds);
});
}, 30000);
describe('streaming calls that error', () => {
// We want to assert that the application does not crash when an error is encountered with an unhandledRejection
@@ -6,8 +6,10 @@ import { DurableContextIdStrategy } from '../src/durable/durable-context-id.stra
import { DurableModule } from '../src/durable/durable.module.js';
describe('Durable providers', () => {
const OVERLAP_REQUEST_COUNT = 1000;
let server: any;
let app: INestApplication;
let baseUrl: string;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
@@ -16,7 +18,8 @@ describe('Durable providers', () => {
app = moduleRef.createNestApplication();
server = app.getHttpServer();
await app.init();
await app.listen(0);
baseUrl = await app.getUrl();
ContextIdFactory.apply(new DurableContextIdStrategy());
});
@@ -39,6 +42,18 @@ describe('Durable providers', () => {
end(res);
});
const performHttpCallAsync = (
tenantId: number,
endpoint = '/durable',
opts: {
forceError: boolean;
} = { forceError: false },
) =>
request(baseUrl)
.get(endpoint)
.set({ ['x-tenant-id']: String(tenantId) })
.set({ ['x-force-error']: opts.forceError ? 'true' : 'false' });
it(`should share durable providers per tenant`, async () => {
let result: request.Response;
result = await new Promise<request.Response>(resolve =>
@@ -107,6 +122,50 @@ describe('Durable providers', () => {
});
});
it(`should preserve request context across overlapping requests from different tenants`, async () => {
const tenantIds = Array.from(
{ length: OVERLAP_REQUEST_COUNT },
(_, index) => index + 21,
);
const responses = await Promise.all(
tenantIds.map(tenantId =>
performHttpCallAsync(tenantId, '/durable/request-context'),
),
);
expect(responses.map(response => response.statusCode)).toEqual(
tenantIds.map(() => 200),
);
expect(responses.map(response => response.body)).toEqual(
tenantIds.map(tenantId => ({
durableService: String(tenantId),
nonDurableService: String(tenantId),
})),
);
}, 20000);
it(`should reuse the durable subtree across overlapping requests for the same tenant`, async () => {
const tenantId = 31;
const responses = await Promise.all(
Array.from({ length: OVERLAP_REQUEST_COUNT }, () =>
performHttpCallAsync(tenantId),
),
);
const counters = responses
.map(response => Number(response.text.match(/Counter: (\d+)/)?.[1]))
.sort((left, right) => left - right);
expect(responses.map(response => response.statusCode)).toEqual(
Array.from({ length: OVERLAP_REQUEST_COUNT }, () => 200),
);
expect(counters).toEqual(
Array.from({ length: OVERLAP_REQUEST_COUNT }, (_, index) => index + 1),
);
}, 20000);
it(`should not cache durable providers that throw errors`, async () => {
let result: request.Response;
@@ -5,8 +5,10 @@ import { RequestChainModule } from '../src/request-chain/request-chain.module.js
import { RequestChainService } from '../src/request-chain/request-chain.service.js';
describe('Request scope (modules propagation)', () => {
const OVERLAP_REQUEST_COUNT = 1000;
let server;
let app: INestApplication;
let baseUrl: string;
beforeAll(async () => {
const module = await Test.createTestingModule({
@@ -15,7 +17,8 @@ describe('Request scope (modules propagation)', () => {
app = module.createNestApplication();
server = app.getHttpServer();
await app.init();
await app.listen(0);
baseUrl = await app.getUrl();
});
describe('when service from parent module is request scoped', () => {
@@ -37,6 +40,32 @@ describe('Request scope (modules propagation)', () => {
});
});
describe('when service from parent module is request scoped under overlapping requests', () => {
let counterBefore: number;
let responses: request.Response[];
beforeAll(async () => {
counterBefore = RequestChainService.COUNTER;
responses = await Promise.all(
Array.from({ length: OVERLAP_REQUEST_COUNT }, () =>
request(baseUrl).get('/hello'),
),
);
}, 20000);
it('should complete every overlapping request successfully', () => {
expect(responses.map(response => response.status)).toEqual(
Array.from({ length: OVERLAP_REQUEST_COUNT }, () => 200),
);
});
it('should create the request-scoped dependency chain for every overlapping request', () => {
expect(RequestChainService.COUNTER - counterBefore).toBe(
OVERLAP_REQUEST_COUNT,
);
});
});
afterAll(async () => {
await app.close();
});
+67 -1
View File
@@ -16,8 +16,10 @@ class Meta {
}
describe('Request scope', () => {
const OVERLAP_REQUEST_COUNT = 1000;
let server;
let app: INestApplication;
let baseUrl: string;
beforeAll(async () => {
const module = await Test.createTestingModule({
@@ -31,7 +33,8 @@ describe('Request scope', () => {
app = module.createNestApplication();
server = app.getHttpServer();
await app.init();
await app.listen(0);
baseUrl = await app.getUrl();
});
describe('when one service is request scoped', () => {
@@ -76,6 +79,69 @@ describe('Request scope', () => {
});
});
describe('when overlapping requests are handled', () => {
let controllerCounterBefore: number;
let usersCounterBefore: number;
let pipeCounterBefore: number;
let interceptorCounterBefore: number;
let guardCounterBefore: number;
let pipeDataLengthBefore: number;
let interceptorDataLengthBefore: number;
let guardDataLengthBefore: number;
let responses: request.Response[];
beforeAll(async () => {
controllerCounterBefore = HelloController.COUNTER;
usersCounterBefore = UsersService.COUNTER;
pipeCounterBefore = UserByIdPipe.COUNTER;
interceptorCounterBefore = Interceptor.COUNTER;
guardCounterBefore = Guard.COUNTER;
pipeDataLengthBefore = UserByIdPipe.REQUEST_SCOPED_DATA.length;
interceptorDataLengthBefore = Interceptor.REQUEST_SCOPED_DATA.length;
guardDataLengthBefore = Guard.REQUEST_SCOPED_DATA.length;
responses = await Promise.all(
Array.from({ length: OVERLAP_REQUEST_COUNT }, () =>
request(baseUrl).get('/hello'),
),
);
}, 20000);
it('should complete every overlapping request successfully', () => {
expect(responses.map(response => response.status)).toEqual(
Array.from({ length: OVERLAP_REQUEST_COUNT }, () => 200),
);
});
it('should create a request-scoped controller and service for every overlapping request', () => {
expect(HelloController.COUNTER - controllerCounterBefore).toBe(
OVERLAP_REQUEST_COUNT,
);
expect(UsersService.COUNTER - usersCounterBefore).toBe(
OVERLAP_REQUEST_COUNT,
);
});
it('should create request-scoped enhancers for every overlapping request', () => {
expect(UserByIdPipe.COUNTER - pipeCounterBefore).toBe(
OVERLAP_REQUEST_COUNT,
);
expect(Interceptor.COUNTER - interceptorCounterBefore).toBe(
OVERLAP_REQUEST_COUNT,
);
expect(Guard.COUNTER - guardCounterBefore).toBe(OVERLAP_REQUEST_COUNT);
expect(
UserByIdPipe.REQUEST_SCOPED_DATA.slice(pipeDataLengthBefore),
).toEqual(Array.from({ length: OVERLAP_REQUEST_COUNT }, () => 1));
expect(
Interceptor.REQUEST_SCOPED_DATA.slice(interceptorDataLengthBefore),
).toEqual(Array.from({ length: OVERLAP_REQUEST_COUNT }, () => 1));
expect(Guard.REQUEST_SCOPED_DATA.slice(guardDataLengthBefore)).toEqual(
Array.from({ length: OVERLAP_REQUEST_COUNT }, () => 1),
);
});
});
afterAll(async () => {
await app.close();
});
@@ -1,4 +1,5 @@
import { INestApplication, Injectable, Scope } from '@nestjs/common';
import { createContextId } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { NestedTransientModule } from '../src/nested-transient/nested-transient.module.js';
@@ -444,4 +445,148 @@ describe('Transient scope', () => {
await app.close();
});
});
describe('when request-scoped providers have a deeply nested transient chain', () => {
let app: INestApplication;
@Injectable({ scope: Scope.TRANSIENT })
class RequestDepthSixTransient {
public static instanceCount = 0;
public readonly instanceId: number;
constructor() {
RequestDepthSixTransient.instanceCount++;
this.instanceId = RequestDepthSixTransient.instanceCount;
}
}
@Injectable({ scope: Scope.TRANSIENT })
class RequestDepthFiveTransient {
public static instanceCount = 0;
public readonly instanceId: number;
constructor(public readonly next: RequestDepthSixTransient) {
RequestDepthFiveTransient.instanceCount++;
this.instanceId = RequestDepthFiveTransient.instanceCount;
}
}
@Injectable({ scope: Scope.TRANSIENT })
class RequestDepthFourTransient {
constructor(public readonly next: RequestDepthFiveTransient) {}
}
@Injectable({ scope: Scope.TRANSIENT })
class RequestDepthThreeTransient {
constructor(public readonly next: RequestDepthFourTransient) {}
}
@Injectable({ scope: Scope.TRANSIENT })
class RequestDepthTwoTransient {
constructor(public readonly next: RequestDepthThreeTransient) {}
}
@Injectable({ scope: Scope.TRANSIENT })
class RequestDepthOneTransient {
constructor(public readonly next: RequestDepthTwoTransient) {}
}
@Injectable({ scope: Scope.REQUEST })
class RequestScopedParentA {
constructor(public readonly chain: RequestDepthOneTransient) {}
}
@Injectable({ scope: Scope.REQUEST })
class RequestScopedParentB {
constructor(public readonly chain: RequestDepthOneTransient) {}
}
beforeEach(async () => {
RequestDepthFiveTransient.instanceCount = 0;
RequestDepthSixTransient.instanceCount = 0;
const module = await Test.createTestingModule({
providers: [
RequestScopedParentA,
RequestScopedParentB,
RequestDepthOneTransient,
RequestDepthTwoTransient,
RequestDepthThreeTransient,
RequestDepthFourTransient,
RequestDepthFiveTransient,
RequestDepthSixTransient,
],
}).compile();
app = module.createNestApplication();
await app.init();
});
it('should create separate deep transient chains for different request-scoped parents in the same context', async () => {
const contextId = createContextId();
const [parentA, parentB] = await Promise.all([
app.resolve(RequestScopedParentA, contextId),
app.resolve(RequestScopedParentB, contextId),
]);
expect(parentA).not.toBe(parentB);
expect(parentA.chain).not.toBe(parentB.chain);
expect(parentA.chain.next.next.next.next.instanceId).not.toBe(
parentB.chain.next.next.next.next.instanceId,
);
expect(parentA.chain.next.next.next.next.next.instanceId).not.toBe(
parentB.chain.next.next.next.next.next.instanceId,
);
}, 20000);
it('should isolate deep transient chains across overlapping request contexts', async () => {
const contextIds = Array.from({ length: 200 }, () => createContextId());
const parents = await Promise.all(
contextIds.map(contextId =>
app.resolve(RequestScopedParentA, contextId),
),
);
expect(new Set(parents).size).toBe(contextIds.length);
expect(
new Set(
parents.map(parent => parent.chain.next.next.next.next.instanceId),
).size,
).toBe(contextIds.length);
expect(
new Set(
parents.map(
parent => parent.chain.next.next.next.next.next.instanceId,
),
).size,
).toBe(contextIds.length);
}, 20000);
it('should reuse the same request-scoped parent while preserving a single deep transient chain per context', async () => {
const contextId = createContextId();
const parents = await Promise.all(
Array.from({ length: 200 }, () =>
app.resolve(RequestScopedParentA, contextId),
),
);
expect(new Set(parents).size).toBe(1);
expect(
new Set(
parents.map(parent => parent.chain.next.next.next.next.instanceId),
).size,
).toBe(1);
expect(
new Set(
parents.map(
parent => parent.chain.next.next.next.next.next.instanceId,
),
).size,
).toBe(1);
}, 20000);
afterEach(async () => {
await app.close();
});
});
});
+287 -392
View File
File diff suppressed because it is too large. Load diff
+22 -20
View File
@@ -91,11 +91,11 @@
"@fastify/multipart": "9.4.0",
"@fastify/static": "9.0.0",
"@fastify/view": "11.1.1",
"@grpc/grpc-js": "1.14.3",
"@grpc/proto-loader": "0.8.0",
"@nats-io/transport-node": "^3.0.2",
"@nestjs/apollo": "13.2.4",
"@nestjs/graphql": "13.2.4",
"@grpc/grpc-js": "1.14.4",
"@grpc/proto-loader": "0.8.1",
"@nats-io/transport-node": "^3.0.2",
"@nestjs/apollo": "13.4.2",
"@nestjs/graphql": "13.4.2",
"@nestjs/mongoose": "11.0.4",
"@nestjs/typeorm": "11.0.0",
"@types/amqplib": "0.10.8",
@@ -103,8 +103,8 @@
"@types/express": "5.0.6",
"@types/gulp": "4.0.18",
"@types/http-errors": "2.0.5",
"@types/node": "25.2.1",
"@types/supertest": "6.0.3",
"@types/node": "25.9.1",
"@types/supertest": "7.2.0",
"@types/ws": "8.18.1",
"@vitest/coverage-istanbul": "4.1.2",
"@vitest/coverage-v8": "4.1.2",
@@ -135,15 +135,16 @@
"lerna": "9.0.4",
"lerna-changelog": "2.2.0",
"light-my-request": "6.6.0",
"lint-staged": "16.2.7",
"lint-staged": "17.0.5",
"markdown-table": "2.0.0",
"mongoose": "9.1.6",
"mqtt": "5.15.0",
"multer": "2.0.2",
"mysql2": "3.17.1",
"oxlint": "1.58.0",
"prettier": "3.7.4",
"redis": "5.10.0",
"mongoose": "9.6.2",
"mqtt": "5.15.1",
"multer": "2.1.1",
"mysql2": "3.22.3",
"nats": "2.29.3",
"oxlint": "1.58.0",
"prettier": "3.7.4",
"redis": "5.12.1",
"reusify": "1.1.0",
"rxjs-compat": "6.6.7",
"socket.io-client": "4.8.3",
@@ -151,11 +152,12 @@
"ts-morph": "27.0.2",
"ts-node": "10.9.2",
"tsx": "4.19.2",
"typeorm": "0.3.28",
"typescript": "6.0.2",
"vite": "8.0.3",
"vitest": "4.1.2",
"ws": "8.19.0"
"tsconfig-paths": "4.2.0",
"typeorm": "0.3.30",
"typescript": "6.0.2",
"vite": "8.0.3",
"vitest": "4.1.2",
"ws": "8.20.1"
},
"engines": {
"node": ">= 20"
+1
View File
@@ -129,6 +129,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<tr>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://memory2.co/" target="_blank"><img src="https://images.opencollective.com/memory-squared/bbe37f5/avatar/256.png?height=50" width="50" valign="middle" /></a></td>
</tr>
</table>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nestjs/common",
"version": "11.1.20",
"version": "11.1.23",
"description": "Nest - modern, fast, powerful node.js web framework (@common)",
"author": "Kamil Mysliwiec",
"homepage": "https://nestjs.com",
+1
View File
@@ -129,6 +129,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<tr>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://memory2.co/" target="_blank"><img src="https://images.opencollective.com/memory-squared/bbe37f5/avatar/256.png?height=50" width="50" valign="middle" /></a></td>
</tr>
</table>
@@ -2,9 +2,17 @@ import { USING_INVALID_CLASS_AS_A_MODULE_MESSAGE } from '../messages.js';
import { RuntimeException } from './runtime.exception.js';
export class InvalidClassModuleException extends RuntimeException {
constructor(metatypeUsedAsAModule: any, scope: any[]) {
constructor(
metatypeUsedAsAModule: any,
scope: any[],
classKind: 'provider' | 'controller' | 'filter',
) {
super(
USING_INVALID_CLASS_AS_A_MODULE_MESSAGE(metatypeUsedAsAModule, scope),
USING_INVALID_CLASS_AS_A_MODULE_MESSAGE(
metatypeUsedAsAModule,
scope,
classKind,
),
);
}
}
@@ -2,7 +2,12 @@ import { INVALID_MODULE_MESSAGE } from '../messages.js';
import { RuntimeException } from './runtime.exception.js';
export class InvalidModuleException extends RuntimeException {
constructor(parentModule: any, index: number, scope: any[]) {
super(INVALID_MODULE_MESSAGE(parentModule, index, scope));
constructor(
parentModule: any,
index: number,
scope: any[],
receivedValue: unknown,
) {
super(INVALID_MODULE_MESSAGE(parentModule, index, scope, receivedValue));
}
}
+30 -2
View File
@@ -157,11 +157,26 @@ export const INVALID_MODULE_MESSAGE = (
parentModule: any,
index: number,
scope: any[],
receivedValue: unknown,
) => {
const parentModuleName = parentModule?.name || 'module';
let formattedValue: string;
let receivedType: string;
if (receivedValue === null) {
formattedValue = 'null';
receivedType = 'null';
} else if (typeof receivedValue === 'string') {
formattedValue = `"${receivedValue}"`;
receivedType = 'string';
} else {
formattedValue = String(receivedValue);
receivedType = typeof receivedValue;
}
return `Nest cannot create the ${parentModuleName} instance.
Received an unexpected value at index [${index}] of the ${parentModuleName} "imports" array.
The received value \`${formattedValue}\` is of type "${receivedType}".
Scope [${stringifyScope(scope)}]`;
};
@@ -169,11 +184,24 @@ Scope [${stringifyScope(scope)}]`;
export const USING_INVALID_CLASS_AS_A_MODULE_MESSAGE = (
metatypeUsedAsAModule: Type | ForwardReference,
scope: any[],
classKind: 'provider' | 'controller' | 'filter',
) => {
const metatypeNameQuote = `"${getInstanceName(metatypeUsedAsAModule)}"`;
return `Classes annotated with @Injectable(), @Catch(), and @Controller() decorators must not appear in the "imports" array of a module.
Please remove ${metatypeNameQuote} (including forwarded occurrences, if any) from all of the "imports" arrays.
let hint: string;
switch (classKind) {
case 'controller':
hint = `${metatypeNameQuote} is decorated with @Controller() and cannot appear in the "imports" array of a module. Please move ${metatypeNameQuote} to the "controllers" array of the importing module instead.`;
break;
case 'provider':
hint = `${metatypeNameQuote} is decorated with @Injectable() and cannot appear in the "imports" array of a module. Please move ${metatypeNameQuote} to the "providers" array of the importing module instead.`;
break;
case 'filter':
hint = `${metatypeNameQuote} is decorated with @Catch() and cannot appear in the "imports" array of a module. Please move ${metatypeNameQuote} to the "providers" array (using the APP_FILTER token to apply it globally) or apply it via @UseFilters() instead.`;
break;
}
return `${hint}
Scope [${stringifyScope(scope)}]
`;
+44 -8
View File
@@ -205,6 +205,9 @@ export class Injector {
localResolutionContext,
resolutionContext.inquirer,
);
if (!instanceHost.isResolved) {
settlementSignal.complete();
}
} catch (err) {
wrapper.removeInstanceByContextId(
this.getContextId(resolutionContext.contextId, wrapper),
@@ -311,7 +314,10 @@ export class Injector {
) {
const metadata = wrapper.getCtorMetadata();
if (metadata && resolutionContext.contextId !== STATIC_CONTEXT) {
if (
resolutionContext.contextId !== STATIC_CONTEXT &&
this.hasDenseCtorMetadata(wrapper, inject, metadata)
) {
const deps = await this.loadCtorMetadata(
metadata,
resolutionContext.contextId,
@@ -461,21 +467,21 @@ export class Injector {
];
}
public reflectConstructorParams<T>(type: Type<T>): any[] {
public reflectConstructorParams(type: Type<unknown> | Function): any[] {
const paramtypes = [
...(Reflect.getMetadata(PARAMTYPES_METADATA, type) || []),
];
const selfParams = this.reflectSelfParams<T>(type);
const selfParams = this.reflectSelfParams(type);
selfParams.forEach(({ index, param }) => (paramtypes[index] = param));
return Array.from(paramtypes);
}
public reflectOptionalParams<T>(type: Type<T>): any[] {
return Reflect.getOwnMetadata(OPTIONAL_DEPS_METADATA, type) || [];
public reflectOptionalParams(type: Type<unknown> | Function): any[] {
return Reflect.getMetadata(OPTIONAL_DEPS_METADATA, type) || [];
}
public reflectSelfParams<T>(type: Type<T>): any[] {
public reflectSelfParams(type: Type<unknown> | Function): any[] {
return Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, type) || [];
}
@@ -1043,7 +1049,6 @@ export class Injector {
wrapper: InstanceWrapper<Injectable>,
resolutionContext: ResolutionContext,
): boolean {
const isSnapshotGraphCompilation = !!this.options?.snapshot;
const isStaticContext = resolutionContext.contextId === STATIC_CONTEXT;
const hasNoInquirer = !resolutionContext.inquirer;
const isTopLevelStaticTransientOrRequestProvider =
@@ -1062,7 +1067,7 @@ export class Injector {
(isTopLevelStaticTransientOrRequestProvider ||
isStaticInquirerOutsideResolutionContext);
return !isSnapshotGraphCompilation && shouldSkipForStaticBootstrap;
return shouldSkipForStaticBootstrap;
}
/**
@@ -1149,6 +1154,37 @@ export class Injector {
);
}
private hasDenseCtorMetadata<T>(
wrapper: InstanceWrapper<T>,
inject: InjectorDependency[] | undefined,
metadata: InstanceWrapper[] | undefined,
): boolean {
if (!metadata) {
return false;
}
// The fast path requires a fully populated metadata array.
// While another request is still registering dependency metadata,
// sparse entries here would feed request-scoped factories `undefined`.
const expectedDepsLength = !isNil(inject)
? inject.length
: wrapper.metatype
? this.reflectConstructorParams(wrapper.metatype).length
: 0;
if (metadata.length !== expectedDepsLength) {
return false;
}
for (let index = 0; index < expectedDepsLength; index++) {
if (metadata[index] === undefined) {
return false;
}
}
return true;
}
private resolveScopedComponentHost(
item: InstanceWrapper,
contextId: ContextId,
+7
View File
@@ -61,6 +61,7 @@ export class NestApplication
private readonly microservices: any[] = [];
private httpServer: any;
private isListening = false;
private isWsModuleRegistered = false;
constructor(
container: NestContainer,
@@ -169,6 +170,7 @@ export class NestApplication
this.appOptions,
this.httpServer,
);
this.isWsModuleRegistered = true;
}
public async init(): Promise<this> {
@@ -476,6 +478,11 @@ export class NestApplication
}
public useWebSocketAdapter(adapter: WebSocketAdapter): this {
if (this.isWsModuleRegistered) {
this.logger.warn(
'useWebSocketAdapter() was called after WebSocket gateways were already initialized. The provided adapter will be stored but will NOT be applied to existing gateways — they remain bound to the previously installed adapter. To install a custom adapter, call app.useWebSocketAdapter(...) BEFORE app.init() (or app.listen()).',
);
}
this.config.setIoAdapter(adapter);
return this;
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@nestjs/core",
"version": "11.1.20",
"version": "11.1.23",
"description": "Nest - modern, fast, powerful node.js web framework (@core)",
"author": "Kamil Mysliwiec",
"license": "MIT",
@@ -47,7 +47,7 @@
"uid": "2.0.2"
},
"devDependencies": {
"@nestjs/common": "11.1.20"
"@nestjs/common": "11.1.23"
},
"peerDependencies": {
"@nestjs/common": "^11.0.0",
+22 -7
View File
@@ -148,7 +148,12 @@ export class DependenciesScanner {
throw new UndefinedModuleException(moduleDefinition, index, scope);
}
if (!innerModule) {
throw new InvalidModuleException(moduleDefinition, index, scope);
throw new InvalidModuleException(
moduleDefinition,
index,
scope,
innerModule,
);
}
if (ctxRegistry.includes(innerModule)) {
continue;
@@ -186,12 +191,22 @@ export class DependenciesScanner {
? moduleDefinition.forwardRef()
: moduleDefinition;
if (
this.isInjectable(moduleToAdd) ||
this.isController(moduleToAdd) ||
this.isExceptionFilter(moduleToAdd)
) {
throw new InvalidClassModuleException(moduleDefinition, scope);
if (this.isInjectable(moduleToAdd)) {
throw new InvalidClassModuleException(
moduleDefinition,
scope,
'provider',
);
}
if (this.isController(moduleToAdd)) {
throw new InvalidClassModuleException(
moduleDefinition,
scope,
'controller',
);
}
if (this.isExceptionFilter(moduleToAdd)) {
throw new InvalidClassModuleException(moduleDefinition, scope, 'filter');
}
return this.container.addModule(moduleToAdd, scope);
+104 -2
View File
@@ -3,6 +3,7 @@ import {
INVALID_MODULE_MESSAGE,
UNDEFINED_MODULE_MESSAGE,
UNKNOWN_EXPORT_MESSAGE,
USING_INVALID_CLASS_AS_A_MODULE_MESSAGE,
} from '../../../errors/messages.js';
import { Module } from '../../../injector/module.js';
import { stringCleaner } from '../../utils/string.cleaner.js';
@@ -383,15 +384,116 @@ Scope [AppModule -> CatsModule]`);
});
describe('INVALID_MODULE_MESSAGE', () => {
it('should display the module name with the invalid index and scope', () => {
it('should display the received `null` value and its type', () => {
const expectedMessage =
stringCleaner(`Nest cannot create the CatsModule instance.
Received an unexpected value at index [0] of the CatsModule "imports" array.
The received value \`null\` is of type "null".
Scope [AppModule -> CatsModule]`);
const actualMessage = stringCleaner(
INVALID_MODULE_MESSAGE(CatsModule, 0, [AppModule, CatsModule]),
INVALID_MODULE_MESSAGE(CatsModule, 0, [AppModule, CatsModule], null),
);
expect(actualMessage).to.be.eq(expectedMessage);
});
it('should display the received `false` value and its type', () => {
const expectedMessage =
stringCleaner(`Nest cannot create the CatsModule instance.
Received an unexpected value at index [0] of the CatsModule "imports" array.
The received value \`false\` is of type "boolean".
Scope [AppModule -> CatsModule]`);
const actualMessage = stringCleaner(
INVALID_MODULE_MESSAGE(CatsModule, 0, [AppModule, CatsModule], false),
);
expect(actualMessage).to.be.eq(expectedMessage);
});
it('should display the received `0` value and its type', () => {
const expectedMessage =
stringCleaner(`Nest cannot create the CatsModule instance.
Received an unexpected value at index [0] of the CatsModule "imports" array.
The received value \`0\` is of type "number".
Scope [AppModule -> CatsModule]`);
const actualMessage = stringCleaner(
INVALID_MODULE_MESSAGE(CatsModule, 0, [AppModule, CatsModule], 0),
);
expect(actualMessage).to.be.eq(expectedMessage);
});
it('should display the received empty string value and its type', () => {
const expectedMessage =
stringCleaner(`Nest cannot create the CatsModule instance.
Received an unexpected value at index [0] of the CatsModule "imports" array.
The received value \`""\` is of type "string".
Scope [AppModule -> CatsModule]`);
const actualMessage = stringCleaner(
INVALID_MODULE_MESSAGE(CatsModule, 0, [AppModule, CatsModule], ''),
);
expect(actualMessage).to.be.eq(expectedMessage);
});
});
describe('USING_INVALID_CLASS_AS_A_MODULE_MESSAGE', () => {
class FooClass {}
it('should identify a class decorated with @Controller() and direct it to the "controllers" array', () => {
const expectedMessage =
stringCleaner(`"FooClass" is decorated with @Controller() and cannot appear in the "imports" array of a module. Please move "FooClass" to the "controllers" array of the importing module instead.
Scope [AppModule]`);
const actualMessage = stringCleaner(
USING_INVALID_CLASS_AS_A_MODULE_MESSAGE(
FooClass,
[AppModule],
'controller',
),
);
expect(actualMessage).to.be.eq(expectedMessage);
});
it('should identify a class decorated with @Injectable() and direct it to the "providers" array', () => {
const expectedMessage =
stringCleaner(`"FooClass" is decorated with @Injectable() and cannot appear in the "imports" array of a module. Please move "FooClass" to the "providers" array of the importing module instead.
Scope [AppModule]`);
const actualMessage = stringCleaner(
USING_INVALID_CLASS_AS_A_MODULE_MESSAGE(
FooClass,
[AppModule],
'provider',
),
);
expect(actualMessage).to.be.eq(expectedMessage);
});
it('should identify a class decorated with @Catch() and direct it to the "providers" array or @UseFilters()', () => {
const expectedMessage =
stringCleaner(`"FooClass" is decorated with @Catch() and cannot appear in the "imports" array of a module. Please move "FooClass" to the "providers" array (using the APP_FILTER token to apply it globally) or apply it via @UseFilters() instead.
Scope [AppModule]`);
const actualMessage = stringCleaner(
USING_INVALID_CLASS_AS_A_MODULE_MESSAGE(
FooClass,
[AppModule],
'filter',
),
);
expect(actualMessage).toBe(expectedMessage);
+60 -1
View File
@@ -1,4 +1,4 @@
import { Optional } from '@nestjs/common';
import { Optional, Scope } from '@nestjs/common';
import { PARAMTYPES_METADATA } from '@nestjs/common/constants.js';
import { Inject } from '../../../common/decorators/core/inject.decorator.js';
import { Injectable } from '../../../common/decorators/core/injectable.decorator.js';
@@ -205,6 +205,33 @@ describe('Injector', () => {
});
});
describe('resolveConstructorParams', () => {
it('should fall back to resolving each param when ctor metadata is sparse', async () => {
const wrapper = new InstanceWrapper();
const metadata = [new InstanceWrapper()];
metadata.length = 2;
const getCtorMetadataSpy = vi
.spyOn(wrapper, 'getCtorMetadata')
.mockReturnValue(metadata);
const loadCtorMetadataSpy = vi.spyOn(injector, 'loadCtorMetadata');
await injector
.resolveConstructorParams(
wrapper,
null!,
['Provider1', 'Provider2'],
() => {},
{ contextId: { id: 2 } },
)
.catch(() => {});
expect(loadCtorMetadataSpy).not.toHaveBeenCalled();
getCtorMetadataSpy.mockRestore();
loadCtorMetadataSpy.mockRestore();
});
});
describe('loadMiddleware', () => {
let loadInstanceSpy: ReturnType<typeof vi.fn>;
@@ -294,6 +321,38 @@ describe('Injector', () => {
});
});
describe('loadProvider', () => {
@Injectable({ scope: Scope.TRANSIENT })
class TransientProvider {}
it('should not eagerly load a top-level transient provider during snapshot bootstrap', async () => {
const snapshotInjector = new Injector({
preview: false,
snapshot: true,
});
const loadInstance = vi.spyOn(snapshotInjector, 'loadInstance');
const loadEnhancersPerContext = vi.spyOn(
snapshotInjector,
'loadEnhancersPerContext',
);
const wrapper = new InstanceWrapper({
metatype: TransientProvider,
instance: null,
isResolved: false,
scope: Scope.TRANSIENT,
});
const moduleRef = {
providers: new Map([[TransientProvider, wrapper]]),
} as any;
await snapshotInjector.loadProvider(wrapper as any, moduleRef);
expect(loadInstance).not.toHaveBeenCalled();
expect(loadEnhancersPerContext).not.toHaveBeenCalled();
});
});
describe('lookupComponent', () => {
let lookupComponentInImports: ReturnType<typeof vi.fn>;
const metatype = { name: 'test', metatype: { name: 'test' } };
@@ -555,4 +555,44 @@ describe('NestApplicationContext', () => {
expect(instances[2]).toBeInstanceOf(Service3);
});
});
describe('snapshot bootstrap', () => {
it('should not eagerly instantiate an unused transient provider', async () => {
@Injectable({ scope: Scope.TRANSIENT })
class UnusedTransient {
static constructorCalls = 0;
constructor() {
UnusedTransient.constructorCalls++;
}
}
const nestContainer = new NestContainer();
const injector = new Injector({
preview: false,
snapshot: true,
});
const instanceLoader = new InstanceLoader(
nestContainer,
injector,
new GraphInspector(nestContainer),
);
const { moduleRef } = (await nestContainer.addModule(class T {}, []))!;
nestContainer.addProvider(UnusedTransient, moduleRef.token);
const modules = nestContainer.getModules();
await instanceLoader.createInstancesOfDependencies(modules);
expect(UnusedTransient.constructorCalls).to.equal(0);
const appCtx = new NestApplicationContext(nestContainer, {
snapshot: true,
});
const instance = await appCtx.resolve(UnusedTransient);
expect(instance).to.be.instanceOf(UnusedTransient);
expect(UnusedTransient.constructorCalls).to.equal(1);
});
});
});
@@ -151,4 +151,52 @@ describe('NestApplication', () => {
expect((noopHttpAdapter as any).init).toHaveBeenCalledOnce();
});
});
describe('useWebSocketAdapter', () => {
function createInstance(): NestApplication {
const noopHttpAdapter = new NoopHttpAdapter({});
const applicationConfig = new ApplicationConfig();
const container = new NestContainer(applicationConfig);
container.setHttpAdapter(noopHttpAdapter);
return new NestApplication(
container,
noopHttpAdapter,
applicationConfig,
new GraphInspector(container),
{},
);
}
it('should not warn when called before WS module registration', () => {
const instance = createInstance();
const warnSpy = vi.spyOn((instance as any).logger, 'warn');
instance.useWebSocketAdapter({} as any);
expect(warnSpy).not.toHaveBeenCalled();
});
it('should warn when called after WS module registration', () => {
const instance = createInstance();
(instance as any).socketModule = { register: vi.fn() };
instance.registerWsModule();
const warnSpy = vi.spyOn((instance as any).logger, 'warn');
instance.useWebSocketAdapter({} as any);
expect(warnSpy).toHaveBeenCalledOnce();
expect(warnSpy.mock.calls[0]?.[0]).toMatch(
/useWebSocketAdapter\(\) was called after WebSocket gateways were already initialized/,
);
});
it('should still set the adapter on ApplicationConfig even when called too late', () => {
const instance = createInstance();
instance.registerWsModule();
const adapter = { create: () => undefined } as any;
instance.useWebSocketAdapter(adapter);
expect((instance as any).config.getIoAdapter()).toBe(adapter);
});
});
});
+1
View File
@@ -129,6 +129,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<tr>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://memory2.co/" target="_blank"><img src="https://images.opencollective.com/memory-squared/bbe37f5/avatar/256.png?height=50" width="50" valign="middle" /></a></td>
</tr>
</table>
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@nestjs/microservices",
"version": "11.1.20",
"version": "11.1.23",
"description": "Nest - modern, fast, powerful node.js web framework (@microservices)",
"author": "Kamil Mysliwiec",
"license": "MIT",
@@ -29,8 +29,8 @@
"tslib": "2.8.1"
},
"devDependencies": {
"@nestjs/common": "11.1.20",
"@nestjs/core": "11.1.20"
"@nestjs/common": "11.1.23",
"@nestjs/core": "11.1.23"
},
"peerDependencies": {
"@grpc/grpc-js": "*",
+1
View File
@@ -129,6 +129,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<tr>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://memory2.co/" target="_blank"><img src="https://images.opencollective.com/memory-squared/bbe37f5/avatar/256.png?height=50" width="50" valign="middle" /></a></td>
</tr>
</table>
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@nestjs/platform-express",
"version": "11.1.20",
"version": "11.1.23",
"description": "Nest - modern, fast, powerful node.js web framework (@platform-express)",
"author": "Kamil Mysliwiec",
"license": "MIT",
@@ -32,8 +32,8 @@
"tslib": "2.8.1"
},
"devDependencies": {
"@nestjs/common": "11.1.20",
"@nestjs/core": "11.1.20"
"@nestjs/common": "11.1.23",
"@nestjs/core": "11.1.23"
},
"peerDependencies": {
"@nestjs/common": "^11.0.0",
+1
View File
@@ -129,6 +129,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<tr>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://memory2.co/" target="_blank"><img src="https://images.opencollective.com/memory-squared/bbe37f5/avatar/256.png?height=50" width="50" valign="middle" /></a></td>
</tr>
</table>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nestjs/platform-fastify",
"version": "11.1.20",
"version": "11.1.23",
"description": "Nest - modern, fast, powerful node.js web framework (@platform-fastify)",
"author": "Kamil Mysliwiec",
"license": "MIT",
+1
View File
@@ -129,6 +129,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<tr>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://memory2.co/" target="_blank"><img src="https://images.opencollective.com/memory-squared/bbe37f5/avatar/256.png?height=50" width="50" valign="middle" /></a></td>
</tr>
</table>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nestjs/platform-socket.io",
"version": "11.1.20",
"version": "11.1.23",
"description": "Nest - modern, fast, powerful node.js web framework (@platform-socket.io)",
"author": "Kamil Mysliwiec",
"license": "MIT",
+1
View File
@@ -129,6 +129,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<tr>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://memory2.co/" target="_blank"><img src="https://images.opencollective.com/memory-squared/bbe37f5/avatar/256.png?height=50" width="50" valign="middle" /></a></td>
</tr>
</table>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nestjs/platform-ws",
"version": "11.1.20",
"version": "11.1.23",
"description": "Nest - modern, fast, powerful node.js web framework (@platform-ws)",
"author": "Kamil Mysliwiec",
"license": "MIT",
+1
View File
@@ -129,6 +129,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<tr>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://memory2.co/" target="_blank"><img src="https://images.opencollective.com/memory-squared/bbe37f5/avatar/256.png?height=50" width="50" valign="middle" /></a></td>
</tr>
</table>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nestjs/testing",
"version": "11.1.20",
"version": "11.1.23",
"description": "Nest - modern, fast, powerful node.js web framework (@testing)",
"author": "Kamil Mysliwiec",
"license": "MIT",
+1
View File
@@ -129,6 +129,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<tr>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://memory2.co/" target="_blank"><img src="https://images.opencollective.com/memory-squared/bbe37f5/avatar/256.png?height=50" width="50" valign="middle" /></a></td>
</tr>
</table>
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@nestjs/websockets",
"version": "11.1.20",
"version": "11.1.23",
"description": "Nest - modern, fast, powerful node.js web framework (@websockets)",
"author": "Kamil Mysliwiec",
"license": "MIT",
@@ -26,8 +26,8 @@
"tslib": "2.8.1"
},
"devDependencies": {
"@nestjs/common": "11.1.20",
"@nestjs/core": "11.1.20"
"@nestjs/common": "11.1.23",
"@nestjs/core": "11.1.23"
},
"peerDependencies": {
"@nestjs/common": "^11.0.0",
+12 -12
View File
@@ -223,9 +223,9 @@
}
},
"node_modules/@nestjs/common": {
"version": "11.1.20",
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.20.tgz",
"integrity": "sha512-uivaQnTwnfnW1cam8rlh9Xpxvvq+9zKs5ywHWtHRHu1CSQ2FnsLvDewP9Ybhyuf6l075BXKgbixxyW9OKZGpmA==",
"version": "11.1.23",
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.23.tgz",
"integrity": "sha512-qKgEqwQXHIVu8TwiISmgbTrGHAFBsseP86KNolBZwAiHQryinJ5FPiDpp0ZJBBryY+WEMnsqaCa4TSxVuQEWug==",
"license": "MIT",
"dependencies": {
"file-type": "21.3.4",
@@ -254,9 +254,9 @@
}
},
"node_modules/@nestjs/core": {
"version": "11.1.20",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.20.tgz",
"integrity": "sha512-RlaeQctEtSNrc3SxwQDnAxv/R22ww2t5dbP8OMzaghXofUPRCe2dXhXZSlKMkCyCSEWX+tMhESlGRB5rcJhYTQ==",
"version": "11.1.23",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.23.tgz",
"integrity": "sha512-Yd+mVFUilw4A6PzV7tyfiW+zrG2wmRXnFZVmNQA+fl1N0k6km4bhhNboxjLu//dzl+XiZI5AsOHHOTegzvOgNQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -295,9 +295,9 @@
}
},
"node_modules/@nestjs/platform-express": {
"version": "11.1.20",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.20.tgz",
"integrity": "sha512-HFJ/bf189AGziCp7o9DPdPEgj7LXvCWswR3hBNiVCIo3SwOwTIss1BsQWCCJVAtVYTmwIHOI0i0k9dMGXh3Hew==",
"version": "11.1.23",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.23.tgz",
"integrity": "sha512-7TQ9v2Z9lej8btTYK1VUVB3nZr0lXFNFiyI/iOc0jAOg31dHdpAWtnMMm9iUvyAfq/ssw7dsBhjCHOmEJJKr0A==",
"license": "MIT",
"dependencies": {
"cors": "2.8.6",
@@ -316,9 +316,9 @@
}
},
"node_modules/@nestjs/platform-fastify": {
"version": "11.1.20",
"resolved": "https://registry.npmjs.org/@nestjs/platform-fastify/-/platform-fastify-11.1.20.tgz",
"integrity": "sha512-2dmSTnGisXEzzZ6NvdVwL43VRIfWe2v22tgyH/pD/6Qb6vj58Q8zp6nhU43uoxu2sgLOxq+9ZdS6izeUFJRcEQ==",
"version": "11.1.23",
"resolved": "https://registry.npmjs.org/@nestjs/platform-fastify/-/platform-fastify-11.1.23.tgz",
"integrity": "sha512-cLSDwMu2wf0DB8HeUl2M7m7cIZRljaziwFFeXt9elHb863yQh31T8X4tR5I/a3QFXS0b7hJvdJ728G14K1oS2w==",
"license": "MIT",
"dependencies": {
"@fastify/cors": "11.2.0",