nestjs-boost 0.0.0-stage → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -0
- package/LICENSE +21 -0
- package/NOTICE.md +6 -0
- package/README.md +168 -2
- package/bin/nestjs-boost.mjs +148 -0
- package/guidelines/core.md +64 -0
- package/lib/agents.mjs +137 -0
- package/lib/installer.mjs +450 -0
- package/lib/prompt.mjs +131 -0
- package/lib/setup.mjs +60 -0
- package/manifest.json +676 -0
- package/package.json +67 -6
- package/scripts/postinstall.mjs +49 -0
- package/skills/nestjs-ai/SKILL.md +145 -0
- package/skills/nestjs-ai/references/ai-reference.md +143 -0
- package/skills/nestjs-architecture/SKILL.md +83 -0
- package/skills/nestjs-architecture/references/dynamic-modules.md +92 -0
- package/skills/nestjs-architecture/references/scopes-lifecycle.md +98 -0
- package/skills/nestjs-architecture/references/troubleshooting.md +77 -0
- package/skills/nestjs-authentication/SKILL.md +115 -0
- package/skills/nestjs-authentication/references/flows.md +180 -0
- package/skills/nestjs-authorization/SKILL.md +104 -0
- package/skills/nestjs-caching/SKILL.md +109 -0
- package/skills/nestjs-cli-tooling/SKILL.md +94 -0
- package/skills/nestjs-cli-tooling/references/nest-cli-json.md +91 -0
- package/skills/nestjs-configuration/SKILL.md +109 -0
- package/skills/nestjs-cqrs/SKILL.md +124 -0
- package/skills/nestjs-database-drizzle/SKILL.md +117 -0
- package/skills/nestjs-database-mikro-orm/SKILL.md +109 -0
- package/skills/nestjs-database-mongoose/SKILL.md +124 -0
- package/skills/nestjs-database-prisma/SKILL.md +126 -0
- package/skills/nestjs-database-sequelize/SKILL.md +88 -0
- package/skills/nestjs-database-typeorm/SKILL.md +110 -0
- package/skills/nestjs-deployment/SKILL.md +133 -0
- package/skills/nestjs-deployment/references/serverless-and-systemd.md +133 -0
- package/skills/nestjs-events/SKILL.md +78 -0
- package/skills/nestjs-file-storage/SKILL.md +111 -0
- package/skills/nestjs-file-storage/references/storage-reference.md +82 -0
- package/skills/nestjs-graphql/SKILL.md +119 -0
- package/skills/nestjs-graphql/references/federation.md +110 -0
- package/skills/nestjs-graphql/references/schema-features.md +133 -0
- package/skills/nestjs-health-checks/SKILL.md +114 -0
- package/skills/nestjs-http-client/SKILL.md +118 -0
- package/skills/nestjs-http-client/references/http-client-reference.md +116 -0
- package/skills/nestjs-http-platform/SKILL.md +104 -0
- package/skills/nestjs-http-platform/references/adapters.md +88 -0
- package/skills/nestjs-http-web-features/SKILL.md +113 -0
- package/skills/nestjs-http-web-features/references/uploads-streaming.md +108 -0
- package/skills/nestjs-i18n/SKILL.md +113 -0
- package/skills/nestjs-idempotency/SKILL.md +112 -0
- package/skills/nestjs-locks/SKILL.md +108 -0
- package/skills/nestjs-logging/SKILL.md +123 -0
- package/skills/nestjs-mail/SKILL.md +125 -0
- package/skills/nestjs-mail/references/mail-reference.md +90 -0
- package/skills/nestjs-mcp-server/SKILL.md +152 -0
- package/skills/nestjs-mcp-server/references/mcp-reference.md +146 -0
- package/skills/nestjs-microservices/SKILL.md +108 -0
- package/skills/nestjs-microservices/references/transports.md +117 -0
- package/skills/nestjs-observability/SKILL.md +108 -0
- package/skills/nestjs-observability/references/apm-and-otel.md +106 -0
- package/skills/nestjs-openapi/SKILL.md +90 -0
- package/skills/nestjs-openapi/references/swagger-reference.md +180 -0
- package/skills/nestjs-outbox/SKILL.md +119 -0
- package/skills/nestjs-outbox/references/outbox-reference.md +74 -0
- package/skills/nestjs-passport-auth/SKILL.md +125 -0
- package/skills/nestjs-queues/SKILL.md +118 -0
- package/skills/nestjs-queues/references/bull-legacy.md +75 -0
- package/skills/nestjs-request-pipeline/SKILL.md +146 -0
- package/skills/nestjs-request-pipeline/references/filters-interceptors.md +114 -0
- package/skills/nestjs-request-pipeline/references/pipes-decorators.md +80 -0
- package/skills/nestjs-resilience/SKILL.md +116 -0
- package/skills/nestjs-resilience/references/resilience-reference.md +74 -0
- package/skills/nestjs-scheduling/SKILL.md +100 -0
- package/skills/nestjs-security-hardening/SKILL.md +99 -0
- package/skills/nestjs-security-hardening/references/throttler-advanced.md +78 -0
- package/skills/nestjs-standalone-apps/SKILL.md +78 -0
- package/skills/nestjs-testing/SKILL.md +118 -0
- package/skills/nestjs-testing/references/advanced-testing.md +62 -0
- package/skills/nestjs-upgrading/SKILL.md +82 -0
- package/skills/nestjs-upgrading/references/esm-switch.md +114 -0
- package/skills/nestjs-validation-serialization/SKILL.md +124 -0
- package/skills/nestjs-validation-serialization/references/validation-options.md +84 -0
- package/skills/nestjs-webhooks/SKILL.md +116 -0
- package/skills/nestjs-webhooks/references/webhooks-reference.md +132 -0
- package/skills/nestjs-websockets/SKILL.md +108 -0
- package/skills/nestjs-workflows/SKILL.md +123 -0
- package/skills/nestjs-workflows/references/workflows-reference.md +85 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# GraphQL federation
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
- Packages and drivers
|
|
5
|
+
- Users subgraph (owns the entity)
|
|
6
|
+
- Posts subgraph (references and extends it)
|
|
7
|
+
- Federation 2
|
|
8
|
+
- Gateway
|
|
9
|
+
- Mercurius federation
|
|
10
|
+
- Subscriptions and hiding operations
|
|
11
|
+
|
|
12
|
+
## Packages and drivers
|
|
13
|
+
|
|
14
|
+
| Role | Apollo | Mercurius |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| Subgraph | `npm i @apollo/subgraph`; `ApolloFederationDriver` / `ApolloFederationDriverConfig` from `@nestjs/apollo` | `npm i @apollo/subgraph @nestjs/mercurius`; `MercuriusFederationDriver` |
|
|
17
|
+
| Gateway | `npm i @apollo/gateway`; `ApolloGatewayDriver` / `ApolloGatewayDriverConfig` | `npm i @mercuriusjs/gateway`; `MercuriusGatewayDriver` |
|
|
18
|
+
|
|
19
|
+
## Users subgraph (owns the entity)
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
@ObjectType()
|
|
23
|
+
@Directive('@key(fields: "id")')
|
|
24
|
+
export class User {
|
|
25
|
+
@Field(() => ID) id: number;
|
|
26
|
+
@Field() name: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@Resolver(() => User)
|
|
30
|
+
export class UsersResolver {
|
|
31
|
+
constructor(private readonly users: UsersService) {}
|
|
32
|
+
@Query(() => User) getUser(@Args('id') id: number) { return this.users.findById(id); }
|
|
33
|
+
@ResolveReference()
|
|
34
|
+
resolveReference(reference: { __typename: string; id: number }) { return this.users.findById(reference.id); } // called by the gateway
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
GraphQLModule.forRoot<ApolloFederationDriverConfig>({ driver: ApolloFederationDriver, autoSchemaFile: true }),
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Schema first: `type User @key(fields: "id") { … }` and `extend type Query { … }` with `typePaths`.
|
|
41
|
+
|
|
42
|
+
## Posts subgraph (references and extends it)
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
@ObjectType()
|
|
46
|
+
@Directive('@extends') // Federation 1 only
|
|
47
|
+
@Directive('@key(fields: "id")')
|
|
48
|
+
export class User {
|
|
49
|
+
@Field(() => ID) @Directive('@external') id: number; // Federation 1 only
|
|
50
|
+
@Field(() => [Post]) posts?: Post[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Resolver(() => User)
|
|
54
|
+
export class UsersResolver {
|
|
55
|
+
@ResolveField(() => [Post]) posts(@Parent() user: User) { return this.posts.forAuthor(user.id); }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@Resolver(() => Post)
|
|
59
|
+
export class PostsResolver {
|
|
60
|
+
@ResolveField(() => User) user(@Parent() post: Post) { return { __typename: 'User', id: post.authorId }; } // reference, resolved by the Users subgraph
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
GraphQLModule.forRoot<ApolloFederationDriverConfig>({
|
|
64
|
+
driver: ApolloFederationDriver,
|
|
65
|
+
autoSchemaFile: true,
|
|
66
|
+
buildSchemaOptions: { orphanedTypes: [User] }, // entity only reachable via _entities
|
|
67
|
+
}),
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Federation 2
|
|
71
|
+
|
|
72
|
+
- Code first: `autoSchemaFile: { federation: 2 }` — Nest adds the `@link` directive.
|
|
73
|
+
- Schema first: `extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])`.
|
|
74
|
+
- Entities have no originating subgraph: drop `extend`, `@extends`, `@external`, and no need to extend `Query`.
|
|
75
|
+
- Mercurius doesn't fully support Federation 2.
|
|
76
|
+
|
|
77
|
+
## Gateway
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
GraphQLModule.forRoot<ApolloGatewayDriverConfig>({
|
|
81
|
+
driver: ApolloGatewayDriver,
|
|
82
|
+
server: { /* Apollo Server options, e.g. context */ },
|
|
83
|
+
gateway: {
|
|
84
|
+
supergraphSdl: new IntrospectAndCompose({
|
|
85
|
+
subgraphs: [
|
|
86
|
+
{ name: 'users', url: 'http://user-service/graphql' },
|
|
87
|
+
{ name: 'posts', url: 'http://post-service/graphql' },
|
|
88
|
+
],
|
|
89
|
+
}),
|
|
90
|
+
},
|
|
91
|
+
}),
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Other gateway options (header forwarding, polling) come from `@apollo/gateway`'s own API; pass them in `gateway`.
|
|
95
|
+
|
|
96
|
+
## Mercurius federation
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
GraphQLModule.forRoot<MercuriusFederationDriverConfig>({ driver: MercuriusFederationDriver, autoSchemaFile: true }); // subgraph (or typePaths)
|
|
100
|
+
GraphQLModule.forRoot<MercuriusGatewayDriverConfig>({
|
|
101
|
+
driver: MercuriusGatewayDriver,
|
|
102
|
+
gateway: { services: [{ name: 'users', url: 'http://user-service/graphql' }, { name: 'posts', url: 'http://post-service/graphql' }] },
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Subscriptions and hiding operations
|
|
107
|
+
|
|
108
|
+
- Subgraphs support subscriptions (`subscriptions` for Apollo, `subscription: true` for Mercurius), but `ApolloGatewayDriver` doesn't forward them: use Apollo Router with federated subscriptions, or the Mercurius gateway (`subscription: true` and `wsUrl` per service).
|
|
109
|
+
- Keep a field/operation out of the public supergraph with Federation 2 `@inaccessible`: `@Directive('@inaccessible')`. It stays executable on the subgraph.
|
|
110
|
+
- Generate SDL offline for federation with `GraphQLSchemaFactory`, passing entity stubs in `orphanedTypes`.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# GraphQL schema features
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
- Schema first
|
|
5
|
+
- Scalars
|
|
6
|
+
- Directives
|
|
7
|
+
- Interfaces
|
|
8
|
+
- Unions and enums
|
|
9
|
+
- Field middleware and extensions
|
|
10
|
+
- Mapped types and inheritance
|
|
11
|
+
- Complexity plugin and other Apollo plugins
|
|
12
|
+
- CLI plugin
|
|
13
|
+
- Generating SDL without running the app
|
|
14
|
+
- Sharing models with the browser
|
|
15
|
+
- Multiple endpoints
|
|
16
|
+
- Mercurius
|
|
17
|
+
|
|
18
|
+
## Schema first
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
22
|
+
driver: ApolloDriver,
|
|
23
|
+
typePaths: ['./**/*.graphql'],
|
|
24
|
+
definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class' }, // generated at startup
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
On-demand typings: a `generate-typings.ts` script with `new GraphQLDefinitionsFactory().generate({ typePaths, path, outputAs: 'class', watch: true })`. Resolver classes use `@Resolver('Author')`, `@Query()`, `@ResolveField()` matching SDL names; interfaces/unions need a `__resolveType` resolver. Add a `ValidationPipe` for argument validation.
|
|
29
|
+
|
|
30
|
+
## Scalars
|
|
31
|
+
|
|
32
|
+
- Built-ins: `ID`, `String`, `Boolean`, `Int`, `Float`, `GraphQLISODateTime` (default for `Date`), `GraphQLTimestamp`.
|
|
33
|
+
- `buildSchemaOptions: { dateScalarMode: 'timestamp', numberScalarMode: 'integer' }`.
|
|
34
|
+
- Code-first custom scalar class: `@Scalar('Date', () => Date) class DateScalar implements CustomScalar<number, Date> { parseValue, serialize, parseLiteral }` registered as a provider.
|
|
35
|
+
- Imported scalars (e.g. `graphql-type-json`): `resolvers: { JSON: GraphQLJSON }` and `@Field(() => GraphQLJSON)`.
|
|
36
|
+
- `new GraphQLScalarType({ name: 'UUID', serialize, parseValue, parseLiteral })` + `resolvers: { UUID: CustomUuidScalar }`.
|
|
37
|
+
|
|
38
|
+
## Directives
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
@Directive('@upper') @Field() title: string; // also @Directive('@deprecated(reason: "…")')
|
|
42
|
+
|
|
43
|
+
GraphQLModule.forRoot({
|
|
44
|
+
transformSchema: (schema) => upperDirectiveTransformer(schema, 'upper'), // mapSchema/getDirective from @graphql-tools/utils
|
|
45
|
+
buildSchemaOptions: { directives: [new GraphQLDirective({ name: 'upper', locations: [DirectiveLocation.FIELD_DEFINITION] })] },
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Directives added with `@Directive()` aren't reflected in the generated schema file.
|
|
50
|
+
|
|
51
|
+
## Interfaces
|
|
52
|
+
|
|
53
|
+
- `@InterfaceType() abstract class Character { @Field(() => ID) id: string }` (TypeScript interfaces can't be used).
|
|
54
|
+
- `@ObjectType({ implements: () => [Character] }) class Human implements Character`.
|
|
55
|
+
- Default `resolveType` needs class instances; otherwise `@InterfaceType({ resolveType(value) { return value.pages ? Book : Author; } })`.
|
|
56
|
+
- Shared field resolvers: `@Resolver(() => Character)` class with `@ResolveField` + `inheritResolversFromInterfaces: true`.
|
|
57
|
+
|
|
58
|
+
## Unions and enums
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
export const ResultUnion = createUnionType({ name: 'ResultUnion', types: () => [Author, Book] as const, resolveType(v) { … } }); // `as const` required
|
|
62
|
+
@Query(() => [ResultUnion]) search(): Array<typeof ResultUnion> { … }
|
|
63
|
+
|
|
64
|
+
export enum AllowedColor { RED, GREEN }
|
|
65
|
+
registerEnumType(AllowedColor, { name: 'AllowedColor', description: '…', valuesMap: { RED: { deprecationReason: '…' } } });
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Field middleware and extensions
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const loggerMiddleware: FieldMiddleware = async (ctx: MiddlewareContext, next: NextFn) => { const value = await next(); return value; };
|
|
72
|
+
@Field({ middleware: [loggerMiddleware] }) title: string;
|
|
73
|
+
GraphQLModule.forRoot({ buildSchemaOptions: { fieldMiddleware: [loggerMiddleware] } }); // global, runs before local
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- Code first only; no DI; keep them cheap; only on `ObjectType` classes. With `fieldResolverEnhancers`, interceptors/guards also wrap them.
|
|
77
|
+
- Metadata: `@Extensions({ role: Role.ADMIN })` on fields/classes/methods; read in middleware via `ctx.info.parentType.getFields()[ctx.info.fieldName].extensions` (field-level permissions).
|
|
78
|
+
|
|
79
|
+
## Mapped types and inheritance
|
|
80
|
+
|
|
81
|
+
`PartialType`, `PickType`, `OmitType`, `IntersectionType` from `@nestjs/graphql`, composable; pass the target decorator as the 2nd arg (`PartialType(CreateUserInput, InputType)`) when deriving between kinds. Abstract base classes with `@ObjectType({ isAbstract: true })` / generic resolvers via a factory function returning a class. Custom mapped helpers: treat returned field metadata as read-only.
|
|
82
|
+
|
|
83
|
+
## Complexity plugin and other Apollo plugins
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
@Plugin()
|
|
87
|
+
export class ComplexityPlugin implements ApolloServerPlugin {
|
|
88
|
+
constructor(private gqlSchemaHost: GraphQLSchemaHost) {}
|
|
89
|
+
async requestDidStart(): Promise<GraphQLRequestListener<BaseContext>> {
|
|
90
|
+
const { schema } = this.gqlSchemaHost;
|
|
91
|
+
return {
|
|
92
|
+
async didResolveOperation({ request, document }) {
|
|
93
|
+
const complexity = getComplexity({ schema, operationName: request.operationName, query: document, variables: request.variables,
|
|
94
|
+
estimators: [fieldExtensionsEstimator(), simpleEstimator({ defaultComplexity: 1 })] });
|
|
95
|
+
if (complexity > 20) throw new GraphQLError(`Query is too complex: ${complexity}`);
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`npm i graphql-query-complexity`; register as a provider; `@Field({ complexity: 3 })` or an estimator function; `@Query(() => T, { complexity: … })`. External plugins: `plugins: [ApolloServerPluginCacheControl({ defaultMaxAge: 5 })]`. Mercurius: register Fastify plugins via `plugins: [{ plugin, options }]` (mercurius-upload in `main.ts`).
|
|
103
|
+
|
|
104
|
+
## CLI plugin
|
|
105
|
+
|
|
106
|
+
`nest-cli.json`: `"plugins": [{ "name": "@nestjs/graphql", "options": { "typeFileNameSuffix": [".input.ts", ".args.ts", ".entity.ts", ".model.ts"], "introspectComments": true } }]`. Infers `@Field()` from types/optionality; override with explicit `@Field()`. SWC needs `--type-check`; ts-jest needs the `@nestjs/graphql/plugin` AST transformer (same pattern as the Swagger plugin).
|
|
107
|
+
|
|
108
|
+
## Generating SDL without running the app
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const app = await NestFactory.create(GraphQLSchemaBuilderModule);
|
|
112
|
+
await app.init();
|
|
113
|
+
const schema = await app.get(GraphQLSchemaFactory).create([RecipesResolver], [DateScalar], { skipCheck: false, orphanedTypes: [] });
|
|
114
|
+
console.log(printSchema(schema));
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`GraphQLSchemaHost#schema` gives the built schema after `onModuleInit`. Federation entities only referenced through `_entities` go into `orphanedTypes`.
|
|
118
|
+
|
|
119
|
+
## Sharing models with the browser
|
|
120
|
+
|
|
121
|
+
`@nestjs/graphql` exposes a decorator shim via the `browser` export condition; bundlers without it can alias `@nestjs/graphql` to `node_modules/@nestjs/graphql/dist/extra/graphql-model-shim`.
|
|
122
|
+
|
|
123
|
+
## Multiple endpoints
|
|
124
|
+
|
|
125
|
+
`GraphQLModule.forRoot({ path: '/cats', include: [CatsModule] })` per endpoint. In code first, `include` limits resolvers; types are global unless `@ObjectType({ registerIn: () => CatsModule })` (also on `@InputType`, `@InterfaceType`, `@ArgsType`, `registerEnumType`, `createUnionType`). Prefer the factory form to avoid circular references.
|
|
126
|
+
|
|
127
|
+
## Mercurius
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
GraphQLModule.forRoot<MercuriusDriverConfig>({ driver: MercuriusDriver, graphiql: true, subscription: true }); // Fastify only
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
GraphiQL at `/graphiql`. Subscriptions: `PubSub` from `mercurius` (`mqemitter`; use `mqemitter-redis` in production), auth via `subscription.onConnect`. Async config with `forRootAsync`. Access the schema with `GraphQLSchemaHost`.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nestjs-health-checks
|
|
3
|
+
description: Adds liveness and readiness health checks to NestJS 12 with @nestjs/terminus — TerminusModule.forRoot options, HealthCheckService with @HealthCheck() controllers, built-in indicators (HTTP ping/response checks, TypeORM, Mongoose, Sequelize, MikroORM, Prisma, microservice, gRPC, memory heap/RSS, disk), custom indicators with HealthIndicatorService attempt()/up()/down()/degraded(), withTimeout() and cacheFor() on attempts, the shutting_down status and gracefulShutdownTimeoutMs for zero-downtime Kubernetes rollouts, and custom Terminus logging. Use when adding /health, /health/live or /health/ready endpoints, Kubernetes probes, dependency checks for databases or upstream APIs, or when pods are killed or kept in rotation wrongly. Not for metrics, tracing or alerting (nestjs-observability) or retries/circuit breakers (nestjs-resilience).
|
|
4
|
+
compatibility: NestJS 12.x; @nestjs/terminus; @nestjs/axios for HttpHealthIndicator
|
|
5
|
+
metadata:
|
|
6
|
+
version: "0.1.0"
|
|
7
|
+
nestjs-major: "12"
|
|
8
|
+
docs-crawled: "2026-11-05"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# NestJS health checks (@nestjs/terminus)
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm i @nestjs/terminus
|
|
15
|
+
npm i @nestjs/axios axios # only for HttpHealthIndicator
|
|
16
|
+
nest g module health && nest g controller health
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Liveness vs readiness
|
|
20
|
+
|
|
21
|
+
| Probe | Checks | Failing means |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| Liveness `/health/live` | The process works (event loop answers; optionally heap/RSS limits) | Restart the container — so **no dependency checks** here |
|
|
24
|
+
| Readiness `/health/ready` | Dependencies it needs to serve (DB, required upstreams) | Stop routing traffic; also reports `shutting_down` during shutdown |
|
|
25
|
+
|
|
26
|
+
## Setup
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
@Module({
|
|
30
|
+
imports: [TerminusModule.forRoot({ gracefulShutdownTimeoutMs: 5000, errorLogStyle: 'json' }), HttpModule],
|
|
31
|
+
controllers: [HealthController],
|
|
32
|
+
providers: [CacheHealthIndicator],
|
|
33
|
+
})
|
|
34
|
+
export class HealthModule {}
|
|
35
|
+
|
|
36
|
+
@Controller('health')
|
|
37
|
+
export class HealthController {
|
|
38
|
+
constructor(
|
|
39
|
+
private readonly health: HealthCheckService,
|
|
40
|
+
private readonly db: TypeOrmHealthIndicator,
|
|
41
|
+
private readonly memory: MemoryHealthIndicator,
|
|
42
|
+
private readonly cacheIndicator: CacheHealthIndicator,
|
|
43
|
+
) {}
|
|
44
|
+
|
|
45
|
+
@Get('live') @HealthCheck()
|
|
46
|
+
live() { return this.health.check([() => this.memory.checkHeap('memory_heap', 300 * 1024 * 1024)]); }
|
|
47
|
+
|
|
48
|
+
@Get('ready') @HealthCheck()
|
|
49
|
+
ready() {
|
|
50
|
+
return this.health.check([
|
|
51
|
+
() => this.db.pingCheck('database').withTimeout(1500).cacheFor(5000),
|
|
52
|
+
() => this.cacheIndicator.isHealthy('cache'),
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- Enable `app.enableShutdownHooks()` — required for the `shutting_down` status and the graceful delay.
|
|
59
|
+
- Make health routes public and unthrottled: `@Public()` (nestjs-authentication / request-pipeline), `@SkipThrottle()`, exclude from the global prefix/versioning if your probes expect `/health` (`setGlobalPrefix('api', { exclude: ['health/(.*)'] })` or `VERSION_NEUTRAL`).
|
|
60
|
+
- Response: `{ status: 'ok' | 'error' | 'degraded' | 'shutting_down', info, error, details }` (`HealthCheckResult`); failures return **503**.
|
|
61
|
+
|
|
62
|
+
## Built-in indicators
|
|
63
|
+
|
|
64
|
+
- HTTP: `http.pingCheck('docs', url)`, `http.responseCheck('svc', url, (res) => res.status === 204)`.
|
|
65
|
+
- Databases: `TypeOrmHealthIndicator`, `MongooseHealthIndicator`, `SequelizeHealthIndicator`, `MikroOrmHealthIndicator`, `PrismaHealthIndicator` (`pingCheck(key, …)`). Multiple TypeORM data sources: inject each and pass `{ connection }`.
|
|
66
|
+
- `MicroserviceHealthIndicator`, `GRPCHealthIndicator`.
|
|
67
|
+
- `memory.checkHeap(key, bytes)`, `memory.checkRSS(key, bytes)`, `disk.checkStorage(key, { path: '/', thresholdPercent: 0.9 })` or `{ threshold: bytes }`.
|
|
68
|
+
- Use `.withTimeout(ms)` (the `timeout` option is deprecated) and `.cacheFor(ms)` for expensive checks; cached results carry `cachedResponse: true`.
|
|
69
|
+
|
|
70
|
+
## Custom indicators
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
@Injectable()
|
|
74
|
+
export class PaymentsApiHealthIndicator {
|
|
75
|
+
constructor(private readonly indicators: HealthIndicatorService, private readonly http: HttpClient) {}
|
|
76
|
+
isHealthy(key: string) {
|
|
77
|
+
return this.indicators.check(key).attempt(async ({ signal }) => { // up on resolve, down on throw
|
|
78
|
+
const { data } = await this.http.get<{ version: string }>('/status', { signal, retry: false });
|
|
79
|
+
return { version: data.version }; // appended to the result
|
|
80
|
+
}).withTimeout(1000);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@Injectable()
|
|
85
|
+
export class CacheHealthIndicator {
|
|
86
|
+
constructor(private readonly indicators: HealthIndicatorService, private readonly cache: CacheService) {}
|
|
87
|
+
async isHealthy(key: string) {
|
|
88
|
+
const indicator = this.indicators.check(key);
|
|
89
|
+
return (await this.cache.isConnected()) ? indicator.up() : indicator.degraded('serving without cache'); // degraded ≠ fail
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- Pass the `signal` to the operation so timeouts cancel it.
|
|
95
|
+
- With `up()/down()/degraded()`, return the state — a thrown error is treated as a bug (500 instead of 503); wrap risky code in try/catch and return `down()`.
|
|
96
|
+
- Any `down` → overall `error`; `degraded` only if none are down.
|
|
97
|
+
|
|
98
|
+
## Zero-downtime shutdown
|
|
99
|
+
|
|
100
|
+
`gracefulShutdownTimeoutMs` (applied on SIGTERM only): readiness answers 503 `shutting_down` while the app keeps serving for the delay — set it slightly longer than the readiness probe period, and keep the pod's termination grace period longer than delay + drain time.
|
|
101
|
+
|
|
102
|
+
## Logging
|
|
103
|
+
|
|
104
|
+
`TerminusModule.forRoot({ logger: TerminusLogger })` (a transient `ConsoleLogger` subclass), `logger: false` to silence, `errorLogStyle: 'pretty' | 'json'`.
|
|
105
|
+
|
|
106
|
+
## Workflow
|
|
107
|
+
|
|
108
|
+
- [ ] 1. Install; `HealthModule` with `TerminusModule.forRoot()`; shutdown hooks on.
|
|
109
|
+
- [ ] 2. Separate live and ready routes; public, unthrottled, outside prefix/versioning as probes require.
|
|
110
|
+
- [ ] 3. Add indicators for required dependencies with timeouts (and caching); degraded for optional ones.
|
|
111
|
+
- [ ] 4. E2E test: 200 when healthy, 503 when an indicator is overridden to fail.
|
|
112
|
+
- [ ] 5. Build, lint, run tests, start the app and curl both endpoints; wire the probes in the deployment manifest.
|
|
113
|
+
|
|
114
|
+
Sources: [Health checks (Terminus)](https://docs.nestjs.com/reliability/terminus)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nestjs-http-client
|
|
3
|
+
description: Calls external HTTP APIs from NestJS 12 with the first-party fetch-based @nestjs/http-client — HttpClientModule.register/registerAsync named clients with baseUrl, headers and timeouts, forRoot app-wide defaults, typed get/post/put/patch/delete with params, query, json and responseType, per-attempt timeouts and AbortSignal deadlines, retries on by default for idempotent methods with backoff and Retry-After, idempotency keys for POST, HttpResponseError/HttpTimeoutError/HttpNetworkError handling and toHttpException 502/504 mapping, function and class interceptors, streaming responses, undici dispatchers, testing with a stub fetch, and migrating from @nestjs/axios. Use when a service must call another service or third-party API, when adding auth headers, retries or timeouts to outbound calls, or when replacing HttpModule/HttpService (Axios). Not for receiving webhooks (nestjs-webhooks), circuit breakers and bulkheads around calls (nestjs-resilience) or microservice transports (nestjs-microservices).
|
|
4
|
+
compatibility: NestJS 12.x; @nestjs/http-client (no runtime deps); optional undici for custom dispatchers
|
|
5
|
+
metadata:
|
|
6
|
+
version: "0.1.0"
|
|
7
|
+
nestjs-major: "12"
|
|
8
|
+
docs-crawled: "2026-11-05"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# NestJS HTTP client (@nestjs/http-client)
|
|
12
|
+
|
|
13
|
+
Prefer `@nestjs/http-client` for new outbound calls; it replaces `@nestjs/axios`'s `HttpService`. If the project uses `@nestjs/axios`, keep it unless asked to migrate (map in `references/http-client-reference.md`). Every method returns a Promise — no `firstValueFrom`.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i @nestjs/http-client
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Register clients
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// app.module.ts — shared defaults (always global; no baseUrl here)
|
|
23
|
+
HttpClientModule.forRoot({ headers: { 'user-agent': 'orders-service/1.0' }, timeout: '10s' }),
|
|
24
|
+
|
|
25
|
+
// github/github.module.ts — one named client per upstream
|
|
26
|
+
HttpClientModule.registerAsync({
|
|
27
|
+
name: 'github',
|
|
28
|
+
imports: [ConfigModule],
|
|
29
|
+
inject: [ConfigService],
|
|
30
|
+
useFactory: (config: ConfigService) => ({
|
|
31
|
+
baseUrl: config.getOrThrow<string>('GITHUB_API_URL'),
|
|
32
|
+
headers: { authorization: `Bearer ${config.getOrThrow<string>('GITHUB_TOKEN')}` },
|
|
33
|
+
timeout: '5s',
|
|
34
|
+
}),
|
|
35
|
+
interceptors: [GithubAuthInterceptor], // classes go beside useFactory
|
|
36
|
+
}),
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- `register()` scopes the client to the importing module (`isGlobal: true` to share). Unnamed = default client injected as `HttpClient`; named clients with `@InjectHttpClient('github')`; token `getHttpClientToken(name)`.
|
|
40
|
+
- Client settings override `forRoot()` defaults, except headers (merged; `null` removes), retry (merged per field) and interceptors (forRoot's run first).
|
|
41
|
+
|
|
42
|
+
## Make requests
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
@Injectable()
|
|
46
|
+
export class ReposService {
|
|
47
|
+
constructor(@InjectHttpClient('github') private readonly github: HttpClient) {}
|
|
48
|
+
|
|
49
|
+
async findAll(org: string) {
|
|
50
|
+
const { data } = await this.github.get<Repo[]>('/orgs/:org/repos', {
|
|
51
|
+
params: { org }, // fills :org, URI-encoded; '..' or missing throws
|
|
52
|
+
query: { type: 'public', topic: ['nest', 'ts'] }, // arrays repeat the key
|
|
53
|
+
});
|
|
54
|
+
return data; // type arg is NOT validated at runtime
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async createIssue(owner: string, repo: string, issue: CreateIssueDto) {
|
|
58
|
+
const { data } = await this.github.post<Issue>('/repos/:owner/:repo/issues', { params: { owner, repo }, json: issue });
|
|
59
|
+
return data;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- Relative URLs append to `baseUrl` with one `/` (the `/v1` prefix is kept). Never interpolate user input into paths — use `params`/`query`.
|
|
65
|
+
- `json` (sets content-type) or `body` (string, Buffer, FormData, stream…), not both. `responseType`: `'auto'` (default), `'json'`, `'text'`, `'arrayBuffer'`, `'stream'`, `'response'`.
|
|
66
|
+
- Response: `{ status, statusText, ok, headers (web Headers: .get()), data, url, request }`.
|
|
67
|
+
- Validate responses from APIs you don't control (e.g. a Standard Schema `parse`).
|
|
68
|
+
|
|
69
|
+
## Timeouts, retries, idempotency
|
|
70
|
+
|
|
71
|
+
- `timeout` applies **per attempt**; a call can take `attempts × timeout` + waits. Cap the whole call with `signal: AbortSignal.timeout(10_000)`.
|
|
72
|
+
- Retries are **on by default**: 3 attempts for GET/HEAD/OPTIONS/PUT/DELETE on network errors, timeouts and 408/429/500/502/503/504, exponential backoff with full jitter (honours `Retry-After`). Configure with `retry: { attempts, backoff: { delay, factor, maxDelay, jitter }, retryIf, methods, statusCodes }` or `retry: false`; `methods`/`statusCodes` **replace** the defaults.
|
|
73
|
+
- POST/PATCH aren't retried. With an upstream that supports idempotency keys: `headers: { 'idempotency-key': \`charge-${order.id}\` }, retry: { methods: ['POST'] }` on that request only.
|
|
74
|
+
- Retry at one layer of a service chain only (retries multiply).
|
|
75
|
+
|
|
76
|
+
## Errors
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
try {
|
|
80
|
+
return (await this.github.get<Repo>('/repos/:owner/:repo', { params: { owner, repo } })).data;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error instanceof HttpResponseError && error.status === 404) throw new NotFoundException();
|
|
83
|
+
throw toHttpException(error); // upstream non-2xx/network/parse → 502, timeouts → 504 (logged)
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- Errors extend `HttpClientError`: `HttpResponseError` (`status`, `body`), `HttpTimeoutError`, `HttpNetworkError` (`cause.code`), `HttpParseError`. `throwOnHttpError: false` returns non-2xx as values.
|
|
88
|
+
- Or map centrally: `@Catch(HttpClientError, DOMException) class F extends BaseExceptionFilter { catch(e, h) { super.catch(toHttpException(e), h); } }`.
|
|
89
|
+
- `toHttpException(error, { forward: [422] })` passes selected upstream statuses/bodies through; `log: false` if you log elsewhere. In RPC/WS handlers convert to `RpcException`/`WsException` yourself.
|
|
90
|
+
|
|
91
|
+
## Security defaults
|
|
92
|
+
|
|
93
|
+
A client with `baseUrl` only talks to its origin (other hosts and cross-origin redirects throw), credentials in URLs are refused, and error messages mask query values. Keep `redirect` default unless the API intentionally redirects off-origin (`'follow'`).
|
|
94
|
+
|
|
95
|
+
## Testing
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const fetch = vi.fn<typeof globalThis.fetch>();
|
|
99
|
+
const moduleRef = await Test.createTestingModule({ imports: [HttpClientModule.forRoot({ fetch }), ReposModule] }).compile();
|
|
100
|
+
await moduleRef.init();
|
|
101
|
+
fetch.mockResolvedValueOnce(Response.json([{ name: 'nest' }])); // one Response per call (bodies read once)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
E2E: `.overrideProvider(HTTP_CLIENT_MODULE_OPTIONS).useValue({ fetch })`, or replace a client via `getHttpClientToken('github')`. Test retries with `retry: false` or several queued responses.
|
|
105
|
+
|
|
106
|
+
## Workflow
|
|
107
|
+
|
|
108
|
+
- [ ] 1. Install; `forRoot()` defaults; one named client per upstream with `registerAsync` + `ConfigService`.
|
|
109
|
+
- [ ] 2. Service methods with `params`/`query`/`json`; validate responses.
|
|
110
|
+
- [ ] 3. Set timeouts, a deadline for user-facing calls, retry policy and idempotency keys for unsafe methods.
|
|
111
|
+
- [ ] 4. Map errors with `toHttpException` or a filter; add a logging interceptor if no tracing.
|
|
112
|
+
- [ ] 5. Stub-fetch tests. Build, lint, run tests, start the app.
|
|
113
|
+
|
|
114
|
+
## Reference files
|
|
115
|
+
|
|
116
|
+
- `references/http-client-reference.md`: interceptors (function and class, per-attempt), streaming to `StreamableFile`, undici `Agent`/`ProxyAgent` dispatchers, redirect modes, RxJS wrapping with cancellation, and the `@nestjs/axios` migration table. Read when going beyond the basics or migrating.
|
|
117
|
+
|
|
118
|
+
Sources: [HTTP client](https://docs.nestjs.com/application/http-client)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# @nestjs/http-client reference
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
- Interceptors
|
|
5
|
+
- Streaming responses
|
|
6
|
+
- Dispatchers: pools, proxies, mTLS
|
|
7
|
+
- Redirects
|
|
8
|
+
- Async configuration variants
|
|
9
|
+
- RxJS
|
|
10
|
+
- Migrating from @nestjs/axios
|
|
11
|
+
|
|
12
|
+
## Interceptors
|
|
13
|
+
|
|
14
|
+
Function form:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
export function apiKeyInterceptor(apiKey: string): HttpClientInterceptorFn {
|
|
18
|
+
return (request, next) => { request.headers.set('x-api-key', apiKey); return next(request); };
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Class form (DI):
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
@Injectable()
|
|
26
|
+
export class GithubAuthInterceptor implements HttpClientInterceptor {
|
|
27
|
+
constructor(private readonly tokens: GithubTokenService) {}
|
|
28
|
+
async intercept(request: HttpRequest, next: HttpHandler): Promise<Response> {
|
|
29
|
+
request.headers.set('authorization', `Bearer ${await this.tokens.getToken()}`);
|
|
30
|
+
return next(request);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- `HttpRequest`: `method`, `url` (URL), `headers` (Headers), `body`, `signal`, `attempt`, `context` (from the request's `context` option).
|
|
36
|
+
- Listed first = outermost; `forRoot()` interceptors run before the client's. They run **once per attempt** (fresh tokens on retry). They see the raw `Response` before the client throws for non-2xx.
|
|
37
|
+
- Class interceptors resolve at app init; an existing provider instance is reused.
|
|
38
|
+
- Logging interceptor: log `method origin+pathname status ms attempt` — not the query string (PII/keys). Tracing needs no interceptor with `@nestjs/observe`.
|
|
39
|
+
|
|
40
|
+
## Streaming responses
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
@Get(':id/pdf')
|
|
44
|
+
async download(@Param('id') id: string) {
|
|
45
|
+
const { data, headers } = await this.billing.get('/invoices/:id/pdf', { params: { id }, responseType: 'stream' });
|
|
46
|
+
return new StreamableFile(data, { type: headers.get('content-type') ?? 'application/pdf' });
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Timeout stops once headers arrive; the `signal` still cancels. Don't forward upstream `content-length` (fetch decompresses).
|
|
51
|
+
|
|
52
|
+
## Dispatchers: pools, proxies, mTLS
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { Agent, ProxyAgent } from 'undici'; // install undici yourself, matching your Node's bundled major
|
|
56
|
+
|
|
57
|
+
HttpClientModule.register({
|
|
58
|
+
name: 'partner',
|
|
59
|
+
baseUrl: 'https://partner.example.com',
|
|
60
|
+
dispatcher: new Agent({ connections: 20, connect: { ca, cert, key } }),
|
|
61
|
+
});
|
|
62
|
+
// proxy: dispatcher: new ProxyAgent('http://proxy.internal:3128')
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Node `http.Agent` instances don't work with fetch.
|
|
66
|
+
|
|
67
|
+
## Redirects
|
|
68
|
+
|
|
69
|
+
Default with `baseUrl`: follow same-origin only, cross-origin → `HttpNetworkError`. `'follow'` anywhere, `'error'` on any redirect, `'manual'` returns 3xx (combine with `throwOnHttpError: false`).
|
|
70
|
+
|
|
71
|
+
## Async configuration variants
|
|
72
|
+
|
|
73
|
+
- `registerAsync({ name, imports, inject, useFactory, interceptors, isGlobal })` — factory returns `HttpClientFactoryOptions` (no name/isGlobal/imports/interceptor classes).
|
|
74
|
+
- `useClass: GithubClientConfig` implementing `HttpClientOptionsFactory.createHttpClientOptions()`; `useExisting` with the exporting module in `imports`.
|
|
75
|
+
- `forRootAsync()` with `HttpClientModuleOptionsFactory`.
|
|
76
|
+
- Outside Nest (scripts): `new HttpClient(options)`.
|
|
77
|
+
|
|
78
|
+
## RxJS
|
|
79
|
+
|
|
80
|
+
`defer(() => this.http.get('/cats'))` starts on subscribe; neither `defer` nor `from` cancels on unsubscribe. For cancellation:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
function fromRequest<T>(send: (signal: AbortSignal) => Promise<T>): Observable<T> {
|
|
84
|
+
return new Observable<T>((subscriber) => {
|
|
85
|
+
const controller = new AbortController();
|
|
86
|
+
send(controller.signal).then((v) => { subscriber.next(v); subscriber.complete(); }, (e) => subscriber.error(e));
|
|
87
|
+
return () => controller.abort();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Migrating from @nestjs/axios
|
|
93
|
+
|
|
94
|
+
| `@nestjs/axios` | `@nestjs/http-client` |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| `HttpModule.register({ baseURL })` | `HttpClientModule.register({ baseUrl })` |
|
|
97
|
+
| `HttpModule.registerAsync()` | `HttpClientModule.registerAsync()` (also takes `name`) |
|
|
98
|
+
| `HttpModuleOptionsFactory.createHttpOptions()` | `HttpClientOptionsFactory.createHttpClientOptions()` |
|
|
99
|
+
| `extraProviders` | a module exporting the providers, in `imports` |
|
|
100
|
+
| one `HttpService` per module | named clients, `@InjectHttpClient(name)` |
|
|
101
|
+
| Observables + `firstValueFrom(...)` | `await this.http.get(url)` |
|
|
102
|
+
| `post(url, data)` | `post(url, { json: data })` |
|
|
103
|
+
| `params` (query) | `query` (arrays repeat keys); `params` = path segments |
|
|
104
|
+
| `response.headers['etag']` | `response.headers.get('etag')` |
|
|
105
|
+
| `AxiosError` / `error.response?.status` | `HttpResponseError` / `error.status`, `error.body` |
|
|
106
|
+
| `error.code === 'ECONNABORTED'` | `HttpTimeoutError` |
|
|
107
|
+
| `validateStatus: () => true` | `throwOnHttpError: false` |
|
|
108
|
+
| `axiosRef` | none; options or a custom `fetch` |
|
|
109
|
+
| `axiosRef.interceptors.*.use()` | `interceptors` (one function for request + response) |
|
|
110
|
+
| `httpsAgent` / `proxy` | `dispatcher` (undici `Agent` / `ProxyAgent`) |
|
|
111
|
+
| `maxRedirects: 0` | `redirect: 'manual'` |
|
|
112
|
+
| no retries | retries on by default; `retry: false` to match old behaviour |
|
|
113
|
+
| `timeout` whole request | `timeout` per attempt (`'5s'`) |
|
|
114
|
+
| `onUploadProgress` / `onDownloadProgress` | none; count stream bytes |
|
|
115
|
+
|
|
116
|
+
Both packages can coexist during migration. Update tests: mock `fetch` instead of `HttpService`.
|