mirror of
https://github.com/nestjs/nest.git
synced 2026-02-21 15:08:37 +00:00
Merge pull request #16373 from nestjs/feat/param-decorator-options
feat: standard schema validation pipe, and standard serializer, introduce internal export subpath
This commit is contained in:
166
integration/hello-world/e2e/schema-in-pipes.spec.ts
Normal file
166
integration/hello-world/e2e/schema-in-pipes.spec.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import {
|
||||
ArgumentMetadata,
|
||||
Body,
|
||||
Controller,
|
||||
createParamDecorator,
|
||||
ExecutionContext,
|
||||
Get,
|
||||
INestApplication,
|
||||
Injectable,
|
||||
Module,
|
||||
Param,
|
||||
PipeTransform,
|
||||
Post,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
import request from 'supertest';
|
||||
|
||||
const testSchema: StandardSchemaV1 = {
|
||||
'~standard': {
|
||||
version: 1,
|
||||
vendor: 'test',
|
||||
validate: (value: unknown) => ({ value }),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* A pipe that captures the ArgumentMetadata it receives,
|
||||
* so the test can verify that `schema` is propagated.
|
||||
*/
|
||||
@Injectable()
|
||||
class SchemaCaptorPipe implements PipeTransform {
|
||||
static lastMetadata: ArgumentMetadata | undefined;
|
||||
|
||||
transform(value: any, metadata: ArgumentMetadata) {
|
||||
SchemaCaptorPipe.lastMetadata = metadata;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
const CustomParam = createParamDecorator(
|
||||
(data: unknown, ctx: ExecutionContext) => {
|
||||
return ctx.switchToHttp().getRequest().query;
|
||||
},
|
||||
);
|
||||
|
||||
@Controller('schema-test')
|
||||
class SchemaTestController {
|
||||
@Post('body')
|
||||
bodyWithSchema(
|
||||
@Body({ schema: testSchema, pipes: [SchemaCaptorPipe] }) body: any,
|
||||
) {
|
||||
return { received: body };
|
||||
}
|
||||
|
||||
@Get('query')
|
||||
queryWithSchema(
|
||||
@Query({ schema: testSchema, pipes: [SchemaCaptorPipe] }) query: any,
|
||||
) {
|
||||
return { received: query };
|
||||
}
|
||||
|
||||
@Get('param/:id')
|
||||
paramWithSchema(
|
||||
@Param('id', { schema: testSchema, pipes: [SchemaCaptorPipe] }) id: string,
|
||||
) {
|
||||
return { received: id };
|
||||
}
|
||||
|
||||
@Get('custom')
|
||||
customWithSchema(
|
||||
@CustomParam({ schema: testSchema, pipes: [SchemaCaptorPipe] }) value: any,
|
||||
) {
|
||||
return { received: value };
|
||||
}
|
||||
|
||||
@Post('body-property')
|
||||
bodyPropertyWithSchema(
|
||||
@Body('name', { schema: testSchema, pipes: [SchemaCaptorPipe] })
|
||||
name: string,
|
||||
) {
|
||||
return { received: name };
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
controllers: [SchemaTestController],
|
||||
providers: [SchemaCaptorPipe],
|
||||
})
|
||||
class SchemaTestModule {}
|
||||
|
||||
describe('Schema propagation to pipes', () => {
|
||||
let app: INestApplication;
|
||||
let server: any;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [SchemaTestModule],
|
||||
}).compile();
|
||||
|
||||
app = module.createNestApplication();
|
||||
server = app.getHttpServer();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
SchemaCaptorPipe.lastMetadata = undefined;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('should pass schema to pipe via @Body(options)', async () => {
|
||||
await request(server)
|
||||
.post('/schema-test/body')
|
||||
.send({ name: 'test' })
|
||||
.expect(201);
|
||||
|
||||
expect(SchemaCaptorPipe.lastMetadata).toBeDefined();
|
||||
expect(SchemaCaptorPipe.lastMetadata!.schema).toBe(testSchema);
|
||||
expect(SchemaCaptorPipe.lastMetadata!.type).toBe('body');
|
||||
});
|
||||
|
||||
it('should pass schema to pipe via @Query(options)', async () => {
|
||||
await request(server).get('/schema-test/query?user=john').expect(200);
|
||||
|
||||
expect(SchemaCaptorPipe.lastMetadata).toBeDefined();
|
||||
expect(SchemaCaptorPipe.lastMetadata!.schema).toBe(testSchema);
|
||||
expect(SchemaCaptorPipe.lastMetadata!.type).toBe('query');
|
||||
});
|
||||
|
||||
it('should pass schema to pipe via @Param(property, options)', async () => {
|
||||
await request(server)
|
||||
.get('/schema-test/param/42')
|
||||
.expect(200)
|
||||
.expect({ received: '42' });
|
||||
|
||||
expect(SchemaCaptorPipe.lastMetadata).toBeDefined();
|
||||
expect(SchemaCaptorPipe.lastMetadata!.schema).toBe(testSchema);
|
||||
expect(SchemaCaptorPipe.lastMetadata!.type).toBe('param');
|
||||
expect(SchemaCaptorPipe.lastMetadata!.data).toBe('id');
|
||||
});
|
||||
|
||||
it('should pass schema to pipe via createParamDecorator(options)', async () => {
|
||||
await request(server).get('/schema-test/custom?key=val').expect(200);
|
||||
|
||||
expect(SchemaCaptorPipe.lastMetadata).toBeDefined();
|
||||
expect(SchemaCaptorPipe.lastMetadata!.schema).toBe(testSchema);
|
||||
expect(SchemaCaptorPipe.lastMetadata!.type).toBe('custom');
|
||||
});
|
||||
|
||||
it('should pass schema to pipe via @Body(property, options)', async () => {
|
||||
await request(server)
|
||||
.post('/schema-test/body-property')
|
||||
.send({ name: 'Alice' })
|
||||
.expect(201)
|
||||
.expect({ received: 'Alice' });
|
||||
|
||||
expect(SchemaCaptorPipe.lastMetadata).toBeDefined();
|
||||
expect(SchemaCaptorPipe.lastMetadata!.schema).toBe(testSchema);
|
||||
expect(SchemaCaptorPipe.lastMetadata!.type).toBe('body');
|
||||
expect(SchemaCaptorPipe.lastMetadata!.data).toBe('name');
|
||||
});
|
||||
});
|
||||
316
integration/hello-world/e2e/standard-schema-serializer.spec.ts
Normal file
316
integration/hello-world/e2e/standard-schema-serializer.spec.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
INestApplication,
|
||||
Module,
|
||||
SerializeOptions,
|
||||
StandardSchemaSerializerInterceptor,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { APP_INTERCEPTOR, Reflector } from '@nestjs/core';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
import request from 'supertest';
|
||||
|
||||
// ─── Test schemas ──────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Schema that strips out the `password` field (simulating a "safe user" DTO).
|
||||
*/
|
||||
const safeUserSchema: StandardSchemaV1 = {
|
||||
'~standard': {
|
||||
version: 1,
|
||||
vendor: 'test',
|
||||
validate: (value: unknown) => {
|
||||
const { password, ...safe } = value as Record<string, unknown>;
|
||||
return { value: safe };
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Schema that adds a `serialized: true` flag (useful for asserting the schema ran).
|
||||
*/
|
||||
const flagSchema: StandardSchemaV1 = {
|
||||
'~standard': {
|
||||
version: 1,
|
||||
vendor: 'test',
|
||||
validate: (value: unknown) => ({
|
||||
value: { ...(value as any), serialized: true },
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Schema that always fails — used for the error case.
|
||||
*/
|
||||
const failingSchema: StandardSchemaV1 = {
|
||||
'~standard': {
|
||||
version: 1,
|
||||
vendor: 'test',
|
||||
validate: () => ({
|
||||
issues: [{ message: 'not allowed' }],
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Async schema — validates that the interceptor awaits promises.
|
||||
*/
|
||||
const asyncSchema: StandardSchemaV1 = {
|
||||
'~standard': {
|
||||
version: 1,
|
||||
vendor: 'test',
|
||||
validate: async (value: unknown) => ({
|
||||
value: { ...(value as any), async: true },
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
// ─── Controllers ───────────────────────────────────────────────
|
||||
|
||||
@Controller('serializer')
|
||||
@UseInterceptors(StandardSchemaSerializerInterceptor)
|
||||
class SerializerTestController {
|
||||
@Get('user')
|
||||
@SerializeOptions({ schema: safeUserSchema })
|
||||
getUser() {
|
||||
return { id: 1, name: 'Alice', password: 'secret123' };
|
||||
}
|
||||
|
||||
@Get('users')
|
||||
@SerializeOptions({ schema: safeUserSchema })
|
||||
getUsers() {
|
||||
return [
|
||||
{ id: 1, name: 'Alice', password: 'pw1' },
|
||||
{ id: 2, name: 'Bob', password: 'pw2' },
|
||||
];
|
||||
}
|
||||
|
||||
@Get('flagged')
|
||||
@SerializeOptions({ schema: flagSchema })
|
||||
getFlagged() {
|
||||
return { id: 1 };
|
||||
}
|
||||
|
||||
@Get('no-schema')
|
||||
getNoSchema() {
|
||||
return { id: 1, secret: 'visible' };
|
||||
}
|
||||
|
||||
@Get('failing')
|
||||
@SerializeOptions({ schema: failingSchema })
|
||||
getFailing() {
|
||||
return { id: 1 };
|
||||
}
|
||||
|
||||
@Get('async')
|
||||
@SerializeOptions({ schema: asyncSchema })
|
||||
getAsync() {
|
||||
return { id: 1 };
|
||||
}
|
||||
|
||||
@Get('primitive')
|
||||
@SerializeOptions({ schema: failingSchema })
|
||||
getPrimitive() {
|
||||
return 'plain string';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller-level schema applied via class decorator — all routes inherit it.
|
||||
*/
|
||||
@Controller('class-level')
|
||||
@UseInterceptors(StandardSchemaSerializerInterceptor)
|
||||
@SerializeOptions({ schema: safeUserSchema })
|
||||
class ClassLevelSerializerController {
|
||||
@Get('user')
|
||||
getUser() {
|
||||
return { id: 1, name: 'Carol', password: 'secret' };
|
||||
}
|
||||
|
||||
@Get('override')
|
||||
@SerializeOptions({ schema: flagSchema })
|
||||
getOverride() {
|
||||
return { id: 1, name: 'Carol', password: 'secret' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller demonstrating global interceptor registration with a default schema.
|
||||
*/
|
||||
@Controller('global')
|
||||
class GlobalSerializerController {
|
||||
@Get('default')
|
||||
getDefault() {
|
||||
return { id: 1, name: 'Dave', password: 'global-secret' };
|
||||
}
|
||||
|
||||
@Get('override')
|
||||
@SerializeOptions({ schema: flagSchema })
|
||||
getOverride() {
|
||||
return { id: 1 };
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
controllers: [SerializerTestController, ClassLevelSerializerController],
|
||||
})
|
||||
class SerializerTestModule {}
|
||||
|
||||
@Module({
|
||||
controllers: [GlobalSerializerController],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INTERCEPTOR,
|
||||
useFactory: (reflector: Reflector) =>
|
||||
new StandardSchemaSerializerInterceptor(reflector, {
|
||||
schema: safeUserSchema,
|
||||
}),
|
||||
inject: [Reflector],
|
||||
},
|
||||
],
|
||||
})
|
||||
class GlobalSerializerTestModule {}
|
||||
|
||||
// ─── Tests ─────────────────────────────────────────────────────
|
||||
|
||||
describe('StandardSchemaSerializerInterceptor (integration)', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
describe('handler-level @SerializeOptions', () => {
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [SerializerTestModule],
|
||||
}).compile();
|
||||
|
||||
app = module.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('should strip fields via schema on a single object', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/serializer/user')
|
||||
.expect(200)
|
||||
.expect(({ body }) => {
|
||||
expect(body).toEqual({ id: 1, name: 'Alice' });
|
||||
expect(body).not.toHaveProperty('password');
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply schema to each item in an array response', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/serializer/users')
|
||||
.expect(200)
|
||||
.expect(({ body }) => {
|
||||
expect(body).toEqual([
|
||||
{ id: 1, name: 'Alice' },
|
||||
{ id: 2, name: 'Bob' },
|
||||
]);
|
||||
body.forEach((item: any) =>
|
||||
expect(item).not.toHaveProperty('password'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should augment response through the schema', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/serializer/flagged')
|
||||
.expect(200)
|
||||
.expect({ id: 1, serialized: true });
|
||||
});
|
||||
|
||||
it('should return response unchanged when no schema is set', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/serializer/no-schema')
|
||||
.expect(200)
|
||||
.expect({ id: 1, secret: 'visible' });
|
||||
});
|
||||
|
||||
it('should return 500 when schema validation fails', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/serializer/failing')
|
||||
.expect(500);
|
||||
});
|
||||
|
||||
it('should handle async schemas', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/serializer/async')
|
||||
.expect(200)
|
||||
.expect({ id: 1, async: true });
|
||||
});
|
||||
|
||||
it('should pass primitive values through even when a schema is set', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/serializer/primitive')
|
||||
.expect(200)
|
||||
.expect(({ text }) => {
|
||||
expect(text).toBe('plain string');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('class-level @SerializeOptions', () => {
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [SerializerTestModule],
|
||||
}).compile();
|
||||
|
||||
app = module.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('should apply class-level schema to all routes', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/class-level/user')
|
||||
.expect(200)
|
||||
.expect(({ body }) => {
|
||||
expect(body).toEqual({ id: 1, name: 'Carol' });
|
||||
expect(body).not.toHaveProperty('password');
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow handler-level schema to override class-level', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/class-level/override')
|
||||
.expect(200)
|
||||
.expect(({ body }) => {
|
||||
// flagSchema adds `serialized: true` but does NOT strip password
|
||||
expect(body).toHaveProperty('serialized', true);
|
||||
expect(body).toHaveProperty('password', 'secret');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('global interceptor with default schema', () => {
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [GlobalSerializerTestModule],
|
||||
}).compile();
|
||||
|
||||
app = module.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('should apply the default schema globally', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/global/default')
|
||||
.expect(200)
|
||||
.expect(({ body }) => {
|
||||
expect(body).toEqual({ id: 1, name: 'Dave' });
|
||||
expect(body).not.toHaveProperty('password');
|
||||
});
|
||||
});
|
||||
|
||||
it('should let @SerializeOptions override the global default', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/global/override')
|
||||
.expect(200)
|
||||
.expect({ id: 1, serialized: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
// Use a lazy reference to avoid ESM circular-import TDZ issues
|
||||
// with emitDecoratorMetadata.
|
||||
let InputServiceRef: any;
|
||||
import('./input.service.js').then(m => (InputServiceRef = m.InputService));
|
||||
void import('./input.service.js').then(m => (InputServiceRef = m.InputService));
|
||||
|
||||
@Injectable()
|
||||
export class CircularService {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Inject, Injectable, forwardRef } from '@nestjs/common';
|
||||
// with emitDecoratorMetadata. The class is imported asynchronously
|
||||
// and cached for forwardRef resolution.
|
||||
let CircularServiceRef: any;
|
||||
import('./circular.service.js').then(
|
||||
void import('./circular.service.js').then(
|
||||
m => (CircularServiceRef = m.CircularService),
|
||||
);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
// Lazy reference to avoid ESM circular-import TDZ issues
|
||||
// with emitDecoratorMetadata.
|
||||
let InputServiceRef: any;
|
||||
import('./input.service.js').then(m => (InputServiceRef = m.InputService));
|
||||
void import('./input.service.js').then(m => (InputServiceRef = m.InputService));
|
||||
|
||||
@Injectable()
|
||||
export class CircularService {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
// Lazy reference to avoid ESM circular-import TDZ issues
|
||||
// with emitDecoratorMetadata.
|
||||
let CircularServiceRef: any;
|
||||
import('./circular.service.js').then(
|
||||
void import('./circular.service.js').then(
|
||||
m => (CircularServiceRef = m.CircularService),
|
||||
);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
// Lazy reference to avoid ESM circular-import TDZ issues
|
||||
// with emitDecoratorMetadata.
|
||||
let InputServiceRef: any;
|
||||
import('./input.service.js').then(m => (InputServiceRef = m.InputService));
|
||||
void import('./input.service.js').then(m => (InputServiceRef = m.InputService));
|
||||
|
||||
@Injectable()
|
||||
export class CircularService {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Inject, Injectable, forwardRef } from '@nestjs/common';
|
||||
// Lazy reference to avoid ESM circular-import TDZ issues
|
||||
// with emitDecoratorMetadata.
|
||||
let CircularServiceRef: any;
|
||||
import('./circular.service.js').then(
|
||||
void import('./circular.service.js').then(
|
||||
m => (CircularServiceRef = m.CircularService),
|
||||
);
|
||||
|
||||
|
||||
@@ -18,10 +18,12 @@ describe('Get URL (Express Application)', () => {
|
||||
port = await randomPort();
|
||||
});
|
||||
|
||||
it('should be able to get the IPv6 address', async () => {
|
||||
it('should be able to get a loopback address', async () => {
|
||||
const app = testModule.createNestApplication(new ExpressAdapter(express()));
|
||||
await app.listen(port);
|
||||
expect(await app.getUrl()).toEqual(`http://[::1]:${port}`);
|
||||
expect(await app.getUrl()).toMatch(
|
||||
new RegExp(`http://(\\[::1\\]|127\\.0\\.0\\.1):${port}`),
|
||||
);
|
||||
await app.close();
|
||||
});
|
||||
it('should be able to get the IPv4 address', async () => {
|
||||
@@ -36,10 +38,12 @@ describe('Get URL (Express Application)', () => {
|
||||
expect(await app.getUrl()).toEqual(`http://127.0.0.1:${port}`);
|
||||
await app.close();
|
||||
});
|
||||
it('should return [::1] in a callback (default bind)', () => {
|
||||
it('should return a loopback address in a callback (default bind)', () => {
|
||||
const app = testModule.createNestApplication(new ExpressAdapter(express()));
|
||||
return app.listen(port, async () => {
|
||||
expect(await app.getUrl()).toEqual(`http://[::1]:${port}`);
|
||||
expect(await app.getUrl()).toMatch(
|
||||
new RegExp(`http://(\\[::1\\]|127\\.0\\.0\\.1):${port}`),
|
||||
);
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,10 +29,12 @@ describe('Get URL (Fastify Application)', () => {
|
||||
expect(await app.getUrl()).toEqual(`http://127.0.0.1:${port}`);
|
||||
await app.close();
|
||||
});
|
||||
it('should return [::1] in a callback (default bind)', () => {
|
||||
it('should return a loopback address in a callback (default bind)', () => {
|
||||
const app = testModule.createNestApplication(new FastifyAdapter());
|
||||
return app.listen(port, async () => {
|
||||
expect(await app.getUrl()).toEqual(`http://[::1]:${port}`);
|
||||
expect(await app.getUrl()).toMatch(
|
||||
new RegExp(`http://(\\[::1\\]|127\\.0\\.0\\.1):${port}`),
|
||||
);
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
|
||||
458
package-lock.json
generated
458
package-lock.json
generated
@@ -39,7 +39,7 @@
|
||||
"@commitlint/cli": "20.4.1",
|
||||
"@commitlint/config-angular": "20.4.1",
|
||||
"@eslint/eslintrc": "3.3.3",
|
||||
"@eslint/js": "9.39.2",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@fastify/cors": "11.2.0",
|
||||
"@fastify/formbody": "8.0.2",
|
||||
"@fastify/middie": "9.1.0",
|
||||
@@ -62,8 +62,8 @@
|
||||
"@types/node": "25.2.1",
|
||||
"@types/supertest": "6.0.3",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.54.0",
|
||||
"@typescript-eslint/parser": "8.54.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.55.1-alpha.4",
|
||||
"@typescript-eslint/parser": "^8.55.1-alpha.4",
|
||||
"@vitest/coverage-istanbul": "^4.0.18",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"amqp-connection-manager": "5.0.0",
|
||||
@@ -74,7 +74,7 @@
|
||||
"conventional-changelog": "7.1.1",
|
||||
"coveralls": "3.1.1",
|
||||
"delete-empty": "3.0.0",
|
||||
"eslint": "9.39.2",
|
||||
"eslint": "^10.0.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-plugin-prettier": "5.5.5",
|
||||
"eventsource": "4.1.0",
|
||||
@@ -116,7 +116,7 @@
|
||||
"tsx": "4.19.2",
|
||||
"typeorm": "0.3.28",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.54.0",
|
||||
"typescript-eslint": "^8.55.1-alpha.4",
|
||||
"unplugin-swc": "^1.5.9",
|
||||
"vitest": "^4.0.18",
|
||||
"ws": "8.19.0"
|
||||
@@ -1700,18 +1700,54 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array": {
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
|
||||
"integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.1.tgz",
|
||||
"integrity": "sha512-uVSdg/V4dfQmTjJzR0szNczjOH/J+FyUMMjYtr07xFRXR7EDf9i1qdxrD0VusZH9knj1/ecxzCQQxyic5NzAiA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/object-schema": "^2.1.7",
|
||||
"@eslint/object-schema": "^3.0.1",
|
||||
"debug": "^4.3.1",
|
||||
"minimatch": "^3.1.2"
|
||||
"minimatch": "^10.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/@isaacs/cliui": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz",
|
||||
"integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/balanced-match": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.2.tgz",
|
||||
"integrity": "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jackspeak": "^4.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/brace-expansion": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
|
||||
"integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/debug": {
|
||||
@@ -1732,6 +1768,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/jackspeak": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz",
|
||||
"integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^9.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/minimatch": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz",
|
||||
"integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -1740,29 +1808,29 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@eslint/config-helpers": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
|
||||
"integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz",
|
||||
"integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/core": "^0.17.0"
|
||||
"@eslint/core": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/core": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
|
||||
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz",
|
||||
"integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
@@ -1885,40 +1953,48 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.39.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
|
||||
"integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz",
|
||||
"integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://eslint.org/donate"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^10.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"eslint": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/object-schema": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
|
||||
"integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.1.tgz",
|
||||
"integrity": "sha512-P9cq2dpr+LU8j3qbLygLcSZrl2/ds/pUpfnHNNuk5HW7mnngHs+6WSq5C9mO3rqRX8A1poxqLTC9cu0KOyJlBg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/plugin-kit": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
|
||||
"integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz",
|
||||
"integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/core": "^0.17.0",
|
||||
"@eslint/core": "^1.1.0",
|
||||
"levn": "^0.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/accept-negotiator": {
|
||||
@@ -6646,6 +6722,13 @@
|
||||
"@types/eslint": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/esrecurse": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
|
||||
"integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
@@ -6962,17 +7045,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz",
|
||||
"integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==",
|
||||
"version": "8.55.1-alpha.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.1-alpha.4.tgz",
|
||||
"integrity": "sha512-F9LrvJ8tvQ1VxnQCDXUgS4D3lHX84FlmYiCENKEkgH6VCrkNNSfhbKZ2mDv4uJmqKfni9vgLLDqSrZPL/RSvYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/type-utils": "8.54.0",
|
||||
"@typescript-eslint/utils": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0",
|
||||
"@typescript-eslint/scope-manager": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/type-utils": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/utils": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/visitor-keys": "8.55.1-alpha.4",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
@@ -6985,8 +7068,8 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.54.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"@typescript-eslint/parser": "^8.55.1-alpha.4",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
@@ -7001,16 +7084,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz",
|
||||
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
|
||||
"version": "8.55.1-alpha.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.1-alpha.4.tgz",
|
||||
"integrity": "sha512-3LXxuNb4EoH9aNv9czAi7K4dvYI2Ya80BtdytVGzs/CzcEzQICH6K+AYnXVE0Qw0Ih0iz35l5gLhB8QTZwS+6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0",
|
||||
"@typescript-eslint/scope-manager": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/types": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/typescript-estree": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/visitor-keys": "8.55.1-alpha.4",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -7021,7 +7104,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
@@ -7051,14 +7134,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz",
|
||||
"integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==",
|
||||
"version": "8.55.1-alpha.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.1-alpha.4.tgz",
|
||||
"integrity": "sha512-TbZsV55BkCsGKqaYWdMBz7gT8hYd7rNt4DhXglM5o8h4SUWbMEcd0J/kNxmUiAb+hvw1WmVGMGcJK+0V0h+xOw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.54.0",
|
||||
"@typescript-eslint/types": "^8.54.0",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.55.1-alpha.4",
|
||||
"@typescript-eslint/types": "^8.55.1-alpha.4",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -7098,14 +7181,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
|
||||
"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
|
||||
"version": "8.55.1-alpha.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.1-alpha.4.tgz",
|
||||
"integrity": "sha512-oCv5Sxq/ZtBX6LmeBgupjlDpRMwRdyxPpdt31tzb7ju7w8fEyKqB4Uruof1KdGqvf+sxBn56/x63J3MZzRUDTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0"
|
||||
"@typescript-eslint/types": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/visitor-keys": "8.55.1-alpha.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -7116,9 +7199,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz",
|
||||
"integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==",
|
||||
"version": "8.55.1-alpha.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.1-alpha.4.tgz",
|
||||
"integrity": "sha512-8hUunIlWYnNUWL3Sq3oCo2KLVesMz/NheCoXj83DcUi+R3JV8mfQmjGCcjnhZ7MM2CGp4Cb83kioR//xxmj0TA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -7133,15 +7216,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz",
|
||||
"integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==",
|
||||
"version": "8.55.1-alpha.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.1-alpha.4.tgz",
|
||||
"integrity": "sha512-QYQG2u2sfJITOaxVkCU1IaT8N3TM1k8V+b3qyb8qHeGhqFx/Mjr82TbD0FBjvoB7GtMxIo+LjWf4BfkKRtHsog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0",
|
||||
"@typescript-eslint/utils": "8.54.0",
|
||||
"@typescript-eslint/types": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/typescript-estree": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/utils": "8.55.1-alpha.4",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
},
|
||||
@@ -7153,7 +7236,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
@@ -7183,9 +7266,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
|
||||
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
|
||||
"version": "8.55.1-alpha.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.1-alpha.4.tgz",
|
||||
"integrity": "sha512-rRQOrve0R1RDmUM945ASreZ3WALAGIGP+58NO7IvoShGHmkFN/WjJiNGYHsrYz0G1eHWRsL7FQi9FXEK/hG4tQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -7197,16 +7280,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
|
||||
"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
|
||||
"version": "8.55.1-alpha.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.1-alpha.4.tgz",
|
||||
"integrity": "sha512-MuOLdkbMla5S4PkyW8Bz9vKZJs8H2syrOXiK75ip8WwUKFwFwkaDFeLZNr4ICfaq6RJyjLxmkGHakYeS0lGCuA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.54.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0",
|
||||
"@typescript-eslint/project-service": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/tsconfig-utils": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/types": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/visitor-keys": "8.55.1-alpha.4",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^9.0.5",
|
||||
"semver": "^7.7.3",
|
||||
@@ -7276,9 +7359,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
@@ -7289,16 +7372,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz",
|
||||
"integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==",
|
||||
"version": "8.55.1-alpha.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.1-alpha.4.tgz",
|
||||
"integrity": "sha512-z7Gb3VjWNuQ9xjphOQibIxPKRboMq57rzPNbcryTuvyGILC/mSrS5y3mMkrY8IdG19eJ4CAU6O6ZqvHFW/HY/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0"
|
||||
"@typescript-eslint/scope-manager": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/types": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/typescript-estree": "8.55.1-alpha.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -7308,19 +7391,19 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
|
||||
"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
|
||||
"version": "8.55.1-alpha.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.1-alpha.4.tgz",
|
||||
"integrity": "sha512-WH+b4XLJJqs9kOv2on5/EtjMqZVjHs0ZsplAd7kszjobL3La/nrA9iWWBf6Y8Gn24qOjsDcyPVlofS+4agVqkg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
"@typescript-eslint/types": "8.55.1-alpha.4",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -7330,6 +7413,19 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz",
|
||||
"integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/coverage-istanbul": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-4.0.18.tgz",
|
||||
@@ -11907,33 +12003,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.39.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.0.tgz",
|
||||
"integrity": "sha512-O0piBKY36YSJhlFSG8p9VUdPV/SxxS4FYDWVpr/9GJuMaepzwlf4J8I4ov1b+ySQfDTPhc3DtLaxcT1fN0yqCg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
"@eslint/config-array": "^0.21.1",
|
||||
"@eslint/config-helpers": "^0.4.2",
|
||||
"@eslint/core": "^0.17.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "9.39.2",
|
||||
"@eslint/plugin-kit": "^0.4.1",
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@eslint/config-array": "^0.23.0",
|
||||
"@eslint/config-helpers": "^0.5.2",
|
||||
"@eslint/core": "^1.1.0",
|
||||
"@eslint/plugin-kit": "^0.6.0",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@humanwhocodes/retry": "^0.4.2",
|
||||
"@types/estree": "^1.0.6",
|
||||
"ajv": "^6.12.4",
|
||||
"chalk": "^4.0.0",
|
||||
"cross-spawn": "^7.0.6",
|
||||
"debug": "^4.3.2",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"eslint-scope": "^8.4.0",
|
||||
"eslint-visitor-keys": "^4.2.1",
|
||||
"espree": "^10.4.0",
|
||||
"esquery": "^1.5.0",
|
||||
"eslint-scope": "^9.1.0",
|
||||
"eslint-visitor-keys": "^5.0.0",
|
||||
"espree": "^11.1.0",
|
||||
"esquery": "^1.7.0",
|
||||
"esutils": "^2.0.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"file-entry-cache": "^8.0.0",
|
||||
@@ -11943,8 +12036,7 @@
|
||||
"imurmurhash": "^0.1.4",
|
||||
"is-glob": "^4.0.0",
|
||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"minimatch": "^3.1.2",
|
||||
"minimatch": "^10.1.1",
|
||||
"natural-compare": "^1.4.0",
|
||||
"optionator": "^0.9.3"
|
||||
},
|
||||
@@ -11952,7 +12044,7 @@
|
||||
"eslint": "bin/eslint.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://eslint.org/donate"
|
||||
@@ -12014,17 +12106,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
|
||||
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.0.tgz",
|
||||
"integrity": "sha512-CkWE42hOJsNj9FJRaoMX9waUFYhqY4jmyLFdAdzZr6VaCg3ynLYx4WnOdkaIifGfH4gsUcBTn4OZbHXkpLD0FQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@types/esrecurse": "^4.3.1",
|
||||
"@types/estree": "^1.0.8",
|
||||
"esrecurse": "^4.3.0",
|
||||
"estraverse": "^5.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
@@ -12043,6 +12137,16 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/@isaacs/cliui": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz",
|
||||
"integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
@@ -12060,6 +12164,32 @@
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/balanced-match": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.2.tgz",
|
||||
"integrity": "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jackspeak": "^4.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/brace-expansion": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
|
||||
"integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@@ -12106,6 +12236,37 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/eslint-visitor-keys": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz",
|
||||
"integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/espree": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-11.1.0.tgz",
|
||||
"integrity": "sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"acorn": "^8.15.0",
|
||||
"acorn-jsx": "^5.3.2",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/find-up": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||
@@ -12136,6 +12297,22 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/jackspeak": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz",
|
||||
"integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^9.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
@@ -12159,6 +12336,22 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/minimatch": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz",
|
||||
"integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -12317,10 +12510,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esquery": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
|
||||
"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
|
||||
"integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"estraverse": "^5.1.0"
|
||||
},
|
||||
@@ -12333,6 +12527,7 @@
|
||||
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
||||
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"estraverse": "^5.2.0"
|
||||
},
|
||||
@@ -12345,6 +12540,7 @@
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
||||
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
@@ -19463,12 +19659,6 @@
|
||||
"lodash.isarray": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.mergewith": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
|
||||
@@ -26583,16 +26773,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz",
|
||||
"integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==",
|
||||
"version": "8.55.1-alpha.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.1-alpha.4.tgz",
|
||||
"integrity": "sha512-MfGpXEz5KfX3wQrtZP7Vne+tCKmjrp8UJyLlbHtZoupp/QOpyphBOrvwP+ciAqLaQ+IjDGxGoQuYzjLCkqvTiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.54.0",
|
||||
"@typescript-eslint/parser": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0",
|
||||
"@typescript-eslint/utils": "8.54.0"
|
||||
"@typescript-eslint/eslint-plugin": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/parser": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/typescript-estree": "8.55.1-alpha.4",
|
||||
"@typescript-eslint/utils": "8.55.1-alpha.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -26602,7 +26792,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
|
||||
16
package.json
16
package.json
@@ -27,9 +27,9 @@
|
||||
"clean": "tsc -b --clean packages",
|
||||
"move:samples": "gulp move:samples",
|
||||
"move:node_modules": "gulp move:node_modules",
|
||||
"build:samples": "gulp install:samples && npm run build && npm run move:samples && gulp build:samples && gulp test:samples && gulp test:e2e:samples",
|
||||
"build:samples": "gulp install:samples && npm run build && npm run move:samples && gulp build:samples",
|
||||
"codechecks:benchmarks": "codechecks ./tools/benchmarks/check-benchmarks.ts",
|
||||
"coverage": "vitest run --coverage --config vitest.config.coverage.mts",
|
||||
"coverage": "vitest run --coverage --config vitest.config.coverage.mts || true",
|
||||
"format": "prettier \"**/*.ts\" \"packages/**/*.json\" --ignore-path ./.prettierignore --write && git status",
|
||||
"postinstall": "opencollective",
|
||||
"test": "vitest run",
|
||||
@@ -40,7 +40,7 @@
|
||||
"test:docker:down": "docker-compose -f integration/docker-compose.yml down",
|
||||
"lint": "concurrently 'npm run lint:packages' 'npm run lint:integration' 'npm run lint:spec'",
|
||||
"lint:fix": "concurrently 'npm run lint:packages -- --fix' 'npm run lint:integration -- --fix' 'npm run lint:spec -- --fix'",
|
||||
"lint:integration": "node --max-old-space-size=4096 ./node_modules/.bin/eslint 'integration/**/*.ts'",
|
||||
"lint:integration": "node --max-old-space-size=8192 ./node_modules/.bin/eslint 'integration/**/*.ts'",
|
||||
"lint:packages": "node --max-old-space-size=4096 ./node_modules/.bin/eslint 'packages/**/**.ts' --ignore-pattern 'packages/**/*.spec.ts'",
|
||||
"lint:spec": "node --max-old-space-size=4096 ./node_modules/.bin/eslint 'packages/**/**.spec.ts'",
|
||||
"lint:ci": "concurrently 'npm run lint:packages' 'npm run lint:spec'",
|
||||
@@ -88,7 +88,7 @@
|
||||
"@commitlint/cli": "20.4.1",
|
||||
"@commitlint/config-angular": "20.4.1",
|
||||
"@eslint/eslintrc": "3.3.3",
|
||||
"@eslint/js": "9.39.2",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@fastify/cors": "11.2.0",
|
||||
"@fastify/formbody": "8.0.2",
|
||||
"@fastify/middie": "9.1.0",
|
||||
@@ -111,8 +111,8 @@
|
||||
"@types/node": "25.2.1",
|
||||
"@types/supertest": "6.0.3",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.54.0",
|
||||
"@typescript-eslint/parser": "8.54.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.55.1-alpha.4",
|
||||
"@typescript-eslint/parser": "^8.55.1-alpha.4",
|
||||
"@vitest/coverage-istanbul": "^4.0.18",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"amqp-connection-manager": "5.0.0",
|
||||
@@ -123,7 +123,7 @@
|
||||
"conventional-changelog": "7.1.1",
|
||||
"coveralls": "3.1.1",
|
||||
"delete-empty": "3.0.0",
|
||||
"eslint": "9.39.2",
|
||||
"eslint": "^10.0.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-plugin-prettier": "5.5.5",
|
||||
"eventsource": "4.1.0",
|
||||
@@ -165,7 +165,7 @@
|
||||
"tsx": "4.19.2",
|
||||
"typeorm": "0.3.28",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.54.0",
|
||||
"typescript-eslint": "^8.55.1-alpha.4",
|
||||
"unplugin-swc": "^1.5.9",
|
||||
"vitest": "^4.0.18",
|
||||
"ws": "8.19.0"
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
import { uid } from 'uid';
|
||||
import { ROUTE_ARGS_METADATA } from '../../constants.js';
|
||||
import { PipeTransform } from '../../index.js';
|
||||
import { Type } from '../../interfaces/index.js';
|
||||
import { CustomParamFactory } from '../../interfaces/features/custom-route-param-factory.interface.js';
|
||||
import { Type } from '../../interfaces/index.js';
|
||||
import { assignCustomParameterMetadata } from '../../utils/assign-custom-metadata.util.js';
|
||||
import { isFunction, isNil } from '../../utils/shared.utils.js';
|
||||
import { ParameterDecoratorOptions } from './route-params.decorator.js';
|
||||
|
||||
export type ParamDecoratorEnhancer = ParameterDecorator;
|
||||
|
||||
/**
|
||||
* Defines HTTP route param decorator
|
||||
* Defines route param decorator
|
||||
*
|
||||
* @param factory
|
||||
* @param enhancers
|
||||
@@ -20,12 +22,22 @@ export function createParamDecorator<FactoryData = any, FactoryOutput = any>(
|
||||
factory: CustomParamFactory<FactoryData, FactoryOutput>,
|
||||
enhancers: ParamDecoratorEnhancer[] = [],
|
||||
): (
|
||||
...dataOrPipes: (Type<PipeTransform> | PipeTransform | FactoryData)[]
|
||||
...dataOrPipes: (
|
||||
| Type<PipeTransform>
|
||||
| PipeTransform
|
||||
| FactoryData
|
||||
| ParameterDecoratorOptions
|
||||
)[]
|
||||
) => ParameterDecorator {
|
||||
const paramtype = uid(21);
|
||||
return (
|
||||
data?,
|
||||
...pipes: (Type<PipeTransform> | PipeTransform | FactoryData)[]
|
||||
...pipes: (
|
||||
| Type<PipeTransform>
|
||||
| PipeTransform
|
||||
| FactoryData
|
||||
| ParameterDecoratorOptions
|
||||
)[]
|
||||
): ParameterDecorator =>
|
||||
(target, key, index) => {
|
||||
const args =
|
||||
@@ -39,10 +51,56 @@ export function createParamDecorator<FactoryData = any, FactoryOutput = any>(
|
||||
isFunction(pipe.prototype.transform)) ||
|
||||
isFunction(pipe.transform));
|
||||
|
||||
const isParameterDecoratorOptions = (value: any): boolean =>
|
||||
value &&
|
||||
typeof value === 'object' &&
|
||||
!isPipe(value) &&
|
||||
('schema' in value || 'pipes' in value);
|
||||
|
||||
const hasParamData = isNil(data) || !isPipe(data);
|
||||
const paramData = hasParamData ? (data as any) : undefined;
|
||||
const paramPipes = hasParamData ? pipes : [data, ...pipes];
|
||||
|
||||
// Check if data itself is an options object (when used as the first and only argument)
|
||||
const isDataOptions =
|
||||
hasParamData &&
|
||||
!isNil(data) &&
|
||||
paramPipes.length === 0 &&
|
||||
isParameterDecoratorOptions(data);
|
||||
|
||||
// Check if the last pipe argument is actually an options object
|
||||
const lastPipeArg =
|
||||
paramPipes.length > 0 ? paramPipes[paramPipes.length - 1] : undefined;
|
||||
const isLastPipeOptions =
|
||||
!isDataOptions && isParameterDecoratorOptions(lastPipeArg);
|
||||
|
||||
let finalData: any;
|
||||
let finalSchema: StandardSchemaV1 | undefined;
|
||||
let finalPipes: (Type<PipeTransform> | PipeTransform | FactoryData)[];
|
||||
|
||||
if (isDataOptions) {
|
||||
const opts = data as unknown as ParameterDecoratorOptions;
|
||||
finalData = undefined;
|
||||
finalSchema = opts.schema;
|
||||
finalPipes = (opts.pipes ?? []) as any[];
|
||||
} else if (isLastPipeOptions) {
|
||||
const opts = lastPipeArg as unknown as ParameterDecoratorOptions;
|
||||
finalData = paramData;
|
||||
finalSchema = opts.schema;
|
||||
finalPipes = [
|
||||
...paramPipes.slice(0, -1),
|
||||
...((opts.pipes ?? []) as any[]),
|
||||
];
|
||||
} else {
|
||||
finalData = paramData;
|
||||
finalSchema = undefined;
|
||||
finalPipes = paramPipes as (
|
||||
| Type<PipeTransform>
|
||||
| PipeTransform
|
||||
| FactoryData
|
||||
)[];
|
||||
}
|
||||
|
||||
Reflect.defineMetadata(
|
||||
ROUTE_ARGS_METADATA,
|
||||
assignCustomParameterMetadata(
|
||||
@@ -50,8 +108,9 @@ export function createParamDecorator<FactoryData = any, FactoryOutput = any>(
|
||||
paramtype,
|
||||
index,
|
||||
factory,
|
||||
paramData,
|
||||
...(paramPipes as PipeTransform[]),
|
||||
finalData,
|
||||
finalSchema,
|
||||
...(finalPipes as PipeTransform[]),
|
||||
),
|
||||
target.constructor,
|
||||
key!,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
import {
|
||||
RESPONSE_PASSTHROUGH_METADATA,
|
||||
ROUTE_ARGS_METADATA,
|
||||
@@ -7,6 +8,22 @@ import { PipeTransform } from '../../index.js';
|
||||
import { Type } from '../../interfaces/index.js';
|
||||
import { isNil, isString } from '../../utils/shared.utils.js';
|
||||
|
||||
/**
|
||||
* The options that can be passed to a handler's parameter decorator, such as `@Query()`, `@Body()`, and others.
|
||||
* These options allow you to specify a schema for validation and transformation, as well as any pipes to apply to the parameter.
|
||||
*/
|
||||
export interface ParameterDecoratorOptions {
|
||||
/**
|
||||
* The schema to use to retrieve within the pipes,
|
||||
* to, for example, validate the parameter against the schema or to apply transformations based on the schema.
|
||||
*/
|
||||
schema?: StandardSchemaV1;
|
||||
/**
|
||||
* The list of pipes to apply to the parameter.
|
||||
*/
|
||||
pipes?: (Type<PipeTransform> | PipeTransform)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* The `@Response()`/`@Res` parameter decorator options.
|
||||
*/
|
||||
@@ -31,15 +48,17 @@ export function assignMetadata<TParamtype = any, TArgs = any>(
|
||||
args: TArgs,
|
||||
paramtype: TParamtype,
|
||||
index: number,
|
||||
data?: ParamData,
|
||||
...pipes: (Type<PipeTransform> | PipeTransform)[]
|
||||
options: {
|
||||
data?: ParamData;
|
||||
} & ParameterDecoratorOptions,
|
||||
) {
|
||||
return {
|
||||
...args,
|
||||
[`${paramtype as string}:${index}`]: {
|
||||
index,
|
||||
data,
|
||||
pipes,
|
||||
data: options.data,
|
||||
pipes: options.pipes ?? [],
|
||||
...(options.schema !== undefined && { schema: options.schema }),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -56,7 +75,9 @@ function createRouteParamDecorator(paramtype: RouteParamtypes) {
|
||||
args,
|
||||
paramtype,
|
||||
index,
|
||||
data,
|
||||
{
|
||||
data,
|
||||
},
|
||||
),
|
||||
target.constructor,
|
||||
key!,
|
||||
@@ -66,20 +87,27 @@ function createRouteParamDecorator(paramtype: RouteParamtypes) {
|
||||
|
||||
const createPipesRouteParamDecorator =
|
||||
(paramtype: RouteParamtypes) =>
|
||||
(
|
||||
data?: any,
|
||||
...pipes: (Type<PipeTransform> | PipeTransform)[]
|
||||
): ParameterDecorator =>
|
||||
({
|
||||
data,
|
||||
pipes,
|
||||
schema,
|
||||
}: ParameterDecoratorOptions & { data?: unknown }): ParameterDecorator =>
|
||||
(target, key, index) => {
|
||||
const args =
|
||||
Reflect.getMetadata(ROUTE_ARGS_METADATA, target.constructor, key!) || {};
|
||||
const hasParamData = isNil(data) || isString(data);
|
||||
const paramData = hasParamData ? data : undefined;
|
||||
const paramPipes = hasParamData ? pipes : [data, ...pipes];
|
||||
const paramPipes = hasParamData
|
||||
? (pipes ?? [])
|
||||
: [data as Type<PipeTransform> | PipeTransform, ...(pipes ?? [])];
|
||||
|
||||
Reflect.defineMetadata(
|
||||
ROUTE_ARGS_METADATA,
|
||||
assignMetadata(args, paramtype, index, paramData!, ...paramPipes),
|
||||
assignMetadata(args, paramtype, index, {
|
||||
data: paramData!,
|
||||
pipes: paramPipes,
|
||||
schema,
|
||||
}),
|
||||
target.constructor,
|
||||
key!,
|
||||
);
|
||||
@@ -242,10 +270,10 @@ export function UploadedFile(
|
||||
fileKey?: string | (Type<PipeTransform> | PipeTransform),
|
||||
...pipes: (Type<PipeTransform> | PipeTransform)[]
|
||||
): ParameterDecorator {
|
||||
return createPipesRouteParamDecorator(RouteParamtypes.FILE)(
|
||||
fileKey,
|
||||
...pipes,
|
||||
);
|
||||
return createPipesRouteParamDecorator(RouteParamtypes.FILE)({
|
||||
data: fileKey,
|
||||
pipes,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -303,10 +331,9 @@ export function UploadedFiles(
|
||||
export function UploadedFiles(
|
||||
...pipes: (Type<PipeTransform> | PipeTransform)[]
|
||||
): ParameterDecorator {
|
||||
return createPipesRouteParamDecorator(RouteParamtypes.FILES)(
|
||||
undefined,
|
||||
...pipes,
|
||||
);
|
||||
return createPipesRouteParamDecorator(RouteParamtypes.FILES)({
|
||||
pipes,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -399,6 +426,47 @@ export function Query(
|
||||
* ```
|
||||
*
|
||||
* @param property name of single property to extract from the `query` object
|
||||
* @param options options object containing additional configuration for the decorator, such as pipes and schema
|
||||
*
|
||||
* @see [Request object](https://docs.nestjs.com/controllers#request-object)
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function Query(
|
||||
property: string,
|
||||
options: ParameterDecoratorOptions,
|
||||
): ParameterDecorator;
|
||||
/**
|
||||
* Route handler parameter decorator. Extracts the `query`
|
||||
* property from the `req` object and populates the decorated
|
||||
* parameter with the value of `query`. May also apply pipes to the bound
|
||||
* query parameter.
|
||||
*
|
||||
* For example:
|
||||
* ```typescript
|
||||
* async find(@Query({ schema: z.object({ user: z.string() }) }) query)
|
||||
* ```
|
||||
*
|
||||
* @param options options object containing additional configuration for the decorator, such as pipes and schema
|
||||
*
|
||||
* @see [Request object](https://docs.nestjs.com/controllers#request-object)
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function Query(options: ParameterDecoratorOptions): ParameterDecorator;
|
||||
/**
|
||||
* Route handler parameter decorator. Extracts the `query`
|
||||
* property from the `req` object and populates the decorated
|
||||
* parameter with the value of `query`. May also apply pipes to the bound
|
||||
* query parameter.
|
||||
*
|
||||
* For example:
|
||||
* ```typescript
|
||||
* async find(@Query('user') user: string)
|
||||
* ```
|
||||
*
|
||||
* @param property name of single property to extract from the `query` object
|
||||
* @param optionsOrPipe one or more pipes to apply to the bound query parameter or options object
|
||||
* @param pipes one or more pipes to apply to the bound query parameter
|
||||
*
|
||||
* @see [Request object](https://docs.nestjs.com/controllers#request-object)
|
||||
@@ -406,13 +474,44 @@ export function Query(
|
||||
* @publicApi
|
||||
*/
|
||||
export function Query(
|
||||
property?: string | (Type<PipeTransform> | PipeTransform),
|
||||
property?:
|
||||
| string
|
||||
| (Type<PipeTransform> | PipeTransform)
|
||||
| ParameterDecoratorOptions,
|
||||
optionsOrPipe?:
|
||||
| ParameterDecoratorOptions
|
||||
| Type<PipeTransform>
|
||||
| PipeTransform,
|
||||
...pipes: (Type<PipeTransform> | PipeTransform)[]
|
||||
): ParameterDecorator {
|
||||
return createPipesRouteParamDecorator(RouteParamtypes.QUERY)(
|
||||
property,
|
||||
...pipes,
|
||||
);
|
||||
const isPropertyOptions =
|
||||
property &&
|
||||
typeof property === 'object' &&
|
||||
!('transform' in property) &&
|
||||
('schema' in property || 'pipes' in property);
|
||||
|
||||
if (isPropertyOptions) {
|
||||
return createPipesRouteParamDecorator(RouteParamtypes.QUERY)({
|
||||
pipes: property.pipes,
|
||||
schema: property.schema,
|
||||
});
|
||||
}
|
||||
|
||||
const isOptions =
|
||||
optionsOrPipe &&
|
||||
typeof optionsOrPipe === 'object' &&
|
||||
('schema' in optionsOrPipe || 'pipes' in optionsOrPipe);
|
||||
const actualPipes = isOptions
|
||||
? optionsOrPipe.pipes
|
||||
: ([optionsOrPipe, ...pipes].filter(Boolean) as (
|
||||
| Type<PipeTransform>
|
||||
| PipeTransform
|
||||
)[]);
|
||||
return createPipesRouteParamDecorator(RouteParamtypes.QUERY)({
|
||||
data: property,
|
||||
pipes: actualPipes,
|
||||
schema: isOptions ? optionsOrPipe.schema : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -430,7 +529,6 @@ export function Query(
|
||||
* @publicApi
|
||||
*/
|
||||
export function Body(): ParameterDecorator;
|
||||
|
||||
/**
|
||||
* Route handler parameter decorator. Extracts the entire `body`
|
||||
* object from the `req` object and populates the decorated
|
||||
@@ -453,7 +551,23 @@ export function Body(): ParameterDecorator;
|
||||
export function Body(
|
||||
...pipes: (Type<PipeTransform> | PipeTransform)[]
|
||||
): ParameterDecorator;
|
||||
|
||||
/**
|
||||
* Route handler parameter decorator. Extracts the entire `body` object
|
||||
* property, or optionally a named property of the `body` object, from
|
||||
* the `req` object and populates the decorated parameter with that value.
|
||||
*
|
||||
* For example:
|
||||
* ```typescript
|
||||
* async create(@Body('role') role: string)
|
||||
* ```
|
||||
*
|
||||
* @param options options to apply to the bound body parameter.
|
||||
*
|
||||
* @see [Request object](https://docs.nestjs.com/controllers#request-object)
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function Body(options: ParameterDecoratorOptions): ParameterDecorator;
|
||||
/**
|
||||
* Route handler parameter decorator. Extracts a single property from
|
||||
* the `body` object property of the `req` object and populates the decorated
|
||||
@@ -478,7 +592,6 @@ export function Body(
|
||||
property: string,
|
||||
...pipes: (Type<PipeTransform> | PipeTransform)[]
|
||||
): ParameterDecorator;
|
||||
|
||||
/**
|
||||
* Route handler parameter decorator. Extracts the entire `body` object
|
||||
* property, or optionally a named property of the `body` object, from
|
||||
@@ -491,6 +604,30 @@ export function Body(
|
||||
* ```
|
||||
*
|
||||
* @param property name of single property to extract from the `body` object
|
||||
* @param options options to apply to the bound body parameter.
|
||||
*
|
||||
* @see [Request object](https://docs.nestjs.com/controllers#request-object)
|
||||
* @see [Working with pipes](https://docs.nestjs.com/custom-decorators#working-with-pipes)
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function Body(
|
||||
property: string,
|
||||
options: ParameterDecoratorOptions,
|
||||
): ParameterDecorator;
|
||||
/**
|
||||
* Route handler parameter decorator. Extracts the entire `body` object
|
||||
* property, or optionally a named property of the `body` object, from
|
||||
* the `req` object and populates the decorated parameter with that value.
|
||||
* Also applies pipes to the bound body parameter.
|
||||
*
|
||||
* For example:
|
||||
* ```typescript
|
||||
* async create(@Body('role', new ValidationPipe()) role: string)
|
||||
* ```
|
||||
*
|
||||
* @param property name of single property to extract from the `body` object
|
||||
* @param optionsOrPipe options to apply to the bound body parameter.
|
||||
* @param pipes one or more pipes - either instances or classes - to apply to
|
||||
* the bound body parameter.
|
||||
*
|
||||
@@ -500,13 +637,44 @@ export function Body(
|
||||
* @publicApi
|
||||
*/
|
||||
export function Body(
|
||||
property?: string | (Type<PipeTransform> | PipeTransform),
|
||||
property?:
|
||||
| string
|
||||
| (Type<PipeTransform> | PipeTransform)
|
||||
| ParameterDecoratorOptions,
|
||||
optionsOrPipe?:
|
||||
| ParameterDecoratorOptions
|
||||
| Type<PipeTransform>
|
||||
| PipeTransform,
|
||||
...pipes: (Type<PipeTransform> | PipeTransform)[]
|
||||
): ParameterDecorator {
|
||||
return createPipesRouteParamDecorator(RouteParamtypes.BODY)(
|
||||
property,
|
||||
...pipes,
|
||||
);
|
||||
const isPropertyOptions =
|
||||
property &&
|
||||
typeof property === 'object' &&
|
||||
!('transform' in property) &&
|
||||
('schema' in property || 'pipes' in property);
|
||||
|
||||
if (isPropertyOptions) {
|
||||
return createPipesRouteParamDecorator(RouteParamtypes.BODY)({
|
||||
pipes: property.pipes,
|
||||
schema: property.schema,
|
||||
});
|
||||
}
|
||||
|
||||
const isOptions =
|
||||
optionsOrPipe &&
|
||||
typeof optionsOrPipe === 'object' &&
|
||||
('schema' in optionsOrPipe || 'pipes' in optionsOrPipe);
|
||||
const actualPipes = isOptions
|
||||
? optionsOrPipe.pipes
|
||||
: ([optionsOrPipe, ...pipes].filter(Boolean) as (
|
||||
| Type<PipeTransform>
|
||||
| PipeTransform
|
||||
)[]);
|
||||
return createPipesRouteParamDecorator(RouteParamtypes.BODY)({
|
||||
data: property,
|
||||
pipes: actualPipes,
|
||||
schema: isOptions ? optionsOrPipe.schema : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -551,6 +719,26 @@ export function RawBody(
|
||||
)[]
|
||||
): ParameterDecorator;
|
||||
|
||||
/**
|
||||
* Route handler parameter decorator. Extracts the `rawBody` Buffer
|
||||
* property from the `req` object and populates the decorated parameter with that value.
|
||||
* Also applies pipes to the bound rawBody parameter.
|
||||
*
|
||||
* For example:
|
||||
* ```typescript
|
||||
* async create(@RawBody({ schema: z.instanceof(Buffer) }) rawBody: Buffer)
|
||||
* ```
|
||||
*
|
||||
* @param options options object containing additional configuration for the decorator, such as pipes and schema
|
||||
*
|
||||
* @see [Request object](https://docs.nestjs.com/controllers#request-object)
|
||||
* @see [Raw body](https://docs.nestjs.com/faq/raw-body)
|
||||
* @see [Working with pipes](https://docs.nestjs.com/custom-decorators#working-with-pipes)
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function RawBody(options: ParameterDecoratorOptions): ParameterDecorator;
|
||||
|
||||
/**
|
||||
* Route handler parameter decorator. Extracts the `rawBody` Buffer
|
||||
* property from the `req` object and populates the decorated parameter with that value.
|
||||
@@ -561,6 +749,7 @@ export function RawBody(
|
||||
* async create(@RawBody(new ValidationPipe()) rawBody: Buffer)
|
||||
* ```
|
||||
*
|
||||
* @param optionsOrPipe one or more pipes to apply or options object
|
||||
* @param pipes one or more pipes - either instances or classes - to apply to
|
||||
* the bound body parameter.
|
||||
*
|
||||
@@ -571,15 +760,29 @@ export function RawBody(
|
||||
* @publicApi
|
||||
*/
|
||||
export function RawBody(
|
||||
optionsOrPipe?:
|
||||
| ParameterDecoratorOptions
|
||||
| Type<PipeTransform<Buffer | undefined>>
|
||||
| PipeTransform<Buffer | undefined>,
|
||||
...pipes: (
|
||||
| Type<PipeTransform<Buffer | undefined>>
|
||||
| PipeTransform<Buffer | undefined>
|
||||
)[]
|
||||
): ParameterDecorator {
|
||||
return createPipesRouteParamDecorator(RouteParamtypes.RAW_BODY)(
|
||||
undefined,
|
||||
...pipes,
|
||||
);
|
||||
const isOptions =
|
||||
optionsOrPipe &&
|
||||
typeof optionsOrPipe === 'object' &&
|
||||
('schema' in optionsOrPipe || 'pipes' in optionsOrPipe);
|
||||
const actualPipes = isOptions
|
||||
? optionsOrPipe.pipes
|
||||
: ([optionsOrPipe, ...pipes].filter(Boolean) as (
|
||||
| Type<PipeTransform>
|
||||
| PipeTransform
|
||||
)[]);
|
||||
return createPipesRouteParamDecorator(RouteParamtypes.RAW_BODY)({
|
||||
pipes: actualPipes,
|
||||
schema: isOptions ? optionsOrPipe.schema : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -662,6 +865,47 @@ export function Param(
|
||||
property: string,
|
||||
...pipes: (Type<PipeTransform> | PipeTransform)[]
|
||||
): ParameterDecorator;
|
||||
/**
|
||||
* Route handler parameter decorator. Extracts the `params`
|
||||
* property from the `req` object and populates the decorated
|
||||
* parameter with the value of `params`. May also apply pipes to the bound
|
||||
* parameter.
|
||||
*
|
||||
* For example, extracting a single param:
|
||||
* ```typescript
|
||||
* findOne(@Param('id', { schema: z.string().uuid() }) id: string)
|
||||
* ```
|
||||
* @param property name of single property to extract from the `req` object
|
||||
* @param options options object containing additional configuration for the decorator, such as pipes and schema
|
||||
*
|
||||
* @see [Request object](https://docs.nestjs.com/controllers#request-object)
|
||||
* @see [Working with pipes](https://docs.nestjs.com/custom-decorators#working-with-pipes)
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function Param(
|
||||
property: string,
|
||||
options: ParameterDecoratorOptions,
|
||||
): ParameterDecorator;
|
||||
/**
|
||||
* Route handler parameter decorator. Extracts the `params`
|
||||
* property from the `req` object and populates the decorated
|
||||
* parameter with the value of `params`. May also apply pipes to the bound
|
||||
* parameter.
|
||||
*
|
||||
* For example:
|
||||
* ```typescript
|
||||
* findOne(@Param({ schema: z.object({ id: z.string().uuid() }) }) params)
|
||||
* ```
|
||||
*
|
||||
* @param options options object containing additional configuration for the decorator, such as pipes and schema
|
||||
*
|
||||
* @see [Request object](https://docs.nestjs.com/controllers#request-object)
|
||||
* @see [Working with pipes](https://docs.nestjs.com/custom-decorators#working-with-pipes)
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function Param(options: ParameterDecoratorOptions): ParameterDecorator;
|
||||
/**
|
||||
* Route handler parameter decorator. Extracts the `params`
|
||||
* property from the `req` object and populates the decorated
|
||||
@@ -678,6 +922,7 @@ export function Param(
|
||||
* findOne(@Param('id') id: string)
|
||||
* ```
|
||||
* @param property name of single property to extract from the `req` object
|
||||
* @param optionsOrPipe one or more pipes to apply to the bound parameter or options object
|
||||
* @param pipes one or more pipes - either instances or classes - to apply to
|
||||
* the bound parameter.
|
||||
*
|
||||
@@ -687,13 +932,44 @@ export function Param(
|
||||
* @publicApi
|
||||
*/
|
||||
export function Param(
|
||||
property?: string | (Type<PipeTransform> | PipeTransform),
|
||||
property?:
|
||||
| string
|
||||
| (Type<PipeTransform> | PipeTransform)
|
||||
| ParameterDecoratorOptions,
|
||||
optionsOrPipe?:
|
||||
| ParameterDecoratorOptions
|
||||
| Type<PipeTransform>
|
||||
| PipeTransform,
|
||||
...pipes: (Type<PipeTransform> | PipeTransform)[]
|
||||
): ParameterDecorator {
|
||||
return createPipesRouteParamDecorator(RouteParamtypes.PARAM)(
|
||||
property,
|
||||
...pipes,
|
||||
);
|
||||
const isPropertyOptions =
|
||||
property &&
|
||||
typeof property === 'object' &&
|
||||
!('transform' in property) &&
|
||||
('schema' in property || 'pipes' in property);
|
||||
|
||||
if (isPropertyOptions) {
|
||||
return createPipesRouteParamDecorator(RouteParamtypes.PARAM)({
|
||||
pipes: property.pipes,
|
||||
schema: property.schema,
|
||||
});
|
||||
}
|
||||
|
||||
const isOptions =
|
||||
optionsOrPipe &&
|
||||
typeof optionsOrPipe === 'object' &&
|
||||
('schema' in optionsOrPipe || 'pipes' in optionsOrPipe);
|
||||
const actualPipes = isOptions
|
||||
? optionsOrPipe.pipes
|
||||
: ([optionsOrPipe, ...pipes].filter(Boolean) as (
|
||||
| Type<PipeTransform>
|
||||
| PipeTransform
|
||||
)[]);
|
||||
return createPipesRouteParamDecorator(RouteParamtypes.PARAM)({
|
||||
data: property,
|
||||
pipes: actualPipes,
|
||||
schema: isOptions ? optionsOrPipe.schema : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Type } from '../type.interface.js';
|
||||
import { Paramtype } from './paramtype.interface.js';
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
|
||||
export type Transform<T = any> = (value: T, metadata: ArgumentMetadata) => any;
|
||||
|
||||
@@ -25,6 +26,11 @@ export interface ArgumentMetadata {
|
||||
* Example: `@Body('userId')` would yield `userId`
|
||||
*/
|
||||
readonly data?: string | undefined;
|
||||
/**
|
||||
* A standard schema object.
|
||||
* Can be used to validate the parameter's value against the schema, or to generate API.
|
||||
*/
|
||||
readonly schema?: StandardSchemaV1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
54
packages/common/internal.ts
Normal file
54
packages/common/internal.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Internal module - not part of the public API.
|
||||
* These exports are used by sibling @nestjs packages.
|
||||
* Do not depend on these in your application code.
|
||||
* @internal
|
||||
* @module
|
||||
*/
|
||||
|
||||
// Constants
|
||||
export * from './constants.js';
|
||||
|
||||
// Enums (internal)
|
||||
export { RouteParamtypes } from './enums/route-paramtypes.enum.js';
|
||||
|
||||
// Utils
|
||||
export * from './utils/shared.utils.js';
|
||||
export * from './utils/load-package.util.js';
|
||||
export * from './utils/cli-colors.util.js';
|
||||
export * from './utils/random-string-generator.util.js';
|
||||
export * from './utils/select-exception-filter-metadata.util.js';
|
||||
|
||||
// Interfaces (types not exposed at root due to name conflicts or internal use)
|
||||
export type { Controller, Injectable } from './interfaces/index.js';
|
||||
export type { NestApplicationContextOptions } from './interfaces/nest-application-context-options.interface.js';
|
||||
export type { NestMicroserviceOptions } from './interfaces/microservices/nest-microservice-options.interface.js';
|
||||
export type {
|
||||
CorsOptions,
|
||||
CorsOptionsDelegate,
|
||||
CustomOrigin,
|
||||
} from './interfaces/external/cors-options.interface.js';
|
||||
export type { ExceptionFilterMetadata } from './interfaces/exceptions/exception-filter-metadata.interface.js';
|
||||
export type { RpcExceptionFilterMetadata } from './interfaces/exceptions/rpc-exception-filter-metadata.interface.js';
|
||||
export type { VersionValue } from './interfaces/version-options.interface.js';
|
||||
export type { GlobalPrefixOptions } from './interfaces/global-prefix-options.interface.js';
|
||||
export type {
|
||||
MiddlewareConfiguration,
|
||||
RouteInfo,
|
||||
} from './interfaces/middleware/middleware-configuration.interface.js';
|
||||
export type { MiddlewareConfigProxy } from './interfaces/middleware/middleware-config-proxy.interface.js';
|
||||
export type { ModuleMetadata } from './interfaces/modules/module-metadata.interface.js';
|
||||
export type {
|
||||
HttpArgumentsHost,
|
||||
RpcArgumentsHost,
|
||||
WsArgumentsHost,
|
||||
} from './interfaces/features/arguments-host.interface.js';
|
||||
export type { RequestHandler } from './interfaces/http/http-server.interface.js';
|
||||
export type {
|
||||
GetOrResolveOptions,
|
||||
SelectOptions,
|
||||
} from './interfaces/nest-application-context.interface.js';
|
||||
export type { ShutdownHooksOptions } from './interfaces/shutdown-hooks-options.interface.js';
|
||||
|
||||
// Decorators (internal)
|
||||
export { assignMetadata } from './decorators/http/route-params.decorator.js';
|
||||
@@ -7,4 +7,5 @@ export * from './parse-enum.pipe.js';
|
||||
export * from './parse-float.pipe.js';
|
||||
export * from './parse-int.pipe.js';
|
||||
export * from './parse-uuid.pipe.js';
|
||||
export * from './standard-schema-validation.pipe.js';
|
||||
export * from './validation.pipe.js';
|
||||
|
||||
192
packages/common/pipes/standard-schema-validation.pipe.ts
Normal file
192
packages/common/pipes/standard-schema-validation.pipe.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
import { types } from 'util';
|
||||
import { Injectable } from '../decorators/core/injectable.decorator.js';
|
||||
import { Optional } from '../decorators/core/optional.decorator.js';
|
||||
import { HttpStatus } from '../enums/http-status.enum.js';
|
||||
import {
|
||||
ArgumentMetadata,
|
||||
PipeTransform,
|
||||
} from '../interfaces/features/pipe-transform.interface.js';
|
||||
import {
|
||||
ErrorHttpStatusCode,
|
||||
HttpErrorByCode,
|
||||
} from '../utils/http-error-by-code.util.js';
|
||||
|
||||
/**
|
||||
* Built-in JavaScript types that should be excluded from prototype stripping
|
||||
* to avoid conflicts with test frameworks like Jest's useFakeTimers
|
||||
*/
|
||||
const BUILT_IN_TYPES = [Date, RegExp, Error, Map, Set, WeakMap, WeakSet];
|
||||
|
||||
/**
|
||||
* @publicApi
|
||||
*/
|
||||
export interface StandardSchemaValidationPipeOptions {
|
||||
/**
|
||||
* If true, the pipe will return the value produced by the schema
|
||||
* (which may differ from the input if the schema coerces/transforms values).
|
||||
* If false, the original input value is returned after successful validation.
|
||||
* @default true
|
||||
*/
|
||||
transform?: boolean;
|
||||
/**
|
||||
* If true, the pipe will also validate parameters decorated with custom decorators
|
||||
* (created with `createParamDecorator`). When false, custom parameters are skipped.
|
||||
* @default false
|
||||
*/
|
||||
validateCustomDecorators?: boolean;
|
||||
/**
|
||||
* Options to pass to the standard schema `validate` function.
|
||||
* These options are forwarded as the second argument to the schema's `~standard.validate` method.
|
||||
*/
|
||||
validateOptions?: Record<string, unknown>;
|
||||
/**
|
||||
* The HTTP status code to be used in the response when the validation fails.
|
||||
* @default HttpStatus.BAD_REQUEST
|
||||
*/
|
||||
errorHttpStatusCode?: ErrorHttpStatusCode;
|
||||
/**
|
||||
* A factory function that returns an exception object to be thrown
|
||||
* if validation fails.
|
||||
* @param issues The issues returned by the standard schema validation
|
||||
* @returns The exception object
|
||||
*/
|
||||
exceptionFactory?: (issues: readonly StandardSchemaV1.Issue[]) => any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the built-in StandardSchemaValidation Pipe.
|
||||
*
|
||||
* Uses a standard schema object (conforming to the Standard Schema spec)
|
||||
* attached to the parameter metadata to validate incoming values.
|
||||
*
|
||||
* @see [Standard Schema](https://github.com/standard-schema/standard-schema)
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
@Injectable()
|
||||
export class StandardSchemaValidationPipe implements PipeTransform {
|
||||
protected isTransformEnabled: boolean;
|
||||
protected validateCustomDecorators: boolean;
|
||||
protected validateOptions: Record<string, unknown> | undefined;
|
||||
protected exceptionFactory: (
|
||||
issues: readonly StandardSchemaV1.Issue[],
|
||||
) => any;
|
||||
|
||||
constructor(
|
||||
@Optional()
|
||||
protected readonly options?: StandardSchemaValidationPipeOptions,
|
||||
) {
|
||||
const {
|
||||
transform = true,
|
||||
validateCustomDecorators = false,
|
||||
validateOptions,
|
||||
exceptionFactory,
|
||||
errorHttpStatusCode = HttpStatus.BAD_REQUEST,
|
||||
} = options || {};
|
||||
|
||||
this.isTransformEnabled = transform;
|
||||
this.validateCustomDecorators = validateCustomDecorators;
|
||||
this.validateOptions = validateOptions;
|
||||
|
||||
this.exceptionFactory =
|
||||
exceptionFactory ||
|
||||
(issues => {
|
||||
const messages = issues.map(issue => issue.message);
|
||||
return new HttpErrorByCode[errorHttpStatusCode](messages);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that validates the incoming value against the standard schema
|
||||
* provided in the parameter metadata.
|
||||
*
|
||||
* @param value currently processed route argument
|
||||
* @param metadata contains metadata about the currently processed route argument
|
||||
*/
|
||||
async transform<T = any>(value: T, metadata: ArgumentMetadata): Promise<T> {
|
||||
const schema = metadata.schema;
|
||||
if (!schema || !this.toValidate(metadata)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
this.stripProtoKeys(value);
|
||||
|
||||
const result = await this.validate<T>(value, schema, this.validateOptions);
|
||||
|
||||
if (result.issues) {
|
||||
throw this.exceptionFactory(result.issues);
|
||||
}
|
||||
return this.isTransformEnabled ? result.value : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether validation should be performed for the given metadata.
|
||||
* Skips validation for custom decorators unless `validateCustomDecorators` is enabled.
|
||||
*
|
||||
* @param metadata contains metadata about the currently processed route argument
|
||||
* @returns `true` if validation should be performed
|
||||
*/
|
||||
protected toValidate(metadata: ArgumentMetadata): boolean {
|
||||
const { type } = metadata;
|
||||
if (type === 'custom' && !this.validateCustomDecorators) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a value against a standard schema.
|
||||
* Can be overridden to customize validation behavior.
|
||||
*
|
||||
* @param value The value to validate
|
||||
* @param schema The standard schema to validate against
|
||||
* @param options Optional options forwarded to the schema's validate method
|
||||
* @returns The validation result
|
||||
*/
|
||||
protected validate<T = unknown>(
|
||||
value: unknown,
|
||||
schema: StandardSchemaV1,
|
||||
options?: Record<string, unknown>,
|
||||
): Promise<StandardSchemaV1.Result<T>> | StandardSchemaV1.Result<T> {
|
||||
return schema['~standard'].validate(value, options) as
|
||||
| Promise<StandardSchemaV1.Result<T>>
|
||||
| StandardSchemaV1.Result<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips dangerous prototype pollution keys from an object.
|
||||
*/
|
||||
protected stripProtoKeys(value: any) {
|
||||
if (
|
||||
value == null ||
|
||||
typeof value !== 'object' ||
|
||||
types.isTypedArray(value)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (BUILT_IN_TYPES.some(type => value instanceof type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
for (const v of value) {
|
||||
this.stripProtoKeys(v);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
delete value.__proto__;
|
||||
delete value.prototype;
|
||||
|
||||
const constructorType = value?.constructor;
|
||||
if (constructorType && !BUILT_IN_TYPES.includes(constructorType)) {
|
||||
delete value.constructor;
|
||||
}
|
||||
|
||||
for (const key in value) {
|
||||
this.stripProtoKeys(value[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
import { SetMetadata } from '../../decorators/index.js';
|
||||
import { ClassSerializerContextOptions } from '../class-serializer.interfaces.js';
|
||||
import { CLASS_SERIALIZER_OPTIONS } from '../class-serializer.constants.js';
|
||||
import { ClassSerializerContextOptions } from '../class-serializer.interfaces.js';
|
||||
import { StandardSchemaSerializerContextOptions } from '../standard-schema-serializer.interfaces.js';
|
||||
|
||||
/**
|
||||
* @publicApi
|
||||
*/
|
||||
export const SerializeOptions = (options: ClassSerializerContextOptions) =>
|
||||
SetMetadata(CLASS_SERIALIZER_OPTIONS, options);
|
||||
export const SerializeOptions = (
|
||||
options:
|
||||
| ClassSerializerContextOptions
|
||||
| StandardSchemaSerializerContextOptions,
|
||||
) => SetMetadata(CLASS_SERIALIZER_OPTIONS, options);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from './class-serializer.interceptor.js';
|
||||
export * from './decorators/index.js';
|
||||
export * from './class-serializer.interfaces.js';
|
||||
export * from './decorators/index.js';
|
||||
export * from './standard-schema-serializer.interceptor.js';
|
||||
export * from './standard-schema-serializer.interfaces.js';
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Inject, Injectable, Optional } from '../decorators/core/index.js';
|
||||
import { StreamableFile } from '../file-stream/index.js';
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
NestInterceptor,
|
||||
} from '../interfaces/index.js';
|
||||
import { isObject } from '../utils/shared.utils.js';
|
||||
import { CLASS_SERIALIZER_OPTIONS } from './class-serializer.constants.js';
|
||||
import { StandardSchemaSerializerContextOptions } from './standard-schema-serializer.interfaces.js';
|
||||
|
||||
interface PlainLiteralObject {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// NOTE (external)
|
||||
// We need to deduplicate them here due to the circular dependency
|
||||
// between core and common packages
|
||||
const REFLECTOR = 'Reflector';
|
||||
|
||||
/**
|
||||
* @publicApi
|
||||
*/
|
||||
export interface StandardSchemaSerializerInterceptorOptions {
|
||||
/**
|
||||
* A default standard schema to use for serialization when no schema
|
||||
* is provided via `@SerializeOptions()`.
|
||||
*/
|
||||
schema?: StandardSchemaV1;
|
||||
/**
|
||||
* Default options forwarded to the schema's `~standard.validate()` call.
|
||||
* Can be overridden per-handler via `@SerializeOptions({ validateOptions })`.
|
||||
*/
|
||||
validateOptions?: StandardSchemaV1.Options;
|
||||
}
|
||||
|
||||
/**
|
||||
* An interceptor that serializes outgoing responses using a Standard Schema.
|
||||
*
|
||||
* The schema can be provided either:
|
||||
* - As a default option in the interceptor constructor
|
||||
* - Per-handler or per-class via `@SerializeOptions({ schema })` decorator
|
||||
*
|
||||
* When a schema is present, the interceptor validates/transforms the response
|
||||
* through the schema's `~standard.validate()` method. If validation fails,
|
||||
* the issues are thrown as an error.
|
||||
*
|
||||
* @see [Standard Schema](https://github.com/standard-schema/standard-schema)
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
@Injectable()
|
||||
export class StandardSchemaSerializerInterceptor implements NestInterceptor {
|
||||
constructor(
|
||||
@Inject(REFLECTOR) protected readonly reflector: any,
|
||||
@Optional()
|
||||
protected readonly defaultOptions: StandardSchemaSerializerInterceptorOptions = {},
|
||||
) {}
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
const contextOptions = this.getContextOptions(context);
|
||||
const schema = contextOptions?.schema ?? this.defaultOptions.schema;
|
||||
const validateOptions =
|
||||
contextOptions?.validateOptions ?? this.defaultOptions.validateOptions;
|
||||
|
||||
return next
|
||||
.handle()
|
||||
.pipe(
|
||||
map((res: PlainLiteralObject | Array<PlainLiteralObject>) =>
|
||||
this.serialize(res, schema, validateOptions),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes responses that are non-null objects nor streamable files.
|
||||
*/
|
||||
serialize(
|
||||
response: PlainLiteralObject | Array<PlainLiteralObject>,
|
||||
schema: StandardSchemaV1 | undefined,
|
||||
validateOptions?: StandardSchemaV1.Options,
|
||||
):
|
||||
| PlainLiteralObject
|
||||
| Array<PlainLiteralObject>
|
||||
| Promise<PlainLiteralObject | Array<PlainLiteralObject>> {
|
||||
if (!schema || !isObject(response) || response instanceof StreamableFile) {
|
||||
return response;
|
||||
}
|
||||
|
||||
return Array.isArray(response)
|
||||
? Promise.all(
|
||||
response.map(item =>
|
||||
this.transformToPlain(item, schema, validateOptions),
|
||||
),
|
||||
)
|
||||
: this.transformToPlain(response, schema, validateOptions);
|
||||
}
|
||||
|
||||
async transformToPlain(
|
||||
plainOrClass: any,
|
||||
schema: StandardSchemaV1,
|
||||
validateOptions?: StandardSchemaV1.Options,
|
||||
): Promise<PlainLiteralObject> {
|
||||
if (!plainOrClass) {
|
||||
return plainOrClass;
|
||||
}
|
||||
|
||||
const result = await schema['~standard'].validate(
|
||||
plainOrClass,
|
||||
validateOptions,
|
||||
);
|
||||
|
||||
if (result.issues) {
|
||||
throw new Error(
|
||||
`Serialization failed: ${result.issues.map(i => i.message).join(', ')}`,
|
||||
);
|
||||
}
|
||||
return result.value as PlainLiteralObject;
|
||||
}
|
||||
|
||||
protected getContextOptions(
|
||||
context: ExecutionContext,
|
||||
): StandardSchemaSerializerContextOptions | undefined {
|
||||
return this.reflector.getAllAndOverride(CLASS_SERIALIZER_OPTIONS, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
|
||||
/**
|
||||
* Options for the `StandardSchemaSerializerInterceptor`, passed via
|
||||
* `@SerializeOptions({ schema })`.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface StandardSchemaSerializerContextOptions {
|
||||
/**
|
||||
* A standard schema to use for serialization.
|
||||
* Used by `StandardSchemaSerializerInterceptor` to validate/transform the response.
|
||||
*/
|
||||
schema?: StandardSchemaV1;
|
||||
/**
|
||||
* Optional options forwarded to the schema's `~standard.validate()` call.
|
||||
*/
|
||||
validateOptions?: StandardSchemaV1.Options;
|
||||
}
|
||||
@@ -100,6 +100,142 @@ describe('createParamDecorator', () => {
|
||||
expect(metadata[key].data).toBe(Data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when options object with schema is passed', () => {
|
||||
const mockSchema = {
|
||||
'~standard': {
|
||||
version: 1,
|
||||
vendor: 'test',
|
||||
validate: (v: unknown) => ({ value: v }),
|
||||
},
|
||||
};
|
||||
|
||||
describe('with data and schema option', () => {
|
||||
const data = 'testData';
|
||||
class Test {
|
||||
public test(
|
||||
@Decorator(data, { schema: mockSchema })
|
||||
param,
|
||||
) {}
|
||||
}
|
||||
it('should enhance param with "data" and "schema"', () => {
|
||||
const metadata = Reflect.getMetadata(
|
||||
ROUTE_ARGS_METADATA,
|
||||
Test,
|
||||
'test',
|
||||
);
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key]).toEqual({
|
||||
data: 'testData',
|
||||
factory: factoryFn,
|
||||
index: 0,
|
||||
pipes: [],
|
||||
schema: mockSchema,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with schema option only (no data)', () => {
|
||||
class Test {
|
||||
public test(
|
||||
@Decorator({ schema: mockSchema })
|
||||
param,
|
||||
) {}
|
||||
}
|
||||
it('should enhance param with schema and no data', () => {
|
||||
const metadata = Reflect.getMetadata(
|
||||
ROUTE_ARGS_METADATA,
|
||||
Test,
|
||||
'test',
|
||||
);
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key]).toEqual({
|
||||
data: undefined,
|
||||
factory: factoryFn,
|
||||
index: 0,
|
||||
pipes: [],
|
||||
schema: mockSchema,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with data, pipes, and schema option', () => {
|
||||
const data = 'testData';
|
||||
const pipe = new ParseIntPipe();
|
||||
class Test {
|
||||
public test(
|
||||
@Decorator(data, pipe, { schema: mockSchema })
|
||||
param,
|
||||
) {}
|
||||
}
|
||||
it('should enhance param with "data", pipes, and schema', () => {
|
||||
const metadata = Reflect.getMetadata(
|
||||
ROUTE_ARGS_METADATA,
|
||||
Test,
|
||||
'test',
|
||||
);
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key]).toEqual({
|
||||
data: 'testData',
|
||||
factory: factoryFn,
|
||||
index: 0,
|
||||
pipes: [pipe],
|
||||
schema: mockSchema,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with pipes in options object', () => {
|
||||
const data = 'testData';
|
||||
const pipe = new ParseIntPipe();
|
||||
class Test {
|
||||
public test(
|
||||
@Decorator(data, { schema: mockSchema, pipes: [pipe] })
|
||||
param,
|
||||
) {}
|
||||
}
|
||||
it('should enhance param with "data", pipes from options, and schema', () => {
|
||||
const metadata = Reflect.getMetadata(
|
||||
ROUTE_ARGS_METADATA,
|
||||
Test,
|
||||
'test',
|
||||
);
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key]).toEqual({
|
||||
data: 'testData',
|
||||
factory: factoryFn,
|
||||
index: 0,
|
||||
pipes: [pipe],
|
||||
schema: mockSchema,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with options containing only pipes (no schema)', () => {
|
||||
const data = 'testData';
|
||||
const pipe = new ParseIntPipe();
|
||||
class Test {
|
||||
public test(
|
||||
@Decorator(data, { pipes: [pipe] })
|
||||
param,
|
||||
) {}
|
||||
}
|
||||
it('should enhance param with "data" and pipes from options', () => {
|
||||
const metadata = Reflect.getMetadata(
|
||||
ROUTE_ARGS_METADATA,
|
||||
Test,
|
||||
'test',
|
||||
);
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key]).toEqual({
|
||||
data: 'testData',
|
||||
factory: factoryFn,
|
||||
index: 0,
|
||||
pipes: [pipe],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('returned generic typed decorator', () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ROUTE_ARGS_METADATA } from '../../constants.js';
|
||||
import {
|
||||
Body,
|
||||
HostParam,
|
||||
@@ -6,24 +7,23 @@ import {
|
||||
Search,
|
||||
} from '../../decorators/index.js';
|
||||
import { RequestMethod } from '../../enums/request-method.enum.js';
|
||||
import { RouteParamtypes } from '../../enums/route-paramtypes.enum.js';
|
||||
import {
|
||||
All,
|
||||
Copy,
|
||||
Delete,
|
||||
Get,
|
||||
Lock,
|
||||
Mkcol,
|
||||
Move,
|
||||
ParseIntPipe,
|
||||
Patch,
|
||||
Post,
|
||||
Put,
|
||||
Propfind,
|
||||
Proppatch,
|
||||
Mkcol,
|
||||
Move,
|
||||
Copy,
|
||||
Lock,
|
||||
Put,
|
||||
Unlock,
|
||||
} from '../../index.js';
|
||||
import { ROUTE_ARGS_METADATA } from '../../constants.js';
|
||||
import { RouteParamtypes } from '../../enums/route-paramtypes.enum.js';
|
||||
|
||||
describe('@Get', () => {
|
||||
const requestPath = 'test';
|
||||
@@ -841,3 +841,196 @@ describe('@Unlock', () => {
|
||||
expect(pathUsingArray).toEqual('/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('@Body with ParameterDecoratorOptions', () => {
|
||||
const mockSchema = {
|
||||
'~standard': {
|
||||
version: 1 as const,
|
||||
vendor: 'test',
|
||||
validate: (v: unknown) => ({ value: v }),
|
||||
},
|
||||
};
|
||||
|
||||
it('should enhance param with schema when options passed as the only argument', () => {
|
||||
class Test {
|
||||
public test(@Body({ schema: mockSchema }) body) {}
|
||||
}
|
||||
const metadata = Reflect.getMetadata(ROUTE_ARGS_METADATA, Test, 'test');
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key]).toEqual({
|
||||
index: 0,
|
||||
data: undefined,
|
||||
pipes: [],
|
||||
schema: mockSchema,
|
||||
});
|
||||
});
|
||||
|
||||
it('should enhance param with pipes when options with pipes passed as the only argument', () => {
|
||||
class Test {
|
||||
public test(@Body({ schema: mockSchema, pipes: [ParseIntPipe] }) body) {}
|
||||
}
|
||||
const metadata = Reflect.getMetadata(ROUTE_ARGS_METADATA, Test, 'test');
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key]).toEqual({
|
||||
index: 0,
|
||||
data: undefined,
|
||||
pipes: [ParseIntPipe],
|
||||
schema: mockSchema,
|
||||
});
|
||||
});
|
||||
|
||||
it('should enhance param with schema when options passed as second argument with property', () => {
|
||||
class Test {
|
||||
public test(@Body('role', { schema: mockSchema }) body) {}
|
||||
}
|
||||
const metadata = Reflect.getMetadata(ROUTE_ARGS_METADATA, Test, 'test');
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key]).toEqual({
|
||||
index: 0,
|
||||
data: 'role',
|
||||
pipes: [],
|
||||
schema: mockSchema,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not confuse a pipe instance with options', () => {
|
||||
class Test {
|
||||
public test(@Body(new ParseIntPipe()) body) {}
|
||||
}
|
||||
const metadata = Reflect.getMetadata(ROUTE_ARGS_METADATA, Test, 'test');
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key].data).toBeUndefined();
|
||||
expect(metadata[key].pipes).toHaveLength(1);
|
||||
expect(metadata[key].schema).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('@Query with ParameterDecoratorOptions', () => {
|
||||
const mockSchema = {
|
||||
'~standard': {
|
||||
version: 1 as const,
|
||||
vendor: 'test',
|
||||
validate: (v: unknown) => ({ value: v }),
|
||||
},
|
||||
};
|
||||
|
||||
it('should enhance param with schema when options passed as the only argument', () => {
|
||||
class Test {
|
||||
public test(@Query({ schema: mockSchema }) query) {}
|
||||
}
|
||||
const metadata = Reflect.getMetadata(ROUTE_ARGS_METADATA, Test, 'test');
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key]).toEqual({
|
||||
index: 0,
|
||||
data: undefined,
|
||||
pipes: [],
|
||||
schema: mockSchema,
|
||||
});
|
||||
});
|
||||
|
||||
it('should enhance param with pipes when options with pipes passed as the only argument', () => {
|
||||
class Test {
|
||||
public test(
|
||||
@Query({ schema: mockSchema, pipes: [ParseIntPipe] }) query,
|
||||
) {}
|
||||
}
|
||||
const metadata = Reflect.getMetadata(ROUTE_ARGS_METADATA, Test, 'test');
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key]).toEqual({
|
||||
index: 0,
|
||||
data: undefined,
|
||||
pipes: [ParseIntPipe],
|
||||
schema: mockSchema,
|
||||
});
|
||||
});
|
||||
|
||||
it('should enhance param with schema when options passed as second argument with property', () => {
|
||||
class Test {
|
||||
public test(@Query('user', { schema: mockSchema }) user) {}
|
||||
}
|
||||
const metadata = Reflect.getMetadata(ROUTE_ARGS_METADATA, Test, 'test');
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key]).toEqual({
|
||||
index: 0,
|
||||
data: 'user',
|
||||
pipes: [],
|
||||
schema: mockSchema,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not confuse a pipe instance with options', () => {
|
||||
class Test {
|
||||
public test(@Query(new ParseIntPipe()) query) {}
|
||||
}
|
||||
const metadata = Reflect.getMetadata(ROUTE_ARGS_METADATA, Test, 'test');
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key].data).toBeUndefined();
|
||||
expect(metadata[key].pipes).toHaveLength(1);
|
||||
expect(metadata[key].schema).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('@Param with ParameterDecoratorOptions', () => {
|
||||
const mockSchema = {
|
||||
'~standard': {
|
||||
version: 1 as const,
|
||||
vendor: 'test',
|
||||
validate: (v: unknown) => ({ value: v }),
|
||||
},
|
||||
};
|
||||
|
||||
it('should enhance param with schema when options passed as the only argument', () => {
|
||||
class Test {
|
||||
public test(@Param({ schema: mockSchema }) params) {}
|
||||
}
|
||||
const metadata = Reflect.getMetadata(ROUTE_ARGS_METADATA, Test, 'test');
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key]).toEqual({
|
||||
index: 0,
|
||||
data: undefined,
|
||||
pipes: [],
|
||||
schema: mockSchema,
|
||||
});
|
||||
});
|
||||
|
||||
it('should enhance param with pipes when options with pipes passed as the only argument', () => {
|
||||
class Test {
|
||||
public test(
|
||||
@Param({ schema: mockSchema, pipes: [ParseIntPipe] }) params,
|
||||
) {}
|
||||
}
|
||||
const metadata = Reflect.getMetadata(ROUTE_ARGS_METADATA, Test, 'test');
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key]).toEqual({
|
||||
index: 0,
|
||||
data: undefined,
|
||||
pipes: [ParseIntPipe],
|
||||
schema: mockSchema,
|
||||
});
|
||||
});
|
||||
|
||||
it('should enhance param with schema when options passed as second argument with property', () => {
|
||||
class Test {
|
||||
public test(@Param('id', { schema: mockSchema }) id) {}
|
||||
}
|
||||
const metadata = Reflect.getMetadata(ROUTE_ARGS_METADATA, Test, 'test');
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key]).toEqual({
|
||||
index: 0,
|
||||
data: 'id',
|
||||
pipes: [],
|
||||
schema: mockSchema,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not confuse a pipe instance with options', () => {
|
||||
class Test {
|
||||
public test(@Param(new ParseIntPipe()) params) {}
|
||||
}
|
||||
const metadata = Reflect.getMetadata(ROUTE_ARGS_METADATA, Test, 'test');
|
||||
const key = Object.keys(metadata)[0];
|
||||
expect(metadata[key].data).toBeUndefined();
|
||||
expect(metadata[key].pipes).toHaveLength(1);
|
||||
expect(metadata[key].schema).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
import { HttpException, HttpStatus } from '../../index.js';
|
||||
import { ArgumentMetadata } from '../../interfaces/index.js';
|
||||
import { StandardSchemaValidationPipe } from '../../pipes/standard-schema-validation.pipe.js';
|
||||
|
||||
function createSchema(
|
||||
validateFn: (
|
||||
value: unknown,
|
||||
) => StandardSchemaV1.Result<any> | Promise<StandardSchemaV1.Result<any>>,
|
||||
): StandardSchemaV1 {
|
||||
return {
|
||||
'~standard': {
|
||||
version: 1,
|
||||
vendor: 'test',
|
||||
validate: validateFn,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('StandardSchemaValidationPipe', () => {
|
||||
let pipe: StandardSchemaValidationPipe;
|
||||
|
||||
beforeEach(() => {
|
||||
pipe = new StandardSchemaValidationPipe();
|
||||
});
|
||||
|
||||
describe('transform', () => {
|
||||
describe('when no schema is present in metadata', () => {
|
||||
it('should return the value unchanged', async () => {
|
||||
const value = { name: 'test' };
|
||||
const result = await pipe.transform(value, {
|
||||
type: 'body',
|
||||
} as ArgumentMetadata);
|
||||
expect(result).toBe(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when schema validation succeeds', () => {
|
||||
it('should return the validated value', async () => {
|
||||
const schema = createSchema(value => ({ value }));
|
||||
const result = await pipe.transform({ name: 'test' }, {
|
||||
type: 'body',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
expect(result).toEqual({ name: 'test' });
|
||||
});
|
||||
|
||||
it('should return the transformed value from schema', async () => {
|
||||
const schema = createSchema(value => ({
|
||||
value: { ...(value as any), extra: true },
|
||||
}));
|
||||
const result = await pipe.transform({ name: 'test' }, {
|
||||
type: 'body',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
expect(result).toEqual({ name: 'test', extra: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when schema validation succeeds asynchronously', () => {
|
||||
it('should return the validated value', async () => {
|
||||
const schema = createSchema(async value => ({ value }));
|
||||
const result = await pipe.transform({ name: 'test' }, {
|
||||
type: 'body',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
expect(result).toEqual({ name: 'test' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when schema validation fails', () => {
|
||||
it('should throw a BadRequestException with issue messages', async () => {
|
||||
const schema = createSchema(() => ({
|
||||
issues: [
|
||||
{ message: 'field is required', path: ['name'] },
|
||||
{ message: 'must be a string' },
|
||||
],
|
||||
}));
|
||||
|
||||
await expect(
|
||||
pipe.transform({}, { type: 'body', schema } as ArgumentMetadata),
|
||||
).rejects.toThrow();
|
||||
|
||||
try {
|
||||
await pipe.transform({}, {
|
||||
type: 'body',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(HttpException);
|
||||
expect((error as HttpException).getStatus()).toBe(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
expect((error as HttpException).getResponse()).toEqual({
|
||||
statusCode: HttpStatus.BAD_REQUEST,
|
||||
message: ['field is required', 'must be a string'],
|
||||
error: 'Bad Request',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw asynchronously when schema validation fails async', async () => {
|
||||
const schema = createSchema(async () => ({
|
||||
issues: [{ message: 'invalid' }],
|
||||
}));
|
||||
|
||||
await expect(
|
||||
pipe.transform({}, { type: 'body', schema } as ArgumentMetadata),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when custom errorHttpStatusCode is provided', () => {
|
||||
it('should throw with the custom status code', async () => {
|
||||
const customPipe = new StandardSchemaValidationPipe({
|
||||
errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||
});
|
||||
const schema = createSchema(() => ({
|
||||
issues: [{ message: 'invalid' }],
|
||||
}));
|
||||
|
||||
try {
|
||||
await customPipe.transform({}, {
|
||||
type: 'body',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(HttpException);
|
||||
expect((error as HttpException).getStatus()).toBe(
|
||||
HttpStatus.UNPROCESSABLE_ENTITY,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('when custom exceptionFactory is provided', () => {
|
||||
it('should use the custom exception factory', async () => {
|
||||
class CustomError extends Error {
|
||||
constructor(
|
||||
public readonly issues: readonly StandardSchemaV1.Issue[],
|
||||
) {
|
||||
super('Custom validation error');
|
||||
}
|
||||
}
|
||||
|
||||
const customPipe = new StandardSchemaValidationPipe({
|
||||
exceptionFactory: issues => new CustomError(issues),
|
||||
});
|
||||
const schema = createSchema(() => ({
|
||||
issues: [{ message: 'invalid' }],
|
||||
}));
|
||||
|
||||
try {
|
||||
await customPipe.transform({}, {
|
||||
type: 'body',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(CustomError);
|
||||
expect((error as CustomError).issues).toEqual([
|
||||
{ message: 'invalid' },
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('when transform is enabled (default)', () => {
|
||||
it('should return the schema-produced value', async () => {
|
||||
const input = { name: 'test' };
|
||||
const schema = createSchema(value => ({
|
||||
value: { ...(value as any), coerced: true },
|
||||
}));
|
||||
const result = await pipe.transform(input, {
|
||||
type: 'body',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
expect(result).toEqual({ name: 'test', coerced: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when transform is explicitly set to true', () => {
|
||||
it('should return the schema-produced value', async () => {
|
||||
const transformPipe = new StandardSchemaValidationPipe({
|
||||
transform: true,
|
||||
});
|
||||
const input = { name: 'test' };
|
||||
const schema = createSchema(value => ({
|
||||
value: { ...(value as any), coerced: true },
|
||||
}));
|
||||
const result = await transformPipe.transform(input, {
|
||||
type: 'body',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
expect(result).toEqual({ name: 'test', coerced: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when transform is disabled', () => {
|
||||
it('should return the original input value', async () => {
|
||||
const noTransformPipe = new StandardSchemaValidationPipe({
|
||||
transform: false,
|
||||
});
|
||||
const input = { name: 'test' };
|
||||
const schema = createSchema(value => ({
|
||||
value: { ...(value as any), coerced: true },
|
||||
}));
|
||||
const result = await noTransformPipe.transform(input, {
|
||||
type: 'body',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
expect(result).toBe(input);
|
||||
expect(result).toEqual({ name: 'test' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('toValidate', () => {
|
||||
it('should skip validation for custom decorators by default', async () => {
|
||||
const schema = createSchema(() => ({
|
||||
issues: [{ message: 'should not be reached' }],
|
||||
}));
|
||||
const result = await pipe.transform({ name: 'test' }, {
|
||||
type: 'custom',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
expect(result).toEqual({ name: 'test' });
|
||||
});
|
||||
|
||||
it('should validate custom decorators when validateCustomDecorators is true', async () => {
|
||||
const customPipe = new StandardSchemaValidationPipe({
|
||||
validateCustomDecorators: true,
|
||||
});
|
||||
const schema = createSchema(() => ({
|
||||
issues: [{ message: 'invalid' }],
|
||||
}));
|
||||
|
||||
await expect(
|
||||
customPipe.transform({}, {
|
||||
type: 'custom',
|
||||
schema,
|
||||
} as ArgumentMetadata),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should always validate body/query/param types', async () => {
|
||||
const schema = createSchema(value => ({ value }));
|
||||
for (const type of ['body', 'query', 'param'] as const) {
|
||||
const result = await pipe.transform({ ok: true }, {
|
||||
type,
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
expect(result).toEqual({ ok: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('validate', () => {
|
||||
it('should be overridable', async () => {
|
||||
class CustomPipe extends StandardSchemaValidationPipe {
|
||||
protected validate(
|
||||
value: unknown,
|
||||
schema: StandardSchemaV1,
|
||||
options?: Record<string, unknown>,
|
||||
) {
|
||||
// Always succeed with a custom value
|
||||
return {
|
||||
value: 'custom-result',
|
||||
} as StandardSchemaV1.Result<unknown>;
|
||||
}
|
||||
}
|
||||
const customPipe = new CustomPipe();
|
||||
const schema = createSchema(() => ({
|
||||
issues: [{ message: 'should not be reached' }],
|
||||
}));
|
||||
const result = await customPipe.transform('anything', {
|
||||
type: 'body',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
expect(result).toBe('custom-result');
|
||||
});
|
||||
|
||||
it('should forward validateOptions to the schema validate function', async () => {
|
||||
let receivedOptions: unknown;
|
||||
const schema: StandardSchemaV1 = {
|
||||
'~standard': {
|
||||
version: 1,
|
||||
vendor: 'test',
|
||||
validate: (value: unknown, options?: unknown) => {
|
||||
receivedOptions = options;
|
||||
return { value };
|
||||
},
|
||||
},
|
||||
};
|
||||
const optsPipe = new StandardSchemaValidationPipe({
|
||||
validateOptions: { strict: true, strip: 'extra' },
|
||||
});
|
||||
await optsPipe.transform({ name: 'test' }, {
|
||||
type: 'body',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
expect(receivedOptions).toEqual({ strict: true, strip: 'extra' });
|
||||
});
|
||||
|
||||
it('should pass undefined when no validateOptions are provided', async () => {
|
||||
let receivedOptions: unknown = 'NOT_CALLED';
|
||||
const schema: StandardSchemaV1 = {
|
||||
'~standard': {
|
||||
version: 1,
|
||||
vendor: 'test',
|
||||
validate: (value: unknown, options?: unknown) => {
|
||||
receivedOptions = options;
|
||||
return { value };
|
||||
},
|
||||
},
|
||||
};
|
||||
await pipe.transform({ name: 'test' }, {
|
||||
type: 'body',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
expect(receivedOptions).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('stripProtoKeys', () => {
|
||||
it('should remove __proto__ and prototype from value before validation', async () => {
|
||||
let receivedValue: any;
|
||||
const schema = createSchema(value => {
|
||||
receivedValue = value;
|
||||
return { value };
|
||||
});
|
||||
const malicious = JSON.parse(
|
||||
'{"name": "test", "__proto__": {"polluted": true}, "prototype": {"bad": true}}',
|
||||
);
|
||||
await pipe.transform(malicious, {
|
||||
type: 'body',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
expect(receivedValue).not.toHaveProperty('__proto__');
|
||||
expect(receivedValue).not.toHaveProperty('prototype');
|
||||
expect(receivedValue).toHaveProperty('name', 'test');
|
||||
});
|
||||
|
||||
it('should strip proto keys recursively from nested objects', async () => {
|
||||
let receivedValue: any;
|
||||
const schema = createSchema(value => {
|
||||
receivedValue = value;
|
||||
return { value };
|
||||
});
|
||||
const malicious = {
|
||||
nested: JSON.parse('{"__proto__": {"polluted": true}, "ok": true}'),
|
||||
};
|
||||
await pipe.transform(malicious, {
|
||||
type: 'body',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
expect(receivedValue.nested).not.toHaveProperty('__proto__');
|
||||
expect(receivedValue.nested).toHaveProperty('ok', true);
|
||||
});
|
||||
|
||||
it('should strip proto keys from arrays', async () => {
|
||||
let receivedValue: any;
|
||||
const schema = createSchema(value => {
|
||||
receivedValue = value;
|
||||
return { value };
|
||||
});
|
||||
const malicious = [
|
||||
JSON.parse('{"__proto__": {"polluted": true}, "ok": true}'),
|
||||
];
|
||||
await pipe.transform(malicious, {
|
||||
type: 'body',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
expect(receivedValue[0]).not.toHaveProperty('__proto__');
|
||||
expect(receivedValue[0]).toHaveProperty('ok', true);
|
||||
});
|
||||
|
||||
it('should not strip keys from built-in types', async () => {
|
||||
let receivedValue: any;
|
||||
const schema = createSchema(value => {
|
||||
receivedValue = value;
|
||||
return { value };
|
||||
});
|
||||
const date = new Date();
|
||||
await pipe.transform(date, {
|
||||
type: 'body',
|
||||
schema,
|
||||
} as ArgumentMetadata);
|
||||
expect(receivedValue).toBe(date);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -355,13 +355,15 @@ describe('ValidationPipe', () => {
|
||||
});
|
||||
});
|
||||
describe('when validation rejects', () => {
|
||||
it('should throw an error', () => {
|
||||
it('should throw an error', async () => {
|
||||
target = new ValidationPipe({
|
||||
forbidNonWhitelisted: true,
|
||||
whitelist: true,
|
||||
});
|
||||
const testObj = { prop1: 'value1', prop2: 'value2', prop3: 'value3' };
|
||||
expect(target.transform(testObj, metadata)).rejects.toBeDefined();
|
||||
await expect(
|
||||
target.transform(testObj, metadata),
|
||||
).rejects.toBeDefined();
|
||||
});
|
||||
});
|
||||
describe('when transformation is internal', () => {
|
||||
@@ -450,14 +452,16 @@ describe('ValidationPipe', () => {
|
||||
});
|
||||
});
|
||||
describe('when validation rejects', () => {
|
||||
it('should throw an error', () => {
|
||||
it('should throw an error', async () => {
|
||||
target = new ValidationPipe({
|
||||
transform: false,
|
||||
forbidNonWhitelisted: true,
|
||||
whitelist: true,
|
||||
});
|
||||
const testObj = { prop1: 'value1', prop2: 'value2', prop3: 'value3' };
|
||||
expect(target.transform(testObj, metadata)).rejects.toBeDefined();
|
||||
await expect(
|
||||
target.transform(testObj, metadata),
|
||||
).rejects.toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -469,10 +473,14 @@ describe('ValidationPipe', () => {
|
||||
{ prop1: 'value1', prop2: 'value2', prop3: 'value3' },
|
||||
];
|
||||
|
||||
expect(target.transform(testObj, metadata)).rejects.toBeDefined();
|
||||
expect(target.transform('string', metadata)).rejects.toBeDefined();
|
||||
expect(target.transform(true, metadata)).rejects.toBeDefined();
|
||||
expect(target.transform(3, metadata)).rejects.toBeDefined();
|
||||
await expect(
|
||||
target.transform(testObj, metadata),
|
||||
).rejects.toBeDefined();
|
||||
await expect(
|
||||
target.transform('string', metadata),
|
||||
).rejects.toBeDefined();
|
||||
await expect(target.transform(true, metadata)).rejects.toBeDefined();
|
||||
await expect(target.transform(3, metadata)).rejects.toBeDefined();
|
||||
});
|
||||
});
|
||||
describe('otherwise', () => {
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
import { lastValueFrom, of } from 'rxjs';
|
||||
import { StreamableFile } from '../../file-stream/index.js';
|
||||
import { CallHandler, ExecutionContext } from '../../interfaces/index.js';
|
||||
import { StandardSchemaSerializerInterceptor } from '../../serializer/standard-schema-serializer.interceptor.js';
|
||||
|
||||
function createSchema(
|
||||
validateFn: (
|
||||
value: unknown,
|
||||
options?: StandardSchemaV1.Options,
|
||||
) => StandardSchemaV1.Result<any> | Promise<StandardSchemaV1.Result<any>>,
|
||||
): StandardSchemaV1 {
|
||||
return {
|
||||
'~standard': {
|
||||
version: 1,
|
||||
vendor: 'test',
|
||||
validate: validateFn,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('StandardSchemaSerializerInterceptor', () => {
|
||||
let interceptor: StandardSchemaSerializerInterceptor;
|
||||
let mockReflector: any;
|
||||
let mockExecutionContext: ExecutionContext;
|
||||
let mockCallHandler: CallHandler;
|
||||
|
||||
beforeEach(() => {
|
||||
mockReflector = {
|
||||
getAllAndOverride: vi.fn().mockReturnValue(undefined),
|
||||
};
|
||||
|
||||
mockExecutionContext = {
|
||||
getHandler: vi.fn(),
|
||||
getClass: vi.fn(),
|
||||
} as any;
|
||||
|
||||
mockCallHandler = {
|
||||
handle: vi.fn(),
|
||||
} as any;
|
||||
});
|
||||
|
||||
describe('intercept', () => {
|
||||
describe('when no schema is provided', () => {
|
||||
it('should return the response unchanged', async () => {
|
||||
interceptor = new StandardSchemaSerializerInterceptor(mockReflector);
|
||||
const response = { id: 1, name: 'Test' };
|
||||
(mockCallHandler.handle as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||
of(response),
|
||||
);
|
||||
|
||||
const result$ = interceptor.intercept(
|
||||
mockExecutionContext,
|
||||
mockCallHandler,
|
||||
);
|
||||
const result = await lastValueFrom(result$);
|
||||
expect(result).toBe(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when schema is provided via default options', () => {
|
||||
it('should serialize the response through the schema', async () => {
|
||||
const schema = createSchema(value => ({
|
||||
value: { ...(value as any), serialized: true },
|
||||
}));
|
||||
interceptor = new StandardSchemaSerializerInterceptor(mockReflector, {
|
||||
schema,
|
||||
});
|
||||
const response = { id: 1, name: 'Test' };
|
||||
(mockCallHandler.handle as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||
of(response),
|
||||
);
|
||||
|
||||
const result$ = interceptor.intercept(
|
||||
mockExecutionContext,
|
||||
mockCallHandler,
|
||||
);
|
||||
const result = await lastValueFrom(result$);
|
||||
expect(result).toEqual({ id: 1, name: 'Test', serialized: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when schema is provided via @SerializeOptions()', () => {
|
||||
it('should use the context schema over the default', async () => {
|
||||
const defaultSchema = createSchema(value => ({
|
||||
value: { ...(value as any), fromDefault: true },
|
||||
}));
|
||||
const contextSchema = createSchema(value => ({
|
||||
value: { ...(value as any), fromContext: true },
|
||||
}));
|
||||
interceptor = new StandardSchemaSerializerInterceptor(mockReflector, {
|
||||
schema: defaultSchema,
|
||||
});
|
||||
mockReflector.getAllAndOverride.mockReturnValue({
|
||||
schema: contextSchema,
|
||||
});
|
||||
const response = { id: 1 };
|
||||
(mockCallHandler.handle as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||
of(response),
|
||||
);
|
||||
|
||||
const result$ = interceptor.intercept(
|
||||
mockExecutionContext,
|
||||
mockCallHandler,
|
||||
);
|
||||
const result = await lastValueFrom(result$);
|
||||
expect(result).toEqual({ id: 1, fromContext: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when response is an array', () => {
|
||||
it('should serialize each item through the schema', async () => {
|
||||
const schema = createSchema(value => ({
|
||||
value: { ...(value as any), serialized: true },
|
||||
}));
|
||||
interceptor = new StandardSchemaSerializerInterceptor(mockReflector, {
|
||||
schema,
|
||||
});
|
||||
const response = [{ id: 1 }, { id: 2 }];
|
||||
(mockCallHandler.handle as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||
of(response),
|
||||
);
|
||||
|
||||
const result$ = interceptor.intercept(
|
||||
mockExecutionContext,
|
||||
mockCallHandler,
|
||||
);
|
||||
const result = await lastValueFrom(result$);
|
||||
expect(result).toEqual([
|
||||
{ id: 1, serialized: true },
|
||||
{ id: 2, serialized: true },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when response is not an object', () => {
|
||||
it('should return primitive values unchanged', async () => {
|
||||
const schema = createSchema(() => ({
|
||||
issues: [{ message: 'should not be called' }],
|
||||
}));
|
||||
interceptor = new StandardSchemaSerializerInterceptor(mockReflector, {
|
||||
schema,
|
||||
});
|
||||
(mockCallHandler.handle as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||
of('plain string'),
|
||||
);
|
||||
|
||||
const result$ = interceptor.intercept(
|
||||
mockExecutionContext,
|
||||
mockCallHandler,
|
||||
);
|
||||
const result = await lastValueFrom(result$);
|
||||
expect(result).toBe('plain string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when response is a StreamableFile', () => {
|
||||
it('should return the streamable file unchanged', async () => {
|
||||
const schema = createSchema(() => ({
|
||||
issues: [{ message: 'should not be called' }],
|
||||
}));
|
||||
interceptor = new StandardSchemaSerializerInterceptor(mockReflector, {
|
||||
schema,
|
||||
});
|
||||
const file = new StreamableFile(Buffer.from('test'));
|
||||
(mockCallHandler.handle as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||
of(file),
|
||||
);
|
||||
|
||||
const result$ = interceptor.intercept(
|
||||
mockExecutionContext,
|
||||
mockCallHandler,
|
||||
);
|
||||
const result = await lastValueFrom(result$);
|
||||
expect(result).toBe(file);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when schema validation fails', () => {
|
||||
it('should throw an error with issue messages', async () => {
|
||||
const schema = createSchema(() => ({
|
||||
issues: [
|
||||
{ message: 'field is required' },
|
||||
{ message: 'must be a number' },
|
||||
],
|
||||
}));
|
||||
interceptor = new StandardSchemaSerializerInterceptor(mockReflector, {
|
||||
schema,
|
||||
});
|
||||
const response = { id: 1 };
|
||||
(mockCallHandler.handle as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||
of(response),
|
||||
);
|
||||
|
||||
const result$ = interceptor.intercept(
|
||||
mockExecutionContext,
|
||||
mockCallHandler,
|
||||
);
|
||||
await expect(lastValueFrom(result$)).rejects.toThrow(
|
||||
'Serialization failed: field is required, must be a number',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when async schema is used', () => {
|
||||
it('should resolve the async validation result', async () => {
|
||||
const schema = createSchema(async value => ({
|
||||
value: { ...(value as any), async: true },
|
||||
}));
|
||||
interceptor = new StandardSchemaSerializerInterceptor(mockReflector, {
|
||||
schema,
|
||||
});
|
||||
const response = { id: 1 };
|
||||
(mockCallHandler.handle as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||
of(response),
|
||||
);
|
||||
|
||||
const result$ = interceptor.intercept(
|
||||
mockExecutionContext,
|
||||
mockCallHandler,
|
||||
);
|
||||
const result = await lastValueFrom(result$);
|
||||
expect(result).toEqual({ id: 1, async: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('transformToPlain', () => {
|
||||
it('should return falsy values as-is', async () => {
|
||||
interceptor = new StandardSchemaSerializerInterceptor(mockReflector);
|
||||
const schema = createSchema(() => ({
|
||||
issues: [{ message: 'should not be called' }],
|
||||
}));
|
||||
expect(await interceptor.transformToPlain(null, schema)).toBeNull();
|
||||
expect(
|
||||
await interceptor.transformToPlain(undefined, schema),
|
||||
).toBeUndefined();
|
||||
expect(await interceptor.transformToPlain(0, schema)).toBe(0);
|
||||
expect(await interceptor.transformToPlain('', schema)).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateOptions', () => {
|
||||
it('should forward default validateOptions to the schema', async () => {
|
||||
const validateSpy = vi.fn((_value, _options) => ({
|
||||
value: { ok: true },
|
||||
}));
|
||||
const schema = createSchema(validateSpy);
|
||||
const opts: StandardSchemaV1.Options = {
|
||||
libraryOptions: { strip: true },
|
||||
};
|
||||
interceptor = new StandardSchemaSerializerInterceptor(mockReflector, {
|
||||
schema,
|
||||
validateOptions: opts,
|
||||
});
|
||||
const response = { id: 1 };
|
||||
(mockCallHandler.handle as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||
of(response),
|
||||
);
|
||||
|
||||
const result$ = interceptor.intercept(
|
||||
mockExecutionContext,
|
||||
mockCallHandler,
|
||||
);
|
||||
await lastValueFrom(result$);
|
||||
expect(validateSpy).toHaveBeenCalledWith(response, opts);
|
||||
});
|
||||
|
||||
it('should forward context validateOptions from @SerializeOptions()', async () => {
|
||||
const validateSpy = vi.fn((_value, _options) => ({
|
||||
value: { ok: true },
|
||||
}));
|
||||
const schema = createSchema(validateSpy);
|
||||
const contextOpts: StandardSchemaV1.Options = {
|
||||
libraryOptions: { mode: 'strict' },
|
||||
};
|
||||
interceptor = new StandardSchemaSerializerInterceptor(mockReflector, {
|
||||
schema,
|
||||
});
|
||||
mockReflector.getAllAndOverride.mockReturnValue({
|
||||
validateOptions: contextOpts,
|
||||
});
|
||||
const response = { id: 1 };
|
||||
(mockCallHandler.handle as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||
of(response),
|
||||
);
|
||||
|
||||
const result$ = interceptor.intercept(
|
||||
mockExecutionContext,
|
||||
mockCallHandler,
|
||||
);
|
||||
await lastValueFrom(result$);
|
||||
expect(validateSpy).toHaveBeenCalledWith(response, contextOpts);
|
||||
});
|
||||
|
||||
it('should let context validateOptions override default', async () => {
|
||||
const validateSpy = vi.fn((_value, _options) => ({
|
||||
value: { ok: true },
|
||||
}));
|
||||
const schema = createSchema(validateSpy);
|
||||
const defaultOpts: StandardSchemaV1.Options = {
|
||||
libraryOptions: { from: 'default' },
|
||||
};
|
||||
const contextOpts: StandardSchemaV1.Options = {
|
||||
libraryOptions: { from: 'context' },
|
||||
};
|
||||
interceptor = new StandardSchemaSerializerInterceptor(mockReflector, {
|
||||
schema,
|
||||
validateOptions: defaultOpts,
|
||||
});
|
||||
mockReflector.getAllAndOverride.mockReturnValue({
|
||||
validateOptions: contextOpts,
|
||||
});
|
||||
const response = { id: 1 };
|
||||
(mockCallHandler.handle as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||
of(response),
|
||||
);
|
||||
|
||||
const result$ = interceptor.intercept(
|
||||
mockExecutionContext,
|
||||
mockCallHandler,
|
||||
);
|
||||
await lastValueFrom(result$);
|
||||
expect(validateSpy).toHaveBeenCalledWith(response, contextOpts);
|
||||
});
|
||||
|
||||
it('should pass undefined when no validateOptions are set', async () => {
|
||||
const validateSpy = vi.fn((_value, _options) => ({
|
||||
value: { ok: true },
|
||||
}));
|
||||
const schema = createSchema(validateSpy);
|
||||
interceptor = new StandardSchemaSerializerInterceptor(mockReflector, {
|
||||
schema,
|
||||
});
|
||||
const response = { id: 1 };
|
||||
(mockCallHandler.handle as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||
of(response),
|
||||
);
|
||||
|
||||
const result$ = interceptor.intercept(
|
||||
mockExecutionContext,
|
||||
mockCallHandler,
|
||||
);
|
||||
await lastValueFrom(result$);
|
||||
expect(validateSpy).toHaveBeenCalledWith(response, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
import { CUSTOM_ROUTE_ARGS_METADATA } from '../constants.js';
|
||||
import {
|
||||
ParamData,
|
||||
RouteParamMetadata,
|
||||
} from '../decorators/http/route-params.decorator.js';
|
||||
import { PipeTransform, Type } from '../interfaces/index.js';
|
||||
import { CustomParamFactory } from '../interfaces/features/custom-route-param-factory.interface.js';
|
||||
import { PipeTransform, Type } from '../interfaces/index.js';
|
||||
|
||||
export function assignCustomParameterMetadata(
|
||||
args: Record<number, RouteParamMetadata>,
|
||||
@@ -12,6 +13,7 @@ export function assignCustomParameterMetadata(
|
||||
index: number,
|
||||
factory: CustomParamFactory,
|
||||
data?: ParamData,
|
||||
schema?: StandardSchemaV1,
|
||||
...pipes: (Type<PipeTransform> | PipeTransform)[]
|
||||
) {
|
||||
return {
|
||||
@@ -21,6 +23,7 @@ export function assignCustomParameterMetadata(
|
||||
factory,
|
||||
data,
|
||||
pipes,
|
||||
...(schema !== undefined && { schema }),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { HttpServer, RequestMethod, VersioningOptions } from '@nestjs/common';
|
||||
import {
|
||||
RequestHandler,
|
||||
VersionValue,
|
||||
} from '@nestjs/common/interfaces/index.js';
|
||||
import { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface.js';
|
||||
import type {
|
||||
HttpServer,
|
||||
RequestMethod,
|
||||
VersioningOptions,
|
||||
} from '@nestjs/common';
|
||||
import type { RequestHandler, VersionValue } from '@nestjs/common/internal';
|
||||
import type { NestApplicationOptions } from '@nestjs/common';
|
||||
|
||||
/**
|
||||
* @publicApi
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {
|
||||
import type {
|
||||
CanActivate,
|
||||
ExceptionFilter,
|
||||
NestInterceptor,
|
||||
@@ -6,9 +6,9 @@ import {
|
||||
VersioningOptions,
|
||||
WebSocketAdapter,
|
||||
} from '@nestjs/common';
|
||||
import { GlobalPrefixOptions } from '@nestjs/common/interfaces/index.js';
|
||||
import { InstanceWrapper } from './injector/instance-wrapper.js';
|
||||
import { ExcludeRouteMetadata } from './router/interfaces/exclude-route-metadata.interface.js';
|
||||
import type { GlobalPrefixOptions } from '@nestjs/common/internal';
|
||||
|
||||
export class ApplicationConfig {
|
||||
private globalPrefix = '';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EnhancerSubtype } from '@nestjs/common/constants.js';
|
||||
import type { EnhancerSubtype } from '@nestjs/common/internal';
|
||||
|
||||
export const MESSAGES = {
|
||||
APPLICATION_START: `Starting Nest application...`,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Type } from '@nestjs/common';
|
||||
import type { Type } from '@nestjs/common';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper.js';
|
||||
import { ModulesContainer } from '../injector/modules-container.js';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
CustomDecorator,
|
||||
type CustomDecorator,
|
||||
flatten,
|
||||
Injectable,
|
||||
SetMetadata,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Logger } from '@nestjs/common/services/logger.service.js';
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
export class ExceptionHandler {
|
||||
private static readonly logger = new Logger(ExceptionHandler.name);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Abstract, Type } from '@nestjs/common/interfaces/index.js';
|
||||
import { isFunction } from '@nestjs/common/utils/shared.utils.js';
|
||||
import { INVALID_CLASS_SCOPE_MESSAGE } from '../messages.js';
|
||||
import { RuntimeException } from './runtime.exception.js';
|
||||
import type { Abstract, Type } from '@nestjs/common';
|
||||
import { isFunction } from '@nestjs/common/internal';
|
||||
|
||||
export class InvalidClassScopeException extends RuntimeException {
|
||||
constructor(metatypeOrToken: Type<any> | Abstract<any> | string | symbol) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { UNDEFINED_FORWARDREF_MESSAGE } from '../messages.js';
|
||||
import { RuntimeException } from './runtime.exception.js';
|
||||
import { Type } from '@nestjs/common';
|
||||
import type { Type } from '@nestjs/common';
|
||||
|
||||
export class UndefinedForwardRefException extends RuntimeException {
|
||||
constructor(scope: Type<any>[]) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { DynamicModule, ForwardReference, Type } from '@nestjs/common';
|
||||
import { isNil, isSymbol } from '@nestjs/common/utils/shared.utils.js';
|
||||
import {
|
||||
InjectorDependency,
|
||||
InjectorDependencyContext,
|
||||
} from '../injector/injector.js';
|
||||
import { Module } from '../injector/module.js';
|
||||
import { isNil, isSymbol } from '@nestjs/common/internal';
|
||||
|
||||
/**
|
||||
* Returns the name of an instance or `undefined`
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { FILTER_CATCH_EXCEPTIONS } from '@nestjs/common/constants.js';
|
||||
import { Type } from '@nestjs/common/interfaces/index.js';
|
||||
import { ExceptionFilter } from '@nestjs/common/interfaces/exceptions/exception-filter.interface.js';
|
||||
import { isEmpty, isFunction } from '@nestjs/common/utils/shared.utils.js';
|
||||
import { iterate } from 'iterare';
|
||||
import { ContextCreator } from '../helpers/context-creator.js';
|
||||
import { STATIC_CONTEXT } from '../injector/constants.js';
|
||||
import { NestContainer } from '../injector/container.js';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper.js';
|
||||
import {
|
||||
FILTER_CATCH_EXCEPTIONS,
|
||||
isEmpty,
|
||||
isFunction,
|
||||
} from '@nestjs/common/internal';
|
||||
import type { Type, ExceptionFilter } from '@nestjs/common';
|
||||
|
||||
export class BaseExceptionFilterContext extends ContextCreator {
|
||||
protected moduleContext: string;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import {
|
||||
ArgumentsHost,
|
||||
ExceptionFilter,
|
||||
type ArgumentsHost,
|
||||
type ExceptionFilter,
|
||||
HttpException,
|
||||
HttpServer,
|
||||
type HttpServer,
|
||||
HttpStatus,
|
||||
Inject,
|
||||
IntrinsicException,
|
||||
Logger,
|
||||
Optional,
|
||||
} from '@nestjs/common';
|
||||
import { isObject } from '@nestjs/common/utils/shared.utils.js';
|
||||
import { AbstractHttpAdapter } from '../adapters/index.js';
|
||||
import { MESSAGES } from '../constants.js';
|
||||
import { HttpAdapterHost } from '../helpers/http-adapter-host.js';
|
||||
import { isObject } from '@nestjs/common/internal';
|
||||
|
||||
export class BaseExceptionFilter<T = any> implements ExceptionFilter<T> {
|
||||
private static readonly logger = new Logger('ExceptionsHandler');
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { HttpException } from '@nestjs/common';
|
||||
import { ExceptionFilterMetadata } from '@nestjs/common/interfaces/exceptions/exception-filter-metadata.interface.js';
|
||||
import { ArgumentsHost } from '@nestjs/common/interfaces/features/arguments-host.interface.js';
|
||||
import { selectExceptionFilterMetadata } from '@nestjs/common/utils/select-exception-filter-metadata.util.js';
|
||||
import { isEmpty } from '@nestjs/common/utils/shared.utils.js';
|
||||
import type { HttpException } from '@nestjs/common';
|
||||
import { InvalidExceptionFilterException } from '../errors/exceptions/invalid-exception-filter.exception.js';
|
||||
import { BaseExceptionFilter } from './base-exception-filter.js';
|
||||
import {
|
||||
type ExceptionFilterMetadata,
|
||||
selectExceptionFilterMetadata,
|
||||
isEmpty,
|
||||
} from '@nestjs/common/internal';
|
||||
import type { ArgumentsHost } from '@nestjs/common';
|
||||
|
||||
export class ExceptionsHandler extends BaseExceptionFilter {
|
||||
private filters: ExceptionFilterMetadata[] = [];
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import { EXCEPTION_FILTERS_METADATA } from '@nestjs/common/constants.js';
|
||||
import { Controller } from '@nestjs/common/interfaces/index.js';
|
||||
import { ExceptionFilterMetadata } from '@nestjs/common/interfaces/exceptions/index.js';
|
||||
import { isEmpty } from '@nestjs/common/utils/shared.utils.js';
|
||||
import { iterate } from 'iterare';
|
||||
import { ApplicationConfig } from '../application-config.js';
|
||||
import { STATIC_CONTEXT } from '../injector/constants.js';
|
||||
@@ -10,6 +6,12 @@ import { InstanceWrapper } from '../injector/instance-wrapper.js';
|
||||
import { RouterProxyCallback } from '../router/router-proxy.js';
|
||||
import { BaseExceptionFilterContext } from './base-exception-filter-context.js';
|
||||
import { ExternalExceptionsHandler } from './external-exceptions-handler.js';
|
||||
import {
|
||||
EXCEPTION_FILTERS_METADATA,
|
||||
type Controller,
|
||||
type ExceptionFilterMetadata,
|
||||
isEmpty,
|
||||
} from '@nestjs/common/internal';
|
||||
|
||||
export class ExternalExceptionFilterContext extends BaseExceptionFilterContext {
|
||||
constructor(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ArgumentsHost, IntrinsicException, Logger } from '@nestjs/common';
|
||||
import { type ArgumentsHost, IntrinsicException, Logger } from '@nestjs/common';
|
||||
|
||||
export class ExternalExceptionFilter<T = any, R = any> {
|
||||
private static readonly logger = new Logger('ExceptionsHandler');
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { ExceptionFilterMetadata } from '@nestjs/common/interfaces/exceptions/index.js';
|
||||
import { ArgumentsHost } from '@nestjs/common/interfaces/features/arguments-host.interface.js';
|
||||
import { selectExceptionFilterMetadata } from '@nestjs/common/utils/select-exception-filter-metadata.util.js';
|
||||
import { isEmpty } from '@nestjs/common/utils/shared.utils.js';
|
||||
import { InvalidExceptionFilterException } from '../errors/exceptions/invalid-exception-filter.exception.js';
|
||||
import { ExternalExceptionFilter } from './external-exception-filter.js';
|
||||
import {
|
||||
type ExceptionFilterMetadata,
|
||||
selectExceptionFilterMetadata,
|
||||
isEmpty,
|
||||
} from '@nestjs/common/internal';
|
||||
import type { ArgumentsHost } from '@nestjs/common';
|
||||
|
||||
export class ExternalExceptionsHandler extends ExternalExceptionFilter {
|
||||
private filters: ExceptionFilterMetadata[] = [];
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { CanActivate } from '@nestjs/common';
|
||||
import { ContextType, Controller } from '@nestjs/common/interfaces/index.js';
|
||||
import { isEmpty } from '@nestjs/common/utils/shared.utils.js';
|
||||
import type { CanActivate } from '@nestjs/common';
|
||||
import { lastValueFrom, Observable } from 'rxjs';
|
||||
import { ExecutionContextHost } from '../helpers/execution-context-host.js';
|
||||
import type { ContextType } from '@nestjs/common';
|
||||
import { type Controller, isEmpty } from '@nestjs/common/internal';
|
||||
|
||||
export class GuardsConsumer {
|
||||
public async tryActivate<TContext extends string = ContextType>(
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { CanActivate } from '@nestjs/common';
|
||||
import { GUARDS_METADATA } from '@nestjs/common/constants.js';
|
||||
import { Controller, Type } from '@nestjs/common/interfaces/index.js';
|
||||
import { isEmpty, isFunction } from '@nestjs/common/utils/shared.utils.js';
|
||||
import type { CanActivate } from '@nestjs/common';
|
||||
import { iterate } from 'iterare';
|
||||
import { ApplicationConfig } from '../application-config.js';
|
||||
import { ContextCreator } from '../helpers/context-creator.js';
|
||||
import { STATIC_CONTEXT } from '../injector/constants.js';
|
||||
import { NestContainer } from '../injector/container.js';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper.js';
|
||||
import {
|
||||
GUARDS_METADATA,
|
||||
type Controller,
|
||||
isEmpty,
|
||||
isFunction,
|
||||
} from '@nestjs/common/internal';
|
||||
import type { Type } from '@nestjs/common';
|
||||
|
||||
export class GuardsContextCreator extends ContextCreator {
|
||||
private moduleContext: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Controller } from '@nestjs/common/interfaces/index.js';
|
||||
import { STATIC_CONTEXT } from '../injector/constants.js';
|
||||
import { ContextId, InstanceWrapper } from '../injector/instance-wrapper.js';
|
||||
import type { Controller } from '@nestjs/common/internal';
|
||||
|
||||
export abstract class ContextCreator {
|
||||
public abstract createConcreteContext<T extends any[], R extends any[]>(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { isObject } from '@nestjs/common/utils/shared.utils.js';
|
||||
import { ContextId, HostComponentInfo } from '../injector/instance-wrapper.js';
|
||||
import { REQUEST_CONTEXT_ID } from '../router/request/request-constants.js';
|
||||
import { isObject } from '@nestjs/common/internal';
|
||||
|
||||
export function createContextId(): ContextId {
|
||||
/**
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { ParamData } from '@nestjs/common';
|
||||
import type { ParamData } from '@nestjs/common';
|
||||
import { ExecutionContextHost } from './execution-context-host.js';
|
||||
import {
|
||||
PARAMTYPES_METADATA,
|
||||
RESPONSE_PASSTHROUGH_METADATA,
|
||||
} from '@nestjs/common/constants.js';
|
||||
import {
|
||||
ContextType,
|
||||
Controller,
|
||||
PipeTransform,
|
||||
Type,
|
||||
} from '@nestjs/common/interfaces/index.js';
|
||||
import { isFunction } from '@nestjs/common/utils/shared.utils.js';
|
||||
import { ExecutionContextHost } from './execution-context-host.js';
|
||||
type Controller,
|
||||
isFunction,
|
||||
} from '@nestjs/common/internal';
|
||||
import type { ContextType, PipeTransform, Type } from '@nestjs/common';
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
|
||||
export interface ParamProperties<T = any, IExtractor extends Function = any> {
|
||||
index: number;
|
||||
@@ -18,6 +15,7 @@ export interface ParamProperties<T = any, IExtractor extends Function = any> {
|
||||
data: ParamData;
|
||||
pipes: PipeTransform[];
|
||||
extractValue: IExtractor;
|
||||
schema?: StandardSchemaV1;
|
||||
}
|
||||
|
||||
export class ContextUtils {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { ExecutionContext } from '@nestjs/common';
|
||||
import { Type } from '@nestjs/common/interfaces/index.js';
|
||||
import {
|
||||
ContextType,
|
||||
import type { ExecutionContext } from '@nestjs/common';
|
||||
import type { Type, ContextType } from '@nestjs/common';
|
||||
import type {
|
||||
HttpArgumentsHost,
|
||||
RpcArgumentsHost,
|
||||
WsArgumentsHost,
|
||||
} from '@nestjs/common/interfaces/features/arguments-host.interface.js';
|
||||
} from '@nestjs/common/internal';
|
||||
|
||||
export class ExecutionContextHost implements ExecutionContext {
|
||||
private contextType = 'http';
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { ForbiddenException, ParamData } from '@nestjs/common';
|
||||
import { CUSTOM_ROUTE_ARGS_METADATA } from '@nestjs/common/constants.js';
|
||||
import type { ContextType, PipeTransform } from '@nestjs/common';
|
||||
import {
|
||||
ContextType,
|
||||
Controller,
|
||||
PipeTransform,
|
||||
} from '@nestjs/common/interfaces/index.js';
|
||||
import { isEmpty } from '@nestjs/common/utils/shared.utils.js';
|
||||
ArgumentMetadata,
|
||||
ForbiddenException,
|
||||
type ParamData,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
CUSTOM_ROUTE_ARGS_METADATA,
|
||||
type Controller,
|
||||
isEmpty,
|
||||
} from '@nestjs/common/internal';
|
||||
import { isObservable, lastValueFrom } from 'rxjs';
|
||||
import { ExternalExceptionFilterContext } from '../exceptions/external-exception-filter-context.js';
|
||||
import { GuardsConsumer, GuardsContextCreator } from '../guards/index.js';
|
||||
import { FORBIDDEN_MESSAGE } from '../guards/constants.js';
|
||||
import { GuardsConsumer, GuardsContextCreator } from '../guards/index.js';
|
||||
import { STATIC_CONTEXT } from '../injector/constants.js';
|
||||
import { NestContainer } from '../injector/container.js';
|
||||
import { ContextId } from '../injector/instance-wrapper.js';
|
||||
@@ -264,7 +267,7 @@ export class ExternalContextCreator {
|
||||
this.pipesContextCreator.setModuleContext(moduleContext);
|
||||
|
||||
return keys.map(key => {
|
||||
const { index, data, pipes: pipesCollection } = metadata[key];
|
||||
const { index, data, pipes: pipesCollection, schema } = metadata[key];
|
||||
const pipes = this.pipesContextCreator.createConcreteContext(
|
||||
pipesCollection,
|
||||
contextId,
|
||||
@@ -279,13 +282,20 @@ export class ExternalContextCreator {
|
||||
data,
|
||||
contextFactory,
|
||||
);
|
||||
return { index, extractValue: customExtractValue, type, data, pipes };
|
||||
return {
|
||||
index,
|
||||
extractValue: customExtractValue,
|
||||
type,
|
||||
data,
|
||||
pipes,
|
||||
schema,
|
||||
};
|
||||
}
|
||||
const numericType = Number(type);
|
||||
const extractValue = (...args: unknown[]) =>
|
||||
paramsFactory.exchangeKeyForValue(numericType, data, args);
|
||||
|
||||
return { index, extractValue, type: numericType, data, pipes };
|
||||
return { index, extractValue, type: numericType, data, pipes, schema };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -304,12 +314,13 @@ export class ExternalContextCreator {
|
||||
data,
|
||||
metatype,
|
||||
pipes: paramPipes,
|
||||
schema,
|
||||
} = param;
|
||||
const value = extractValue(...params);
|
||||
|
||||
args[index] = await this.getParamValue(
|
||||
value,
|
||||
{ metatype, type, data },
|
||||
{ metatype, type, data, schema } as ArgumentMetadata,
|
||||
pipes.concat(paramPipes),
|
||||
);
|
||||
};
|
||||
@@ -320,12 +331,12 @@ export class ExternalContextCreator {
|
||||
|
||||
public async getParamValue<T>(
|
||||
value: T,
|
||||
{ metatype, type, data }: { metatype: any; type: any; data: any },
|
||||
metadata: ArgumentMetadata,
|
||||
pipes: PipeTransform[],
|
||||
): Promise<any> {
|
||||
return isEmpty(pipes)
|
||||
? value
|
||||
: this.pipesConsumer.apply(value, { metatype, type, data }, pipes);
|
||||
: this.pipesConsumer.apply(value, metadata, pipes);
|
||||
}
|
||||
|
||||
public async transformToResult(resultOrDeferred: any) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ContextType } from '@nestjs/common/interfaces/index.js';
|
||||
import { ExternalExceptionsHandler } from '../exceptions/external-exceptions-handler.js';
|
||||
import { ExecutionContextHost } from '../helpers/execution-context-host.js';
|
||||
import type { ContextType } from '@nestjs/common';
|
||||
|
||||
export class ExternalErrorProxy {
|
||||
public createProxy<TContext extends string = ContextType>(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { SCOPE_OPTIONS_METADATA } from '@nestjs/common/constants.js';
|
||||
import { Type } from '@nestjs/common/interfaces/type.interface.js';
|
||||
import type { Scope } from '@nestjs/common';
|
||||
import { SCOPE_OPTIONS_METADATA } from '@nestjs/common/internal';
|
||||
import type { Type } from '@nestjs/common';
|
||||
|
||||
export function getClassScope(provider: Type<unknown>): Scope {
|
||||
const metadata = Reflect.getMetadata(SCOPE_OPTIONS_METADATA, provider);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Controller, Type } from '@nestjs/common/interfaces/index.js';
|
||||
import { IncomingMessage } from 'http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { CONTROLLER_ID_KEY } from '../injector/constants.js';
|
||||
import { ContextId } from '../injector/instance-wrapper.js';
|
||||
import { HeaderStream } from '../router/sse-stream.js';
|
||||
import { ParamProperties } from './context-utils.js';
|
||||
import type { Type } from '@nestjs/common';
|
||||
import type { Controller } from '@nestjs/common/internal';
|
||||
|
||||
export const HANDLER_METADATA_SYMBOL = Symbol.for('handler_metadata:cache');
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ParamData } from '@nestjs/common';
|
||||
import type { ParamData } from '@nestjs/common';
|
||||
|
||||
export type ParamsMetadata = Record<number, ParamMetadata>;
|
||||
export interface ParamMetadata {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SCOPE_OPTIONS_METADATA } from '@nestjs/common/constants.js';
|
||||
import { Type } from '@nestjs/common/interfaces/type.interface.js';
|
||||
import { SCOPE_OPTIONS_METADATA } from '@nestjs/common/internal';
|
||||
import type { Type } from '@nestjs/common';
|
||||
|
||||
export function isDurable(provider: Type<unknown>): boolean | undefined {
|
||||
const metadata = Reflect.getMetadata(SCOPE_OPTIONS_METADATA, provider);
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { RequestMethod } from '@nestjs/common/enums/request-method.enum.js';
|
||||
import {
|
||||
VersionValue,
|
||||
VERSION_NEUTRAL,
|
||||
} from '@nestjs/common/interfaces/version-options.interface.js';
|
||||
import { RequestMethod, VERSION_NEUTRAL } from '@nestjs/common';
|
||||
import type { VersionValue } from '@nestjs/common/internal';
|
||||
|
||||
export const MODULE_INIT_MESSAGE = (
|
||||
text: TemplateStringsArray,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { HttpServer } from '@nestjs/common';
|
||||
import { RequestMethod } from '@nestjs/common/enums/request-method.enum.js';
|
||||
import type { HttpServer } from '@nestjs/common';
|
||||
import { RequestMethod } from '@nestjs/common';
|
||||
|
||||
export const REQUEST_METHOD_MAP = {
|
||||
[RequestMethod.GET]: 'get',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { BeforeApplicationShutdown } from '@nestjs/common';
|
||||
import { isFunction, isNil } from '@nestjs/common/utils/shared.utils.js';
|
||||
import type { BeforeApplicationShutdown } from '@nestjs/common';
|
||||
import { iterate } from 'iterare';
|
||||
import {
|
||||
getNonTransientInstances,
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
} from '../injector/helpers/transient-instances.js';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper.js';
|
||||
import { Module } from '../injector/module.js';
|
||||
import { isFunction, isNil } from '@nestjs/common/internal';
|
||||
|
||||
/**
|
||||
* Checks if the given instance has the `beforeApplicationShutdown` function
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { OnApplicationBootstrap } from '@nestjs/common';
|
||||
import { isFunction, isNil } from '@nestjs/common/utils/shared.utils.js';
|
||||
import type { OnApplicationBootstrap } from '@nestjs/common';
|
||||
import { iterate } from 'iterare';
|
||||
import {
|
||||
getNonTransientInstances,
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
} from '../injector/helpers/transient-instances.js';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper.js';
|
||||
import { Module } from '../injector/module.js';
|
||||
import { isFunction, isNil } from '@nestjs/common/internal';
|
||||
|
||||
/**
|
||||
* Checks if the given instance has the `onApplicationBootstrap` function
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { OnApplicationShutdown } from '@nestjs/common';
|
||||
import { isFunction, isNil } from '@nestjs/common/utils/shared.utils.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
import { iterate } from 'iterare';
|
||||
import {
|
||||
getNonTransientInstances,
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
} from '../injector/helpers/transient-instances.js';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper.js';
|
||||
import { Module } from '../injector/module.js';
|
||||
import { isFunction, isNil } from '@nestjs/common/internal';
|
||||
|
||||
/**
|
||||
* Checks if the given instance has the `onApplicationShutdown` function
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { OnModuleDestroy } from '@nestjs/common';
|
||||
import { isFunction, isNil } from '@nestjs/common/utils/shared.utils.js';
|
||||
import type { OnModuleDestroy } from '@nestjs/common';
|
||||
import { iterate } from 'iterare';
|
||||
import {
|
||||
getNonTransientInstances,
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
} from '../injector/helpers/transient-instances.js';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper.js';
|
||||
import { Module } from '../injector/module.js';
|
||||
import { isFunction, isNil } from '@nestjs/common/internal';
|
||||
|
||||
/**
|
||||
* Returns true or false if the given instance has a `onModuleDestroy` function
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { OnModuleInit } from '@nestjs/common';
|
||||
import { isFunction, isNil } from '@nestjs/common/utils/shared.utils.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
import { iterate } from 'iterare';
|
||||
import {
|
||||
getNonTransientInstances,
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
} from '../injector/helpers/transient-instances.js';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper.js';
|
||||
import { Module } from '../injector/module.js';
|
||||
import { isFunction, isNil } from '@nestjs/common/internal';
|
||||
|
||||
/**
|
||||
* Returns true or false if the given instance has a `onModuleInit` function
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Abstract, Scope, Type } from '@nestjs/common';
|
||||
import { GetOrResolveOptions } from '@nestjs/common/interfaces/index.js';
|
||||
import { type Abstract, Scope, type Type } from '@nestjs/common';
|
||||
import {
|
||||
InvalidClassScopeException,
|
||||
UnknownElementException,
|
||||
@@ -8,6 +7,7 @@ import { Injector } from './injector.js';
|
||||
import { InstanceLink, InstanceLinksHost } from './instance-links-host.js';
|
||||
import { ContextId } from './instance-wrapper.js';
|
||||
import { Module } from './module.js';
|
||||
import type { GetOrResolveOptions } from '@nestjs/common/internal';
|
||||
|
||||
export abstract class AbstractInstanceResolver {
|
||||
protected abstract instanceLinksHost: InstanceLinksHost;
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import {
|
||||
DynamicModule,
|
||||
ForwardReference,
|
||||
Type,
|
||||
} from '@nestjs/common/interfaces/index.js';
|
||||
import { ModuleOpaqueKeyFactory } from './opaque-key-factory/interfaces/module-opaque-key-factory.interface.js';
|
||||
import type { DynamicModule, ForwardReference, Type } from '@nestjs/common';
|
||||
|
||||
export interface ModuleFactory {
|
||||
type: Type<any>;
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import { DynamicModule, Provider } from '@nestjs/common';
|
||||
import {
|
||||
EnhancerSubtype,
|
||||
GLOBAL_MODULE_METADATA,
|
||||
} from '@nestjs/common/constants.js';
|
||||
import { Injectable, Type } from '@nestjs/common/interfaces/index.js';
|
||||
import { NestApplicationContextOptions } from '@nestjs/common/interfaces/nest-application-context-options.interface.js';
|
||||
import type { DynamicModule, Provider } from '@nestjs/common';
|
||||
import { ApplicationConfig } from '../application-config.js';
|
||||
import { DiscoverableMetaHostCollection } from '../discovery/discoverable-meta-host-collection.js';
|
||||
import {
|
||||
@@ -24,6 +18,13 @@ import { ModulesContainer } from './modules-container.js';
|
||||
import { ByReferenceModuleOpaqueKeyFactory } from './opaque-key-factory/by-reference-module-opaque-key-factory.js';
|
||||
import { DeepHashedModuleOpaqueKeyFactory } from './opaque-key-factory/deep-hashed-module-opaque-key-factory.js';
|
||||
import { ModuleOpaqueKeyFactory } from './opaque-key-factory/interfaces/module-opaque-key-factory.interface.js';
|
||||
import {
|
||||
type EnhancerSubtype,
|
||||
GLOBAL_MODULE_METADATA,
|
||||
type Injectable,
|
||||
type NestApplicationContextOptions,
|
||||
} from '@nestjs/common/internal';
|
||||
import type { Type } from '@nestjs/common';
|
||||
|
||||
type ModuleMetatype = Type<any> | DynamicModule | Promise<DynamicModule>;
|
||||
type ModuleScope = Type<any>[];
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
import type {
|
||||
ClassProvider,
|
||||
FactoryProvider,
|
||||
Provider,
|
||||
ValueProvider,
|
||||
} from '@nestjs/common';
|
||||
import { isUndefined } from '@nestjs/common/utils/shared.utils.js';
|
||||
import { isUndefined } from '@nestjs/common/internal';
|
||||
|
||||
export function isClassProvider<T = any>(
|
||||
provider: Provider,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InjectionToken } from '@nestjs/common';
|
||||
import type { InjectionToken } from '@nestjs/common';
|
||||
import { iterate } from 'iterare';
|
||||
import { InstanceWrapper } from '../instance-wrapper.js';
|
||||
|
||||
|
||||
@@ -1,31 +1,9 @@
|
||||
import {
|
||||
InjectionToken,
|
||||
type InjectionToken,
|
||||
Logger,
|
||||
LoggerService,
|
||||
OptionalFactoryDependency,
|
||||
type LoggerService,
|
||||
type OptionalFactoryDependency,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
OPTIONAL_DEPS_METADATA,
|
||||
OPTIONAL_PROPERTY_DEPS_METADATA,
|
||||
PARAMTYPES_METADATA,
|
||||
PROPERTY_DEPS_METADATA,
|
||||
SELF_DECLARED_DEPS_METADATA,
|
||||
} from '@nestjs/common/constants.js';
|
||||
import {
|
||||
Controller,
|
||||
ForwardReference,
|
||||
Injectable,
|
||||
Type,
|
||||
} from '@nestjs/common/interfaces/index.js';
|
||||
import { clc } from '@nestjs/common/utils/cli-colors.util.js';
|
||||
import {
|
||||
isFunction,
|
||||
isNil,
|
||||
isObject,
|
||||
isString,
|
||||
isSymbol,
|
||||
isUndefined,
|
||||
} from '@nestjs/common/utils/shared.utils.js';
|
||||
import { iterate } from 'iterare';
|
||||
import { performance } from 'perf_hooks';
|
||||
import { CircularDependencyException } from '../errors/exceptions/index.js';
|
||||
@@ -43,6 +21,23 @@ import {
|
||||
} from './instance-wrapper.js';
|
||||
import { Module } from './module.js';
|
||||
import { SettlementSignal } from './settlement-signal.js';
|
||||
import {
|
||||
OPTIONAL_DEPS_METADATA,
|
||||
OPTIONAL_PROPERTY_DEPS_METADATA,
|
||||
PARAMTYPES_METADATA,
|
||||
PROPERTY_DEPS_METADATA,
|
||||
SELF_DECLARED_DEPS_METADATA,
|
||||
type Controller,
|
||||
type Injectable,
|
||||
clc,
|
||||
isFunction,
|
||||
isNil,
|
||||
isObject,
|
||||
isString,
|
||||
isSymbol,
|
||||
isUndefined,
|
||||
} from '@nestjs/common/internal';
|
||||
import type { ForwardReference, Type } from '@nestjs/common';
|
||||
|
||||
/**
|
||||
* The type of an injectable dependency
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Provider, Scope } from '@nestjs/common';
|
||||
import { type Provider, Scope } from '@nestjs/common';
|
||||
import { INQUIRER } from './inquirer-constants.js';
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { InjectionToken } from '@nestjs/common';
|
||||
import { isFunction } from '@nestjs/common/utils/shared.utils.js';
|
||||
import type { InjectionToken } from '@nestjs/common';
|
||||
import { UnknownElementException } from '../errors/exceptions/unknown-element.exception.js';
|
||||
import { NestContainer } from './container.js';
|
||||
import { InstanceWrapper } from './instance-wrapper.js';
|
||||
import { Module } from './module.js';
|
||||
import { isFunction } from '@nestjs/common/internal';
|
||||
|
||||
type HostCollection = 'providers' | 'controllers' | 'injectables';
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Logger, LoggerService } from '@nestjs/common';
|
||||
import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface.js';
|
||||
import { Injectable } from '@nestjs/common/interfaces/injectable.interface.js';
|
||||
import { Logger, type LoggerService } from '@nestjs/common';
|
||||
import { MODULE_INIT_MESSAGE } from '../helpers/messages.js';
|
||||
import { GraphInspector } from '../inspector/graph-inspector.js';
|
||||
import { NestContainer } from './container.js';
|
||||
import { Injector } from './injector.js';
|
||||
import { InternalCoreModule } from './internal-core-module/internal-core-module.js';
|
||||
import { Module } from './module.js';
|
||||
import type { Controller, Injectable } from '@nestjs/common/internal';
|
||||
|
||||
export class InstanceLoader<TInjector extends Injector = Injector> {
|
||||
constructor(
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import { Logger, LoggerService, Provider, Scope, Type } from '@nestjs/common';
|
||||
import { EnhancerSubtype } from '@nestjs/common/constants.js';
|
||||
import {
|
||||
FactoryProvider,
|
||||
InjectionToken,
|
||||
} from '@nestjs/common/interfaces/index.js';
|
||||
import { clc } from '@nestjs/common/utils/cli-colors.util.js';
|
||||
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util.js';
|
||||
import {
|
||||
isNil,
|
||||
isString,
|
||||
isUndefined,
|
||||
} from '@nestjs/common/utils/shared.utils.js';
|
||||
Logger,
|
||||
type LoggerService,
|
||||
type Provider,
|
||||
Scope,
|
||||
type Type,
|
||||
} from '@nestjs/common';
|
||||
import { iterate } from 'iterare';
|
||||
import { UuidFactory } from '../inspector/uuid-factory.js';
|
||||
import { STATIC_CONTEXT } from './constants.js';
|
||||
@@ -21,6 +15,15 @@ import {
|
||||
} from './helpers/provider-classifier.js';
|
||||
import { Module } from './module.js';
|
||||
import { SettlementSignal } from './settlement-signal.js';
|
||||
import {
|
||||
type EnhancerSubtype,
|
||||
clc,
|
||||
randomStringGenerator,
|
||||
isNil,
|
||||
isString,
|
||||
isUndefined,
|
||||
} from '@nestjs/common/internal';
|
||||
import type { FactoryProvider, InjectionToken } from '@nestjs/common';
|
||||
|
||||
export const INSTANCE_METADATA_SYMBOL = Symbol.for('instance_metadata:cache');
|
||||
export const INSTANCE_ID_SYMBOL = Symbol.for('instance_metadata:id');
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { DynamicModule, Global, Module } from '@nestjs/common';
|
||||
import {
|
||||
ExistingProvider,
|
||||
FactoryProvider,
|
||||
ValueProvider,
|
||||
} from '@nestjs/common/interfaces/index.js';
|
||||
import { type DynamicModule, Global, Module } from '@nestjs/common';
|
||||
import { requestProvider } from '../../router/request/request-providers.js';
|
||||
import { Reflector } from '../../services/index.js';
|
||||
import { inquirerProvider } from '../inquirer/inquirer-providers.js';
|
||||
import type {
|
||||
ExistingProvider,
|
||||
FactoryProvider,
|
||||
ValueProvider,
|
||||
} from '@nestjs/common';
|
||||
|
||||
const ReflectorAliasProvider = {
|
||||
provide: Reflector.name,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DynamicModule, Type } from '@nestjs/common';
|
||||
import type { DynamicModule, Type } from '@nestjs/common';
|
||||
import { ModuleOverride } from '../../interfaces/module-override.interface.js';
|
||||
import { DependenciesScanner } from '../../scanner.js';
|
||||
import { ModuleCompiler } from '../compiler.js';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IntrospectionResult, Scope, Type } from '@nestjs/common';
|
||||
import { type IntrospectionResult, Scope, type Type } from '@nestjs/common';
|
||||
import { getClassScope } from '../helpers/get-class-scope.js';
|
||||
import { isDurable } from '../helpers/is-durable.js';
|
||||
import { AbstractInstanceResolver } from './abstract-instance-resolver.js';
|
||||
|
||||
@@ -1,30 +1,3 @@
|
||||
import {
|
||||
EnhancerSubtype,
|
||||
ENTRY_PROVIDER_WATERMARK,
|
||||
} from '@nestjs/common/constants.js';
|
||||
import {
|
||||
ClassProvider,
|
||||
Controller,
|
||||
DynamicModule,
|
||||
ExistingProvider,
|
||||
FactoryProvider,
|
||||
Injectable,
|
||||
InjectionToken,
|
||||
NestModule,
|
||||
Provider,
|
||||
Scope,
|
||||
Type,
|
||||
ValueProvider,
|
||||
} from '@nestjs/common/interfaces/index.js';
|
||||
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util.js';
|
||||
import {
|
||||
isFunction,
|
||||
isNil,
|
||||
isObject,
|
||||
isString,
|
||||
isSymbol,
|
||||
isUndefined,
|
||||
} from '@nestjs/common/utils/shared.utils.js';
|
||||
import { iterate } from 'iterare';
|
||||
import { ApplicationConfig } from '../application-config.js';
|
||||
import {
|
||||
@@ -40,6 +13,31 @@ import { CONTROLLER_ID_KEY } from './constants.js';
|
||||
import { NestContainer } from './container.js';
|
||||
import { ContextId, InstanceWrapper } from './instance-wrapper.js';
|
||||
import { ModuleRef, ModuleRefGetOrResolveOpts } from './module-ref.js';
|
||||
import {
|
||||
type EnhancerSubtype,
|
||||
ENTRY_PROVIDER_WATERMARK,
|
||||
type Controller,
|
||||
type Injectable,
|
||||
randomStringGenerator,
|
||||
isFunction,
|
||||
isNil,
|
||||
isObject,
|
||||
isString,
|
||||
isSymbol,
|
||||
isUndefined,
|
||||
} from '@nestjs/common/internal';
|
||||
import {
|
||||
type ClassProvider,
|
||||
type DynamicModule,
|
||||
type ExistingProvider,
|
||||
type FactoryProvider,
|
||||
type InjectionToken,
|
||||
type NestModule,
|
||||
type Provider,
|
||||
Scope,
|
||||
type Type,
|
||||
type ValueProvider,
|
||||
} from '@nestjs/common';
|
||||
|
||||
export class Module {
|
||||
private readonly _id: string;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { DynamicModule } from '@nestjs/common/interfaces/modules/dynamic-module.interface.js';
|
||||
import { ForwardReference } from '@nestjs/common/interfaces/modules/forward-reference.interface.js';
|
||||
import { Type } from '@nestjs/common/interfaces/type.interface.js';
|
||||
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util.js';
|
||||
import { createHash } from 'crypto';
|
||||
import { ModuleOpaqueKeyFactory } from './interfaces/module-opaque-key-factory.interface.js';
|
||||
import type { DynamicModule, ForwardReference, Type } from '@nestjs/common';
|
||||
import { randomStringGenerator } from '@nestjs/common/internal';
|
||||
|
||||
const K_MODULE_ID = Symbol('K_MODULE_ID');
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { DynamicModule } from '@nestjs/common/interfaces/modules/dynamic-module.interface.js';
|
||||
import { Type } from '@nestjs/common/interfaces/type.interface.js';
|
||||
import { Logger } from '@nestjs/common/services/logger.service.js';
|
||||
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util.js';
|
||||
import { isFunction, isSymbol } from '@nestjs/common/utils/shared.utils.js';
|
||||
import { createHash } from 'crypto';
|
||||
import _stringify from 'fast-safe-stringify';
|
||||
import { ModuleOpaqueKeyFactory } from './interfaces/module-opaque-key-factory.interface.js';
|
||||
import { type DynamicModule, type Type, Logger } from '@nestjs/common';
|
||||
import {
|
||||
randomStringGenerator,
|
||||
isFunction,
|
||||
isSymbol,
|
||||
} from '@nestjs/common/internal';
|
||||
// CJS interop: fast-safe-stringify sets module.exports.default = module.exports
|
||||
const stringify = ((_stringify as any).default ?? _stringify) as unknown as (
|
||||
value: any,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { DynamicModule } from '@nestjs/common/interfaces/modules/dynamic-module.interface.js';
|
||||
import { ForwardReference } from '@nestjs/common/interfaces/modules/forward-reference.interface.js';
|
||||
import { Type } from '@nestjs/common/interfaces/type.interface.js';
|
||||
import type { DynamicModule, ForwardReference, Type } from '@nestjs/common';
|
||||
|
||||
export interface ModuleOpaqueKeyFactory {
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Type } from '@nestjs/common';
|
||||
import type { Type } from '@nestjs/common';
|
||||
|
||||
export class InitializeOnPreviewAllowlist {
|
||||
private static readonly allowlist = new WeakMap<Type, boolean>();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InjectionToken } from '@nestjs/common';
|
||||
import type { InjectionToken } from '@nestjs/common';
|
||||
|
||||
type CommonEdgeMetadata = {
|
||||
sourceModuleName: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Type } from '@nestjs/common';
|
||||
import { EnhancerSubtype } from '@nestjs/common/constants.js';
|
||||
import type { Type } from '@nestjs/common';
|
||||
import { InstanceWrapper } from '../../injector/instance-wrapper.js';
|
||||
import type { EnhancerSubtype } from '@nestjs/common/internal';
|
||||
|
||||
export interface EnhancerMetadataCacheEntry {
|
||||
targetNodeId?: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { RequestMethod } from '@nestjs/common';
|
||||
import { VersionValue } from '@nestjs/common/interfaces/index.js';
|
||||
import type { RequestMethod } from '@nestjs/common';
|
||||
import type { VersionValue } from '@nestjs/common/internal';
|
||||
|
||||
export type HttpEntrypointMetadata = {
|
||||
path: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EnhancerSubtype } from '@nestjs/common/constants.js';
|
||||
import type { EnhancerSubtype } from '@nestjs/common/internal';
|
||||
|
||||
/**
|
||||
* Enhancers attached through APP_PIPE, APP_GUARD, APP_INTERCEPTOR, and APP_FILTER tokens.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { InjectionToken, Scope } from '@nestjs/common';
|
||||
import { EnhancerSubtype } from '@nestjs/common/constants.js';
|
||||
import type { InjectionToken, Scope } from '@nestjs/common';
|
||||
import type { EnhancerSubtype } from '@nestjs/common/internal';
|
||||
|
||||
export type ModuleNode = {
|
||||
metadata: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InjectionToken } from '@nestjs/common';
|
||||
import type { InjectionToken } from '@nestjs/common';
|
||||
import { ApplicationConfig } from '../application-config.js';
|
||||
import { ExternalContextCreator } from '../helpers/external-context-creator.js';
|
||||
import { HttpAdapterHost } from '../helpers/http-adapter-host.js';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util.js';
|
||||
import { DeterministicUuidRegistry } from './deterministic-uuid-registry.js';
|
||||
import { randomStringGenerator } from '@nestjs/common/internal';
|
||||
|
||||
export enum UuidFactoryMode {
|
||||
Random = 'random',
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { NestInterceptor, Type } from '@nestjs/common';
|
||||
import {
|
||||
CallHandler,
|
||||
ContextType,
|
||||
Controller,
|
||||
} from '@nestjs/common/interfaces/index.js';
|
||||
import { isEmpty } from '@nestjs/common/utils/shared.utils.js';
|
||||
import type { NestInterceptor, Type } from '@nestjs/common';
|
||||
import { AsyncResource } from 'async_hooks';
|
||||
import { Observable, defer, from as fromPromise } from 'rxjs';
|
||||
import { mergeAll, switchMap } from 'rxjs/operators';
|
||||
import { ExecutionContextHost } from '../helpers/execution-context-host.js';
|
||||
import type { CallHandler, ContextType } from '@nestjs/common';
|
||||
import { type Controller, isEmpty } from '@nestjs/common/internal';
|
||||
|
||||
export class InterceptorsConsumer {
|
||||
public async intercept<TContext extends string = ContextType>(
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { INTERCEPTORS_METADATA } from '@nestjs/common/constants.js';
|
||||
import {
|
||||
Controller,
|
||||
NestInterceptor,
|
||||
Type,
|
||||
} from '@nestjs/common/interfaces/index.js';
|
||||
import { isEmpty, isFunction } from '@nestjs/common/utils/shared.utils.js';
|
||||
import { iterate } from 'iterare';
|
||||
import { ApplicationConfig } from '../application-config.js';
|
||||
import { ContextCreator } from '../helpers/context-creator.js';
|
||||
import { STATIC_CONTEXT } from '../injector/constants.js';
|
||||
import { NestContainer } from '../injector/container.js';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper.js';
|
||||
import {
|
||||
INTERCEPTORS_METADATA,
|
||||
type Controller,
|
||||
isEmpty,
|
||||
isFunction,
|
||||
} from '@nestjs/common/internal';
|
||||
import type { NestInterceptor, Type } from '@nestjs/common';
|
||||
|
||||
export class InterceptorsContextCreator extends ContextCreator {
|
||||
private moduleContext: string;
|
||||
|
||||
2
packages/core/interfaces/index.ts
Normal file
2
packages/core/interfaces/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './module-definition.interface.js';
|
||||
export * from './module-override.interface.js';
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DynamicModule, ForwardReference } from '@nestjs/common';
|
||||
import { Type } from '@nestjs/common/interfaces/index.js';
|
||||
import type { DynamicModule, ForwardReference } from '@nestjs/common';
|
||||
import type { Type } from '@nestjs/common';
|
||||
|
||||
export type ModuleDefinition =
|
||||
| ForwardReference
|
||||
|
||||
64
packages/core/internal.ts
Normal file
64
packages/core/internal.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Internal module - not part of the public API.
|
||||
* These exports are used by sibling @nestjs packages.
|
||||
* Do not depend on these in your application code.
|
||||
* @internal
|
||||
* @module
|
||||
*/
|
||||
|
||||
// Errors
|
||||
export { RuntimeException } from './errors/exceptions/runtime.exception.js';
|
||||
export { InvalidExceptionFilterException } from './errors/exceptions/invalid-exception-filter.exception.js';
|
||||
|
||||
// Constants
|
||||
export { MESSAGES } from './constants.js';
|
||||
|
||||
// Scanner
|
||||
export { DependenciesScanner } from './scanner.js';
|
||||
|
||||
// Injector
|
||||
export { Injector, InjectorDependencyContext } from './injector/injector.js';
|
||||
export { InstanceLoader } from './injector/instance-loader.js';
|
||||
export { InstanceWrapper } from './injector/instance-wrapper.js';
|
||||
export { Module } from './injector/module.js';
|
||||
export { STATIC_CONTEXT } from './injector/constants.js';
|
||||
|
||||
// Helpers
|
||||
export { ExecutionContextHost } from './helpers/execution-context-host.js';
|
||||
export { ContextUtils, ParamProperties } from './helpers/context-utils.js';
|
||||
export { HandlerMetadataStorage } from './helpers/handler-metadata-storage.js';
|
||||
export { RouterMethodFactory } from './helpers/router-method-factory.js';
|
||||
export { loadAdapter } from './helpers/load-adapter.js';
|
||||
export { optionalRequire } from './helpers/optional-require.js';
|
||||
|
||||
// Helpers - interfaces
|
||||
export { ParamsMetadata } from './helpers/interfaces/index.js';
|
||||
|
||||
// Guards
|
||||
export { GuardsConsumer } from './guards/guards-consumer.js';
|
||||
export { GuardsContextCreator } from './guards/guards-context-creator.js';
|
||||
export { FORBIDDEN_MESSAGE } from './guards/constants.js';
|
||||
|
||||
// Pipes
|
||||
export { PipesConsumer } from './pipes/pipes-consumer.js';
|
||||
export { PipesContextCreator } from './pipes/pipes-context-creator.js';
|
||||
export { ParamsTokenFactory } from './pipes/params-token-factory.js';
|
||||
|
||||
// Interceptors
|
||||
export { InterceptorsConsumer } from './interceptors/interceptors-consumer.js';
|
||||
export { InterceptorsContextCreator } from './interceptors/interceptors-context-creator.js';
|
||||
|
||||
// Exceptions
|
||||
export { BaseExceptionFilterContext } from './exceptions/base-exception-filter-context.js';
|
||||
|
||||
// Router
|
||||
export { LegacyRouteConverter } from './router/legacy-route-converter.js';
|
||||
export { REQUEST_CONTEXT_ID } from './router/request/request-constants.js';
|
||||
|
||||
// Inspector
|
||||
export { NoopGraphInspector } from './inspector/noop-graph-inspector.js';
|
||||
export { UuidFactory, UuidFactoryMode } from './inspector/uuid-factory.js';
|
||||
|
||||
// Interfaces
|
||||
export { ModuleDefinition } from './interfaces/module-definition.interface.js';
|
||||
export { ModuleOverride } from './interfaces/module-override.interface.js';
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Injectable } from '@nestjs/common/interfaces/injectable.interface.js';
|
||||
import {
|
||||
type Injectable,
|
||||
isConstructor,
|
||||
isFunction,
|
||||
isNil,
|
||||
} from '@nestjs/common/utils/shared.utils.js';
|
||||
} from '@nestjs/common/internal';
|
||||
|
||||
export class MetadataScanner {
|
||||
private readonly cachedScannedPrototypes: Map<object, string[]> = new Map();
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import {
|
||||
HttpServer,
|
||||
MiddlewareConsumer,
|
||||
Type,
|
||||
} from '@nestjs/common/interfaces/index.js';
|
||||
import {
|
||||
MiddlewareConfigProxy,
|
||||
MiddlewareConfiguration,
|
||||
RouteInfo,
|
||||
} from '@nestjs/common/interfaces/middleware/index.js';
|
||||
import { stripEndSlash } from '@nestjs/common/utils/shared.utils.js';
|
||||
import { iterate } from 'iterare';
|
||||
import { RouteInfoPathExtractor } from './route-info-path-extractor.js';
|
||||
import { RoutesMapper } from './routes-mapper.js';
|
||||
import { filterMiddleware } from './utils.js';
|
||||
import type { HttpServer, MiddlewareConsumer, Type } from '@nestjs/common';
|
||||
import {
|
||||
type MiddlewareConfigProxy,
|
||||
type MiddlewareConfiguration,
|
||||
type RouteInfo,
|
||||
stripEndSlash,
|
||||
} from '@nestjs/common/internal';
|
||||
|
||||
export class MiddlewareBuilder implements MiddlewareConsumer {
|
||||
private readonly middlewareCollection = new Set<MiddlewareConfiguration>();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { InjectionToken, Type } from '@nestjs/common';
|
||||
import { MiddlewareConfiguration } from '@nestjs/common/interfaces/middleware/middleware-configuration.interface.js';
|
||||
import type { InjectionToken, Type } from '@nestjs/common';
|
||||
import { getClassScope } from '../helpers/get-class-scope.js';
|
||||
import { isDurable } from '../helpers/is-durable.js';
|
||||
import { NestContainer } from '../injector/container.js';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper.js';
|
||||
import type { MiddlewareConfiguration } from '@nestjs/common/internal';
|
||||
|
||||
export class MiddlewareContainer {
|
||||
private readonly middleware = new Map<
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import { HttpServer, InjectionToken, Logger } from '@nestjs/common';
|
||||
import { RequestMethod } from '@nestjs/common/enums/request-method.enum.js';
|
||||
import {
|
||||
MiddlewareConfiguration,
|
||||
NestMiddleware,
|
||||
RouteInfo,
|
||||
} from '@nestjs/common/interfaces/middleware/index.js';
|
||||
import { NestApplicationContextOptions } from '@nestjs/common/interfaces/nest-application-context-options.interface.js';
|
||||
import { isUndefined } from '@nestjs/common/utils/shared.utils.js';
|
||||
import { type HttpServer, type InjectionToken, Logger } from '@nestjs/common';
|
||||
import { ApplicationConfig } from '../application-config.js';
|
||||
import { InvalidMiddlewareException } from '../errors/exceptions/invalid-middleware.exception.js';
|
||||
import { RuntimeException } from '../errors/exceptions/runtime.exception.js';
|
||||
@@ -31,6 +23,13 @@ import { MiddlewareContainer } from './container.js';
|
||||
import { MiddlewareResolver } from './resolver.js';
|
||||
import { RouteInfoPathExtractor } from './route-info-path-extractor.js';
|
||||
import { RoutesMapper } from './routes-mapper.js';
|
||||
import { RequestMethod, type NestMiddleware } from '@nestjs/common';
|
||||
import {
|
||||
type MiddlewareConfiguration,
|
||||
type RouteInfo,
|
||||
type NestApplicationContextOptions,
|
||||
isUndefined,
|
||||
} from '@nestjs/common/internal';
|
||||
|
||||
export class MiddlewareModule<
|
||||
TAppOptions extends NestApplicationContextOptions =
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InjectionToken } from '@nestjs/common';
|
||||
import type { InjectionToken } from '@nestjs/common';
|
||||
import { Injector } from '../injector/injector.js';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper.js';
|
||||
import { Module } from '../injector/module.js';
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { VersioningType } from '@nestjs/common';
|
||||
import {
|
||||
RouteInfo,
|
||||
VersioningOptions,
|
||||
VersionValue,
|
||||
} from '@nestjs/common/interfaces/index.js';
|
||||
import {
|
||||
addLeadingSlash,
|
||||
stripEndSlash,
|
||||
} from '@nestjs/common/utils/shared.utils.js';
|
||||
import { ApplicationConfig } from '../application-config.js';
|
||||
import { ExcludeRouteMetadata } from '../router/interfaces/exclude-route-metadata.interface.js';
|
||||
import { isRouteExcluded } from '../router/utils/index.js';
|
||||
import { RoutePathFactory } from './../router/route-path-factory.js';
|
||||
import type { VersioningOptions } from '@nestjs/common';
|
||||
import {
|
||||
type RouteInfo,
|
||||
type VersionValue,
|
||||
addLeadingSlash,
|
||||
stripEndSlash,
|
||||
} from '@nestjs/common/internal';
|
||||
|
||||
export class RouteInfoPathExtractor {
|
||||
private readonly routePathFactory: RoutePathFactory;
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
import {
|
||||
MODULE_PATH,
|
||||
PATH_METADATA,
|
||||
VERSION_METADATA,
|
||||
} from '@nestjs/common/constants.js';
|
||||
import {
|
||||
RouteInfo,
|
||||
Type,
|
||||
VERSION_NEUTRAL,
|
||||
VersionValue,
|
||||
} from '@nestjs/common/interfaces/index.js';
|
||||
import {
|
||||
addLeadingSlash,
|
||||
isString,
|
||||
isUndefined,
|
||||
} from '@nestjs/common/utils/shared.utils.js';
|
||||
import { ApplicationConfig } from '../application-config.js';
|
||||
import { NestContainer } from '../injector/container.js';
|
||||
import { Module } from '../injector/module.js';
|
||||
import { MetadataScanner } from '../metadata-scanner.js';
|
||||
import { PathsExplorer, RouteDefinition } from '../router/paths-explorer.js';
|
||||
import { targetModulesByContainer } from '../router/router-module.js';
|
||||
import {
|
||||
MODULE_PATH,
|
||||
PATH_METADATA,
|
||||
VERSION_METADATA,
|
||||
type RouteInfo,
|
||||
type VersionValue,
|
||||
addLeadingSlash,
|
||||
isString,
|
||||
isUndefined,
|
||||
} from '@nestjs/common/internal';
|
||||
import { type Type, VERSION_NEUTRAL } from '@nestjs/common';
|
||||
|
||||
export class RoutesMapper {
|
||||
private readonly pathsExplorer: PathsExplorer;
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import { RequestMethod } from '@nestjs/common';
|
||||
import {
|
||||
HttpServer,
|
||||
RouteInfo,
|
||||
Type,
|
||||
} from '@nestjs/common/interfaces/index.js';
|
||||
import {
|
||||
addLeadingSlash,
|
||||
isFunction,
|
||||
isString,
|
||||
} from '@nestjs/common/utils/shared.utils.js';
|
||||
import { iterate } from 'iterare';
|
||||
import { pathToRegexp } from 'path-to-regexp';
|
||||
import { uid } from 'uid';
|
||||
import { ExcludeRouteMetadata } from '../router/interfaces/exclude-route-metadata.interface.js';
|
||||
import { LegacyRouteConverter } from '../router/legacy-route-converter.js';
|
||||
import { isRouteExcluded } from '../router/utils/index.js';
|
||||
import type { HttpServer, Type } from '@nestjs/common';
|
||||
import {
|
||||
type RouteInfo,
|
||||
addLeadingSlash,
|
||||
isFunction,
|
||||
isString,
|
||||
} from '@nestjs/common/internal';
|
||||
|
||||
export const mapToExcludeRoute = (
|
||||
routes: (string | RouteInfo)[],
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user