@acontplus/ng-infrastructure 2.3.0 → 3.1.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/README.md
CHANGED
|
@@ -1,85 +1,159 @@
|
|
|
1
1
|
# @acontplus/ng-infrastructure
|
|
2
2
|
|
|
3
|
-
Angular infrastructure library providing HTTP interceptors
|
|
4
|
-
adapters, and core services following clean architecture
|
|
5
|
-
enterprise applications.
|
|
3
|
+
Angular infrastructure library providing HTTP interceptors with multi-language
|
|
4
|
+
support, repositories, adapters, and core services following clean architecture
|
|
5
|
+
patterns for robust enterprise applications.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
# Using npm
|
|
11
10
|
npm install @acontplus/ng-infrastructure
|
|
12
|
-
|
|
13
|
-
# Using pnpm
|
|
11
|
+
# or
|
|
14
12
|
pnpm add @acontplus/ng-infrastructure
|
|
15
13
|
```
|
|
16
14
|
|
|
15
|
+
## Peer Dependencies
|
|
16
|
+
|
|
17
|
+
- `@angular/common`: ^22.1.0
|
|
18
|
+
- `@angular/core`: ^22.1.0
|
|
19
|
+
- `@angular/router`: ^22.1.0
|
|
20
|
+
- `@acontplus/core`: ^1.2.0
|
|
21
|
+
- `@acontplus/ng-config`: ^1.1.0
|
|
22
|
+
- `@acontplus/ng-notifications`: ^1.0.0
|
|
23
|
+
|
|
17
24
|
## Features
|
|
18
25
|
|
|
19
|
-
- **HTTP Interceptors**: API request/response handling
|
|
20
|
-
and loading indicators
|
|
26
|
+
- **HTTP Interceptors**: API request/response handling with localized error messages,
|
|
27
|
+
`Accept-Language` header injection, HTTP context management, and loading indicators
|
|
28
|
+
- **Language Detection**: `LanguageInfo` service resolves language from JWT claims,
|
|
29
|
+
session storage, and browser settings
|
|
21
30
|
- **Repository Pattern**: Base HTTP repository, generic repository, and
|
|
22
31
|
repository factory
|
|
23
|
-
- **HTTP Adapters**: Angular HTTP client adapter for external service
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
management
|
|
32
|
+
- **HTTP Adapters**: Angular HTTP client adapter for external service integration
|
|
33
|
+
- **Core Services**: Configuration, correlation tracking, language info, logging,
|
|
34
|
+
and tenant management
|
|
27
35
|
- **Use Cases**: Base use case patterns with command and query separation (CQRS)
|
|
28
|
-
- **Clean Architecture**: Separation of concerns with infrastructure layer
|
|
29
|
-
abstractions
|
|
30
|
-
- **TypeScript Support**: Full type safety with comprehensive interfaces
|
|
36
|
+
- **Clean Architecture**: Separation of concerns with infrastructure layer abstractions
|
|
31
37
|
|
|
32
38
|
## Quick Start
|
|
33
39
|
|
|
34
40
|
### Configure Interceptors
|
|
35
41
|
|
|
42
|
+
The recommended interceptor order is **httpContextInterceptor first**, then
|
|
43
|
+
apiInterceptor, then others:
|
|
44
|
+
|
|
36
45
|
```typescript
|
|
37
46
|
import {
|
|
38
47
|
apiInterceptor,
|
|
39
48
|
httpContextInterceptor,
|
|
40
49
|
spinnerInterceptor,
|
|
50
|
+
provideHttpContext,
|
|
41
51
|
} from '@acontplus/ng-infrastructure';
|
|
42
52
|
|
|
43
|
-
// In app.config.ts
|
|
44
53
|
export const appConfig: ApplicationConfig = {
|
|
45
54
|
providers: [
|
|
46
55
|
provideHttpClient(
|
|
47
|
-
withInterceptors([
|
|
56
|
+
withInterceptors([
|
|
57
|
+
httpContextInterceptor, // 1st: URL resolution, headers, Accept-Language, 401 refresh
|
|
58
|
+
apiInterceptor, // 2nd: response normalization, localized toasts, Bearer tokens
|
|
59
|
+
spinnerInterceptor, // 3rd: loading indicator
|
|
60
|
+
// csrfInterceptor, // 4th: CSRF tokens (from @acontplus/ng-auth)
|
|
61
|
+
]),
|
|
48
62
|
),
|
|
63
|
+
provideHttpContext({
|
|
64
|
+
enableLanguageHeader: true, // sends Accept-Language header
|
|
65
|
+
}),
|
|
49
66
|
],
|
|
50
67
|
};
|
|
51
68
|
```
|
|
52
69
|
|
|
70
|
+
`provideHttpContext()` configures the `httpContextInterceptor`. Enable features
|
|
71
|
+
matching your backend contract:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
provideHttpContext({
|
|
75
|
+
enableLanguageHeader: true, // add Accept-Language header (default: true)
|
|
76
|
+
enableCorrelationTracking: true, // add Correlation-Id header (default: true)
|
|
77
|
+
includeAuthToken: true, // add Authorization: Bearer header (default: true)
|
|
78
|
+
baseUrlInjection: true, // prepend apiBaseUrl (default: true)
|
|
79
|
+
refreshTokenCallback: () => authService.refreshToken(), // 401 refresh handler
|
|
80
|
+
logoutCallback: () => authService.logout(), // failed refresh handler
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
53
84
|
## HTTP Interceptors
|
|
54
85
|
|
|
55
86
|
### API Interceptor
|
|
56
87
|
|
|
57
|
-
Handles API request/response transformation
|
|
88
|
+
Handles API request/response transformation, **localized error messages**, and
|
|
89
|
+
notification display. Uses `@acontplus/core` `getLocalizedErrorMessage()` and
|
|
90
|
+
`getLocalizedAppMessage()` to show translated notifications based on the current
|
|
91
|
+
language.
|
|
58
92
|
|
|
59
93
|
```typescript
|
|
60
94
|
import { apiInterceptor } from '@acontplus/ng-infrastructure';
|
|
61
95
|
|
|
62
96
|
// Automatically handles:
|
|
63
|
-
// -
|
|
64
|
-
// -
|
|
65
|
-
// -
|
|
66
|
-
// -
|
|
97
|
+
// - Response standardization into ApiResponse envelope
|
|
98
|
+
// - Data unwrapping for success/warning responses
|
|
99
|
+
// - Localized toast notifications via LanguageInfo + AppMessageKey
|
|
100
|
+
// - Individual ApiError entries translated by error code
|
|
101
|
+
// - HTTP-level errors (0, 5xx) with localized titles and messages
|
|
102
|
+
// - SKIP_NOTIFICATION / SHOW_NOTIFICATIONS HttpContext tokens
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### HttpContext Tokens
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { HttpContext } from '@angular/common/http';
|
|
109
|
+
import { SKIP_NOTIFICATION, SHOW_NOTIFICATIONS } from '@acontplus/ng-infrastructure';
|
|
110
|
+
|
|
111
|
+
// Suppress all toast notifications for this request
|
|
112
|
+
this.http.get('/api/health', { context: new HttpContext().set(SKIP_NOTIFICATION, true) });
|
|
113
|
+
|
|
114
|
+
// Force-show notifications even on GET requests or excluded URLs
|
|
115
|
+
this.http.get('/api/export', { context: new HttpContext().set(SHOW_NOTIFICATIONS, true) });
|
|
67
116
|
```
|
|
68
117
|
|
|
69
118
|
### HTTP Context Interceptor
|
|
70
119
|
|
|
71
|
-
|
|
120
|
+
Adds standard headers (`Correlation-Id`, `Tenant-Id`, `Request-Id`, `Accept-Language`,
|
|
121
|
+
`Client-Version`, `Client-Id`), resolves request URLs against `apiBaseUrl`, and
|
|
122
|
+
handles 401 token refresh, 403 forbidden, and 429 rate-limiting events.
|
|
72
123
|
|
|
73
124
|
```typescript
|
|
74
125
|
import { httpContextInterceptor } from '@acontplus/ng-infrastructure';
|
|
75
126
|
|
|
76
127
|
// Automatically adds:
|
|
77
|
-
// -
|
|
78
|
-
// - Request
|
|
79
|
-
// - Tenant
|
|
80
|
-
// -
|
|
128
|
+
// - Accept-Language: <bcp47 tag> (from LanguageInfo)
|
|
129
|
+
// - Correlation-Id, Request-Id, Timestamp
|
|
130
|
+
// - Tenant-Id (when multi-tenancy is enabled)
|
|
131
|
+
// - Client-Version, Client-Id
|
|
132
|
+
// - Authorization: Bearer (when auth token available)
|
|
133
|
+
// - Content-Type: application/json (for POST/PUT/PATCH with JSON body)
|
|
81
134
|
```
|
|
82
135
|
|
|
136
|
+
#### `Accept-Language` header
|
|
137
|
+
|
|
138
|
+
The interceptor resolves the current language from `LanguageInfo` (priority:
|
|
139
|
+
JWT `locale` claim → `sessionStorage` cache → browser `navigator.languages`)
|
|
140
|
+
and sends the corresponding BCP47 tag. Disable with `enableLanguageHeader: false`.
|
|
141
|
+
|
|
142
|
+
#### API base URL convention
|
|
143
|
+
|
|
144
|
+
Configure `Environment.apiBaseUrl` without a trailing slash. The interceptor safely joins it
|
|
145
|
+
with request paths, accepting paths with or without a leading slash.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
apiBaseUrl: 'https://api.example.com/gateway';
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
For example, both `auth/login` and `/auth/login` resolve to
|
|
152
|
+
`https://api.example.com/gateway/auth/login`. Absolute and protocol-relative URLs are left
|
|
153
|
+
unchanged. The interceptor deliberately does not use `new URL(path, baseUrl)`: it joins an API
|
|
154
|
+
prefix and endpoint rather than resolving an RFC 3986 relative reference, which could discard a
|
|
155
|
+
base path such as `/gateway`.
|
|
156
|
+
|
|
83
157
|
### Spinner Interceptor
|
|
84
158
|
|
|
85
159
|
Manages loading indicators during HTTP operations.
|
|
@@ -103,7 +177,8 @@ Abstract base class for HTTP-based data access.
|
|
|
103
177
|
```typescript
|
|
104
178
|
import { BaseHttpRepository } from '@acontplus/ng-infrastructure';
|
|
105
179
|
import { Injectable } from '@angular/core';
|
|
106
|
-
import {
|
|
180
|
+
import { Observable } from 'rxjs';
|
|
181
|
+
import { RepositoryConfig } from '@acontplus/core';
|
|
107
182
|
|
|
108
183
|
interface User {
|
|
109
184
|
id: number;
|
|
@@ -112,17 +187,15 @@ interface User {
|
|
|
112
187
|
}
|
|
113
188
|
|
|
114
189
|
@Injectable({ providedIn: 'root' })
|
|
115
|
-
export class UserRepository extends BaseHttpRepository
|
|
116
|
-
|
|
117
|
-
|
|
190
|
+
export class UserRepository extends BaseHttpRepository {
|
|
191
|
+
protected override config: RepositoryConfig = {
|
|
192
|
+
baseUrl: '/api',
|
|
193
|
+
endpoint: 'users',
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
findAll(): Observable<User[]> {
|
|
197
|
+
return this.get<User[]>();
|
|
118
198
|
}
|
|
119
|
-
|
|
120
|
-
// Inherits common CRUD operations:
|
|
121
|
-
// - getById(id: number)
|
|
122
|
-
// - getAll()
|
|
123
|
-
// - create(entity: User)
|
|
124
|
-
// - update(id: number, entity: Partial<User>)
|
|
125
|
-
// - delete(id: number)
|
|
126
199
|
}
|
|
127
200
|
```
|
|
128
201
|
|
|
@@ -130,21 +203,9 @@ export class UserRepository extends BaseHttpRepository<User> {
|
|
|
130
203
|
|
|
131
204
|
Generic repository implementation with type safety.
|
|
132
205
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
@Injectable({ providedIn: 'root' })
|
|
137
|
-
export class CustomerRepository extends GenericRepository<Customer, number> {
|
|
138
|
-
constructor(http: HttpClient) {
|
|
139
|
-
super(http, '/api/customers');
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Custom business methods
|
|
143
|
-
async findByEmail(email: string): Promise<Customer[]> {
|
|
144
|
-
return this.http.get<Customer[]>(`${this.baseUrl}/search?email=${email}`).toPromise() || [];
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
```
|
|
206
|
+
`GenericRepository` receives its `RepositoryConfig` through `REPOSITORY_CONFIG`.
|
|
207
|
+
For application services, prefer `RepositoryFactory` when a reusable CRUD
|
|
208
|
+
repository is sufficient.
|
|
148
209
|
|
|
149
210
|
### Repository Factory
|
|
150
211
|
|
|
@@ -152,17 +213,18 @@ Factory pattern for creating repository instances.
|
|
|
152
213
|
|
|
153
214
|
```typescript
|
|
154
215
|
import { RepositoryFactory } from '@acontplus/ng-infrastructure';
|
|
216
|
+
import { Injectable } from '@angular/core';
|
|
155
217
|
|
|
156
218
|
@Injectable({ providedIn: 'root' })
|
|
157
219
|
export class DataService {
|
|
158
220
|
constructor(private repositoryFactory: RepositoryFactory) {}
|
|
159
221
|
|
|
160
222
|
getUserRepository() {
|
|
161
|
-
return this.repositoryFactory.create<User>('users');
|
|
223
|
+
return this.repositoryFactory.create<User>({ baseUrl: '/api', endpoint: 'users' });
|
|
162
224
|
}
|
|
163
225
|
|
|
164
226
|
getCustomerRepository() {
|
|
165
|
-
return this.repositoryFactory.create<Customer>('customers');
|
|
227
|
+
return this.repositoryFactory.create<Customer>({ baseUrl: '/api', endpoint: 'customers' });
|
|
166
228
|
}
|
|
167
229
|
}
|
|
168
230
|
```
|
|
@@ -174,41 +236,87 @@ export class DataService {
|
|
|
174
236
|
Adapter for Angular HTTP client integration.
|
|
175
237
|
|
|
176
238
|
```typescript
|
|
239
|
+
import { HttpClient } from '@angular/common/http';
|
|
240
|
+
import { inject, Injectable } from '@angular/core';
|
|
177
241
|
import { AngularHttpAdapter } from '@acontplus/ng-infrastructure';
|
|
178
242
|
|
|
179
243
|
@Injectable({ providedIn: 'root' })
|
|
180
244
|
export class ExternalApiService {
|
|
181
|
-
|
|
245
|
+
private readonly httpAdapter = new AngularHttpAdapter(
|
|
246
|
+
inject(HttpClient),
|
|
247
|
+
'https://api.example.com',
|
|
248
|
+
);
|
|
182
249
|
|
|
183
|
-
async fetchExternalData(
|
|
184
|
-
return this.httpAdapter.get(
|
|
250
|
+
async fetchExternalData(): Promise<unknown> {
|
|
251
|
+
return this.httpAdapter.get('data');
|
|
185
252
|
}
|
|
186
253
|
|
|
187
|
-
async postData(
|
|
188
|
-
return this.httpAdapter.post(
|
|
254
|
+
async postData(data: unknown): Promise<unknown> {
|
|
255
|
+
return this.httpAdapter.post('data', data);
|
|
189
256
|
}
|
|
190
257
|
}
|
|
191
258
|
```
|
|
192
259
|
|
|
193
260
|
## Core Services
|
|
194
261
|
|
|
262
|
+
### LanguageInfo Service
|
|
263
|
+
|
|
264
|
+
Detects and manages the current application language with a priority chain:
|
|
265
|
+
JWT `locale` claim → `sessionStorage` cache → browser `navigator.languages`.
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { LanguageInfo } from '@acontplus/ng-infrastructure';
|
|
269
|
+
import { Language, languageToBcp47 } from '@acontplus/core';
|
|
270
|
+
import { Injectable } from '@angular/core';
|
|
271
|
+
|
|
272
|
+
@Injectable({ providedIn: 'root' })
|
|
273
|
+
export class LocaleService {
|
|
274
|
+
constructor(private languageInfo: LanguageInfo) {}
|
|
275
|
+
|
|
276
|
+
getCurrentLanguage(): Language {
|
|
277
|
+
return this.languageInfo.getCurrentLanguage();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
getBcp47Tag(): string {
|
|
281
|
+
return this.languageInfo.getBcp47Tag();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
setLanguage(language: Language): void {
|
|
285
|
+
this.languageInfo.setLanguage(language);
|
|
286
|
+
// persists to sessionStorage automatically
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The `httpContextInterceptor` consumes `LanguageInfo` to set the `Accept-Language`
|
|
292
|
+
header on every request. The `apiInterceptor` uses it for localized toast
|
|
293
|
+
notifications.
|
|
294
|
+
|
|
295
|
+
**Signals**:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
const lang = this.languageInfo.language(); // Signal<Language>
|
|
299
|
+
const tag = this.languageInfo.bcp47Tag(); // Signal<string>
|
|
300
|
+
```
|
|
301
|
+
|
|
195
302
|
### Core Config Service
|
|
196
303
|
|
|
197
304
|
Manages application configuration and settings.
|
|
198
305
|
|
|
199
306
|
```typescript
|
|
200
307
|
import { CoreConfigService } from '@acontplus/ng-infrastructure';
|
|
308
|
+
import { Injectable } from '@angular/core';
|
|
201
309
|
|
|
202
310
|
@Injectable({ providedIn: 'root' })
|
|
203
311
|
export class AppService {
|
|
204
312
|
constructor(private configService: CoreConfigService) {}
|
|
205
313
|
|
|
206
314
|
getApiBaseUrl(): string {
|
|
207
|
-
return this.configService.
|
|
315
|
+
return this.configService.getConfig().apiBaseUrl;
|
|
208
316
|
}
|
|
209
317
|
|
|
210
|
-
getTimeout(): number {
|
|
211
|
-
return this.configService.
|
|
318
|
+
getTimeout(): number | undefined {
|
|
319
|
+
return this.configService.get('apiTimeout');
|
|
212
320
|
}
|
|
213
321
|
}
|
|
214
322
|
```
|
|
@@ -219,17 +327,18 @@ Handles correlation IDs for distributed request tracing.
|
|
|
219
327
|
|
|
220
328
|
```typescript
|
|
221
329
|
import { CorrelationInfo } from '@acontplus/ng-infrastructure';
|
|
330
|
+
import { Injectable } from '@angular/core';
|
|
222
331
|
|
|
223
332
|
@Injectable({ providedIn: 'root' })
|
|
224
333
|
export class TrackingService {
|
|
225
334
|
constructor(private correlationInfo: CorrelationInfo) {}
|
|
226
335
|
|
|
227
336
|
getCurrentCorrelationId(): string {
|
|
228
|
-
return this.correlationInfo.
|
|
337
|
+
return this.correlationInfo.getId();
|
|
229
338
|
}
|
|
230
339
|
|
|
231
|
-
|
|
232
|
-
|
|
340
|
+
resetCorrelationId(): void {
|
|
341
|
+
this.correlationInfo.resetCorrelationId();
|
|
233
342
|
}
|
|
234
343
|
}
|
|
235
344
|
```
|
|
@@ -240,6 +349,7 @@ Structured logging with correlation tracking.
|
|
|
240
349
|
|
|
241
350
|
```typescript
|
|
242
351
|
import { LoggingService } from '@acontplus/ng-infrastructure';
|
|
352
|
+
import { Injectable } from '@angular/core';
|
|
243
353
|
|
|
244
354
|
@Injectable({ providedIn: 'root' })
|
|
245
355
|
export class BusinessService {
|
|
@@ -266,8 +376,13 @@ export class BusinessService {
|
|
|
266
376
|
|
|
267
377
|
Abstract base class for business logic encapsulation.
|
|
268
378
|
|
|
379
|
+
`Command` and `Query` capture `LoggingService` while Angular creates the
|
|
380
|
+
subclass. Register concrete subclasses with Angular DI; do not instantiate
|
|
381
|
+
them outside an injection context.
|
|
382
|
+
|
|
269
383
|
```typescript
|
|
270
384
|
import { BaseUseCase } from '@acontplus/ng-infrastructure';
|
|
385
|
+
import { Observable } from 'rxjs';
|
|
271
386
|
|
|
272
387
|
export class CreateUserUseCase extends BaseUseCase<CreateUserCommand, User> {
|
|
273
388
|
constructor(
|
|
@@ -277,20 +392,17 @@ export class CreateUserUseCase extends BaseUseCase<CreateUserCommand, User> {
|
|
|
277
392
|
super();
|
|
278
393
|
}
|
|
279
394
|
|
|
280
|
-
|
|
395
|
+
execute(command: CreateUserCommand): Observable<User> {
|
|
281
396
|
this.logger.info('Creating user', { email: command.email });
|
|
282
397
|
|
|
283
398
|
// Validation
|
|
284
399
|
this.validateCommand(command);
|
|
285
400
|
|
|
286
401
|
// Business logic
|
|
287
|
-
|
|
402
|
+
return this.userRepository.create({
|
|
288
403
|
name: command.name,
|
|
289
404
|
email: command.email,
|
|
290
405
|
});
|
|
291
|
-
|
|
292
|
-
this.logger.info('User created successfully', { userId: user.id });
|
|
293
|
-
return user;
|
|
294
406
|
}
|
|
295
407
|
|
|
296
408
|
private validateCommand(command: CreateUserCommand): void {
|
|
@@ -306,42 +418,34 @@ export class CreateUserUseCase extends BaseUseCase<CreateUserCommand, User> {
|
|
|
306
418
|
Separation of read and write operations.
|
|
307
419
|
|
|
308
420
|
```typescript
|
|
309
|
-
import {
|
|
421
|
+
import { Command, Query } from '@acontplus/ng-infrastructure';
|
|
422
|
+
import { Observable } from 'rxjs';
|
|
310
423
|
|
|
311
424
|
// Command for write operations
|
|
312
|
-
export
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
public readonly email: string,
|
|
317
|
-
) {
|
|
318
|
-
super();
|
|
319
|
-
}
|
|
425
|
+
export interface UpdateUserRequest {
|
|
426
|
+
userId: number;
|
|
427
|
+
name: string;
|
|
428
|
+
email: string;
|
|
320
429
|
}
|
|
321
430
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
constructor(public readonly userId: number) {
|
|
431
|
+
export class UpdateUserCommand extends Command<UpdateUserRequest, User> {
|
|
432
|
+
constructor(private readonly userRepository: UserRepository) {
|
|
325
433
|
super();
|
|
326
434
|
}
|
|
327
|
-
}
|
|
328
435
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
private updateUserUseCase: UpdateUserUseCase,
|
|
334
|
-
private getUserUseCase: GetUserUseCase,
|
|
335
|
-
) {}
|
|
436
|
+
protected executeInternal(request: UpdateUserRequest): Observable<User> {
|
|
437
|
+
return this.userRepository.update(request.userId, request);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
336
440
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
441
|
+
// Query for read operations
|
|
442
|
+
export class GetUserQuery extends Query<number, User> {
|
|
443
|
+
constructor(private readonly userRepository: UserRepository) {
|
|
444
|
+
super();
|
|
340
445
|
}
|
|
341
446
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
return this.getUserUseCase.execute(query);
|
|
447
|
+
protected executeInternal(userId: number): Observable<User> {
|
|
448
|
+
return this.userRepository.getById(userId);
|
|
345
449
|
}
|
|
346
450
|
}
|
|
347
451
|
```
|