@flusys/ng-core 4.1.1 → 5.0.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 +94 -304
- package/fesm2022/flusys-ng-core.mjs +3 -3
- package/fesm2022/flusys-ng-core.mjs.map +1 -1
- package/package.json +1 -1
- package/types/flusys-ng-core.d.ts +2 -1
package/README.md
CHANGED
|
@@ -1,69 +1,9 @@
|
|
|
1
1
|
# @flusys/ng-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Foundation library for the FLUSYS Angular platform — app configuration token, base HTTP service, loader service, and HTTP interceptors.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@flusys/ng-core)
|
|
6
|
-
[](https://www.typescriptlang.org)
|
|
8
|
-
[](LICENSE)
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Table of Contents
|
|
13
|
-
|
|
14
|
-
- [Overview](#overview)
|
|
15
|
-
- [Features](#features)
|
|
16
|
-
- [Compatibility](#compatibility)
|
|
17
|
-
- [Installation](#installation)
|
|
18
|
-
- [Quick Start](#quick-start)
|
|
19
|
-
- [Configuration Reference](#configuration-reference)
|
|
20
|
-
- [IAppConfig](#iappconfig)
|
|
21
|
-
- [APP_CONFIG Token](#app_config-token)
|
|
22
|
-
- [Base Services](#base-services)
|
|
23
|
-
- [BaseApiService](#baseapiservice)
|
|
24
|
-
- [ApiLoaderService](#apiloaderservice)
|
|
25
|
-
- [HTTP Interceptors](#http-interceptors)
|
|
26
|
-
- [Helper Functions](#helper-functions)
|
|
27
|
-
- [Exported Tokens](#exported-tokens)
|
|
28
|
-
- [Troubleshooting](#troubleshooting)
|
|
29
|
-
- [License](#license)
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## Overview
|
|
34
|
-
|
|
35
|
-
`@flusys/ng-core` is the zero-dependency foundation of the FLUSYS Angular package hierarchy. Every other FLUSYS Angular package depends on it. It provides:
|
|
36
|
-
|
|
37
|
-
- The **`APP_CONFIG`** injection token — single source of truth for all application configuration
|
|
38
|
-
- **`BaseApiService`** — typed POST-only RPC HTTP client
|
|
39
|
-
- **`ApiLoaderService`** — manages global loading state across concurrent requests
|
|
40
|
-
- **HTTP interceptors** — base URL injection and error normalization
|
|
41
|
-
- Utility helper functions for feature detection
|
|
42
|
-
|
|
43
|
-
This package has **no FLUSYS dependencies** — it only depends on Angular core.
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## Features
|
|
48
|
-
|
|
49
|
-
- ✅ Typed `IAppConfig` with full IntelliSense
|
|
50
|
-
- ✅ POST-only RPC HTTP client (`BaseApiService`)
|
|
51
|
-
- ✅ Global loading state management (`ApiLoaderService`)
|
|
52
|
-
- ✅ `APP_CONFIG` injection token for DI-based configuration
|
|
53
|
-
- ✅ Feature detection helpers (`isAuthEnabled()`, `isIamEnabled()`, `isSignUpEnabled()`, etc.)
|
|
54
|
-
- ✅ HTTP interceptors for base URL and error handling
|
|
55
|
-
- ✅ Zoneless-compatible (no Zone.js dependency)
|
|
56
|
-
- ✅ Signal-friendly architecture
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## Compatibility
|
|
61
|
-
|
|
62
|
-
| Package | Version |
|
|
63
|
-
|---------|---------|
|
|
64
|
-
| Angular | 21+ |
|
|
65
|
-
| TypeScript | 5.x |
|
|
66
|
-
| RxJS | 7.x |
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
67
7
|
|
|
68
8
|
---
|
|
69
9
|
|
|
@@ -75,308 +15,158 @@ npm install @flusys/ng-core
|
|
|
75
15
|
|
|
76
16
|
---
|
|
77
17
|
|
|
78
|
-
##
|
|
18
|
+
## 1. Register APP_CONFIG and Interceptors
|
|
79
19
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
// environments/environment.ts
|
|
84
|
-
import { IAppConfig } from '@flusys/ng-core';
|
|
85
|
-
|
|
86
|
-
export const environment: IAppConfig = {
|
|
87
|
-
appName: 'My App',
|
|
88
|
-
apiBaseUrl: 'http://localhost:2002',
|
|
89
|
-
production: false,
|
|
90
|
-
services: {
|
|
91
|
-
auth: {
|
|
92
|
-
enabled: true,
|
|
93
|
-
features: {
|
|
94
|
-
signUp: true,
|
|
95
|
-
emailVerification: true,
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
iam: { enabled: true },
|
|
99
|
-
storage: { enabled: true },
|
|
100
|
-
email: { enabled: false },
|
|
101
|
-
formBuilder: { enabled: false },
|
|
102
|
-
eventManager: { enabled: false },
|
|
103
|
-
notification: { enabled: false },
|
|
104
|
-
localization: { enabled: false },
|
|
105
|
-
},
|
|
106
|
-
multiTenant: false,
|
|
107
|
-
permissionMode: 'FULL',
|
|
108
|
-
};
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### 2. Provide Config in `app.config.ts`
|
|
20
|
+
Provide the config token and HTTP interceptors once in `app.config.ts`. Every FLUSYS package reads from `APP_CONFIG`.
|
|
112
21
|
|
|
113
22
|
```typescript
|
|
114
23
|
import { ApplicationConfig } from '@angular/core';
|
|
115
|
-
import {
|
|
116
|
-
import {
|
|
24
|
+
import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
|
|
25
|
+
import {
|
|
26
|
+
APP_CONFIG,
|
|
27
|
+
errorCatchingInterceptor,
|
|
28
|
+
apiLoaderInterceptor,
|
|
29
|
+
} from '@flusys/ng-core';
|
|
117
30
|
|
|
118
31
|
export const appConfig: ApplicationConfig = {
|
|
119
32
|
providers: [
|
|
120
|
-
{
|
|
121
|
-
|
|
33
|
+
{
|
|
34
|
+
provide: APP_CONFIG,
|
|
35
|
+
useValue: {
|
|
36
|
+
appName: 'My App',
|
|
37
|
+
appLogo: '/assets/logo.png',
|
|
38
|
+
apiBaseUrl: 'http://localhost:2002',
|
|
39
|
+
production: false,
|
|
40
|
+
permissionMode: 'FULL', // 'RBAC' | 'DIRECT' | 'FULL'
|
|
41
|
+
multiTenant: false,
|
|
42
|
+
services: {
|
|
43
|
+
auth: {
|
|
44
|
+
enabled: true,
|
|
45
|
+
features: {
|
|
46
|
+
signUp: true,
|
|
47
|
+
emailVerification: true,
|
|
48
|
+
companySelection: true,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
iam: { enabled: true },
|
|
52
|
+
storage: { enabled: true },
|
|
53
|
+
formBuilder: { enabled: false },
|
|
54
|
+
email: { enabled: false },
|
|
55
|
+
eventManager: { enabled: false },
|
|
56
|
+
notification: { enabled: false, socketUrl: 'ws://localhost:2002' }, // optional
|
|
57
|
+
localization: { enabled: false },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
provideHttpClient(
|
|
62
|
+
withFetch(),
|
|
63
|
+
withInterceptors([
|
|
64
|
+
// authInterceptor, // from @flusys/ng-auth — add first
|
|
65
|
+
// tokenRefreshInterceptor, // from @flusys/ng-auth — add second
|
|
66
|
+
errorCatchingInterceptor, // shows PrimeNG toast on non-401/403 errors
|
|
67
|
+
apiLoaderInterceptor, // drives global progress bar
|
|
68
|
+
]),
|
|
69
|
+
),
|
|
122
70
|
],
|
|
123
71
|
};
|
|
124
72
|
```
|
|
125
73
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
import { inject } from '@angular/core';
|
|
130
|
-
import { APP_CONFIG, IAppConfig } from '@flusys/ng-core';
|
|
131
|
-
|
|
132
|
-
@Injectable({ providedIn: 'root' })
|
|
133
|
-
export class MyService {
|
|
134
|
-
private config = inject<IAppConfig>(APP_CONFIG);
|
|
135
|
-
|
|
136
|
-
getApiUrl(): string {
|
|
137
|
-
return this.config.apiBaseUrl;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
```
|
|
74
|
+
Add `<lib-app-config />` once in your app root template. It renders the global PrimeNG toast, confirm dialog, and progress bar.
|
|
141
75
|
|
|
142
76
|
---
|
|
143
77
|
|
|
144
|
-
##
|
|
78
|
+
## 2. BaseApiService
|
|
145
79
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
Complete configuration interface for the FLUSYS platform:
|
|
149
|
-
|
|
150
|
-
```typescript
|
|
151
|
-
interface IAppConfig {
|
|
152
|
-
/** Application display name */
|
|
153
|
-
appName: string;
|
|
154
|
-
|
|
155
|
-
/** Backend API base URL */
|
|
156
|
-
apiBaseUrl: string;
|
|
157
|
-
|
|
158
|
-
/** Production mode flag */
|
|
159
|
-
production: boolean;
|
|
160
|
-
|
|
161
|
-
/** Multi-tenant mode — isolates data per company */
|
|
162
|
-
multiTenant?: boolean;
|
|
163
|
-
|
|
164
|
-
/** IAM permission evaluation mode */
|
|
165
|
-
permissionMode?: 'RBAC' | 'DIRECT' | 'FULL';
|
|
166
|
-
|
|
167
|
-
services: {
|
|
168
|
-
auth?: {
|
|
169
|
-
enabled: boolean;
|
|
170
|
-
features?: {
|
|
171
|
-
/** Allow self-registration */
|
|
172
|
-
signUp?: boolean;
|
|
173
|
-
/** Email verification on register */
|
|
174
|
-
emailVerification?: boolean;
|
|
175
|
-
/** Require company selection after login */
|
|
176
|
-
companySelection?: boolean;
|
|
177
|
-
};
|
|
178
|
-
};
|
|
179
|
-
iam?: { enabled: boolean };
|
|
180
|
-
storage?: { enabled: boolean };
|
|
181
|
-
email?: { enabled: boolean };
|
|
182
|
-
formBuilder?: { enabled: boolean };
|
|
183
|
-
eventManager?: { enabled: boolean };
|
|
184
|
-
notification?: { enabled: boolean };
|
|
185
|
-
localization?: { enabled: boolean };
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### APP_CONFIG Token
|
|
80
|
+
Extend to create a typed service for any POST-only RPC endpoint. The base URL is derived from `APP_CONFIG.apiBaseUrl`.
|
|
191
81
|
|
|
192
82
|
```typescript
|
|
193
|
-
import {
|
|
194
|
-
|
|
195
|
-
// In component/service
|
|
196
|
-
const config = inject<IAppConfig>(APP_CONFIG);
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
---
|
|
200
|
-
|
|
201
|
-
## Base Services
|
|
202
|
-
|
|
203
|
-
### BaseApiService
|
|
204
|
-
|
|
205
|
-
Typed HTTP client configured for POST-only RPC endpoints. All FLUSYS API services extend this class.
|
|
206
|
-
|
|
207
|
-
```typescript
|
|
208
|
-
import { BaseApiService, ISingleResponse, IListResponse } from '@flusys/ng-core';
|
|
83
|
+
import { Injectable } from '@angular/core';
|
|
84
|
+
import { BaseApiService } from '@flusys/ng-core';
|
|
209
85
|
|
|
210
86
|
@Injectable({ providedIn: 'root' })
|
|
211
|
-
export class
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
getAll(params: IListParams): Observable<IListResponse<Product>> {
|
|
215
|
-
return this.post(`${this.resource}/get-all`, params);
|
|
87
|
+
export class ProductService extends BaseApiService {
|
|
88
|
+
getAll() {
|
|
89
|
+
return this.http.post(`${this.baseUrl}/products/get-all`, {});
|
|
216
90
|
}
|
|
217
91
|
|
|
218
|
-
|
|
219
|
-
return this.post(
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
insert(data: CreateProductDto): Observable<ISingleResponse<Product>> {
|
|
223
|
-
return this.post(`${this.resource}/insert`, data);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
update(data: UpdateProductDto): Observable<ISingleResponse<Product>> {
|
|
227
|
-
return this.post(`${this.resource}/update`, data);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
delete(id: string): Observable<IMessageResponse> {
|
|
231
|
-
return this.post(`${this.resource}/delete`, { id });
|
|
92
|
+
findById(id: string) {
|
|
93
|
+
return this.http.post(this.getUrl(`products/get/${id}`), {});
|
|
232
94
|
}
|
|
233
95
|
}
|
|
234
96
|
```
|
|
235
97
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
| Method | Signature | Description |
|
|
239
|
-
|--------|-----------|-------------|
|
|
240
|
-
| `post<T>` | `(path, body, options?)` | POST request with automatic base URL |
|
|
241
|
-
| `get<T>` | `(path, options?)` | GET request (rarely used in FLUSYS) |
|
|
242
|
-
| `setLoader` | `(show: boolean)` | Manually toggle loading indicator |
|
|
243
|
-
|
|
244
|
-
**BaseApiService** automatically:
|
|
245
|
-
- Prepends `apiBaseUrl` from `APP_CONFIG`
|
|
246
|
-
- Sets `Content-Type: application/json`
|
|
247
|
-
- Triggers `ApiLoaderService` for loading state
|
|
248
|
-
- Normalizes errors through `GlobalErrorHandler`
|
|
98
|
+
---
|
|
249
99
|
|
|
250
|
-
|
|
100
|
+
## 3. ApiLoaderService — Global Progress Bar
|
|
251
101
|
|
|
252
|
-
|
|
102
|
+
`apiLoaderInterceptor` automatically increments/decrements a reference count per request. `show` is `true` while any request is in flight.
|
|
253
103
|
|
|
254
104
|
```typescript
|
|
255
105
|
import { ApiLoaderService } from '@flusys/ng-core';
|
|
256
106
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
private loader = inject(ApiLoaderService);
|
|
260
|
-
|
|
261
|
-
// Reactive loading state (signal)
|
|
262
|
-
isLoading = this.loader.isLoading;
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
```html
|
|
267
|
-
<!-- Use in template -->
|
|
268
|
-
@if (isLoading()) {
|
|
269
|
-
<p-progressBar mode="indeterminate" />
|
|
270
|
-
}
|
|
107
|
+
readonly loader = inject(ApiLoaderService);
|
|
108
|
+
// loader.show — Signal<boolean>
|
|
271
109
|
```
|
|
272
110
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
| Member | Type | Description |
|
|
276
|
-
|--------|------|-------------|
|
|
277
|
-
| `isLoading` | `Signal<boolean>` | True when any HTTP request is pending |
|
|
278
|
-
| `increment()` | `void` | Manually increment request counter |
|
|
279
|
-
| `decrement()` | `void` | Manually decrement request counter |
|
|
280
|
-
|
|
281
|
-
---
|
|
282
|
-
|
|
283
|
-
## HTTP Interceptors
|
|
284
|
-
|
|
285
|
-
`@flusys/ng-core` provides base HTTP interceptors registered via `provideHttpClient`:
|
|
286
|
-
|
|
287
|
-
### Base URL Interceptor
|
|
288
|
-
|
|
289
|
-
Automatically prepends `APP_CONFIG.apiBaseUrl` to all relative HTTP requests.
|
|
111
|
+
Tag requests to get per-section loading signals:
|
|
290
112
|
|
|
291
113
|
```typescript
|
|
292
|
-
|
|
293
|
-
// Relative URLs like '/auth/login' become 'http://localhost:2002/auth/login'
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
### Error Normalizer Interceptor
|
|
114
|
+
import { HttpHeaders } from '@angular/common/http';
|
|
297
115
|
|
|
298
|
-
|
|
116
|
+
this.http.post(url, body, {
|
|
117
|
+
headers: new HttpHeaders({ 'x-loader-tag': 'user-list' }),
|
|
118
|
+
});
|
|
299
119
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
```typescript
|
|
303
|
-
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
|
304
|
-
import { baseUrlInterceptor, errorNormalizerInterceptor } from '@flusys/ng-core';
|
|
305
|
-
|
|
306
|
-
export const appConfig: ApplicationConfig = {
|
|
307
|
-
providers: [
|
|
308
|
-
provideHttpClient(
|
|
309
|
-
withInterceptors([
|
|
310
|
-
baseUrlInterceptor,
|
|
311
|
-
errorNormalizerInterceptor,
|
|
312
|
-
])
|
|
313
|
-
),
|
|
314
|
-
],
|
|
315
|
-
};
|
|
120
|
+
const isUserListLoading = this.loader.getLoader('user-list'); // Signal<boolean>
|
|
316
121
|
```
|
|
317
122
|
|
|
318
123
|
---
|
|
319
124
|
|
|
320
|
-
##
|
|
125
|
+
## 4. Skip Error Toast Per Request
|
|
321
126
|
|
|
322
|
-
|
|
127
|
+
Opt out of the global error toast for requests you handle locally:
|
|
323
128
|
|
|
324
129
|
```typescript
|
|
325
|
-
import {
|
|
326
|
-
|
|
327
|
-
isIamEnabled,
|
|
328
|
-
isStorageEnabled,
|
|
329
|
-
isSignUpEnabled,
|
|
330
|
-
isEmailVerificationEnabled,
|
|
331
|
-
isCompanySelectionEnabled,
|
|
332
|
-
isLocalizationEnabled,
|
|
333
|
-
isNotificationEnabled,
|
|
334
|
-
} from '@flusys/ng-core';
|
|
130
|
+
import { SKIP_ERROR_TOAST } from '@flusys/ng-core';
|
|
131
|
+
import { HttpContext } from '@angular/common/http';
|
|
335
132
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const storageOn = isStorageEnabled(config); // boolean
|
|
340
|
-
const signUpOn = isSignUpEnabled(config); // boolean
|
|
133
|
+
this.http.post(url, body, {
|
|
134
|
+
context: new HttpContext().set(SKIP_ERROR_TOAST, true),
|
|
135
|
+
});
|
|
341
136
|
```
|
|
342
137
|
|
|
343
138
|
---
|
|
344
139
|
|
|
345
|
-
##
|
|
346
|
-
|
|
347
|
-
| Token | Type | Description |
|
|
348
|
-
|-------|------|-------------|
|
|
349
|
-
| `APP_CONFIG` | `InjectionToken<IAppConfig>` | Global app configuration |
|
|
350
|
-
| `TRANSLATE_ADAPTER` | `InjectionToken<ITranslateAdapter>` | Localization adapter interface |
|
|
351
|
-
|
|
352
|
-
---
|
|
353
|
-
|
|
354
|
-
## Troubleshooting
|
|
140
|
+
## 5. TRANSLATE_ADAPTER
|
|
355
141
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
You forgot to provide `APP_CONFIG` in `app.config.ts`:
|
|
142
|
+
Register an adapter so the error interceptor translates error messages. Implemented and provided by `@flusys/ng-localization`.
|
|
359
143
|
|
|
360
144
|
```typescript
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
145
|
+
import { TRANSLATE_ADAPTER } from '@flusys/ng-core';
|
|
146
|
+
|
|
147
|
+
// In app.config.ts providers:
|
|
148
|
+
{ provide: TRANSLATE_ADAPTER, useClass: MyTranslateAdapter }
|
|
364
149
|
```
|
|
365
150
|
|
|
366
|
-
|
|
151
|
+
Required interface: `languageCode: Signal<string>` and `translate(key: string, params?: Record<string, unknown>): string`.
|
|
367
152
|
|
|
368
|
-
|
|
153
|
+
---
|
|
369
154
|
|
|
370
|
-
|
|
155
|
+
## 6. Feature Helper Functions
|
|
371
156
|
|
|
372
|
-
|
|
157
|
+
Read flags without injecting `APP_CONFIG` directly:
|
|
373
158
|
|
|
374
159
|
```typescript
|
|
375
|
-
|
|
376
|
-
|
|
160
|
+
import {
|
|
161
|
+
isSignUpEnabled,
|
|
162
|
+
getPermissionMode,
|
|
163
|
+
getNotificationSocketUrl,
|
|
164
|
+
} from '@flusys/ng-core';
|
|
377
165
|
|
|
378
|
-
|
|
379
|
-
|
|
166
|
+
const config = inject(APP_CONFIG);
|
|
167
|
+
isSignUpEnabled(config); // boolean — reads services.auth.features.signUp
|
|
168
|
+
getPermissionMode(config); // 'RBAC' | 'DIRECT' | 'FULL'
|
|
169
|
+
getNotificationSocketUrl(config); // string | undefined
|
|
380
170
|
```
|
|
381
171
|
|
|
382
172
|
---
|
|
@@ -153,17 +153,17 @@ function errorCatchingInterceptor(req, next) {
|
|
|
153
153
|
}
|
|
154
154
|
/**
|
|
155
155
|
* Extract error summary and detail from HTTP error response.
|
|
156
|
-
* Uses backend response format: { message, messageKey,
|
|
156
|
+
* Uses backend response format: { message, messageKey, messageVariables, error, statusCode }
|
|
157
157
|
* If messageKey is present and translate adapter is available, translates the message.
|
|
158
158
|
*/
|
|
159
159
|
function extractErrorInfo(error, translateAdapter) {
|
|
160
160
|
// Check if we have a messageKey for translation
|
|
161
161
|
const messageKey = error.error?.messageKey;
|
|
162
|
-
const
|
|
162
|
+
const messageVariables = error.error?.messageVariables;
|
|
163
163
|
let detail;
|
|
164
164
|
if (messageKey && translateAdapter) {
|
|
165
165
|
// Use translated message if available
|
|
166
|
-
detail = translateAdapter.translate(messageKey,
|
|
166
|
+
detail = translateAdapter.translate(messageKey, messageVariables);
|
|
167
167
|
}
|
|
168
168
|
else if (typeof error.error?.message === 'string') {
|
|
169
169
|
detail = error.error.message;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flusys-ng-core.mjs","sources":["../../../projects/ng-core/services/api-loader.service.ts","../../../projects/ng-core/components/lib-app-config/lib-app-config.component.ts","../../../projects/ng-core/components/lib-app-config/lib-app-config.component.html","../../../projects/ng-core/interceptors/api-loader.interceptor.ts","../../../projects/ng-core/interfaces/translate-adapter.interface.ts","../../../projects/ng-core/interceptors/error-catching.interceptor.ts","../../../projects/ng-core/interfaces/app-config.interface.ts","../../../projects/ng-core/services/base-api.service.ts","../../../projects/ng-core/public-api.ts","../../../projects/ng-core/flusys-ng-core.ts"],"sourcesContent":["import { computed, Injectable, Signal, signal } from '@angular/core';\n\n@Injectable({ providedIn: 'root' })\nexport class ApiLoaderService {\n private readonly loaders = signal<Record<string, number>>({});\n readonly DEFAULT_TAG = 'default';\n\n // Global loading state\n readonly show: Signal<boolean> = computed(() => {\n const loaders = this.loaders();\n return Object.values(loaders).some((count) => count > 0);\n });\n\n // Get specific loader as a signal\n getLoader(name: string): Signal<boolean> {\n return computed(() => (this.loaders()[name] ?? 0) > 0);\n }\n\n increase(name: string = this.DEFAULT_TAG): void {\n this.loaders.update((loaders) => ({\n ...loaders,\n [name]: (loaders[name] ?? 0) + 1,\n }));\n }\n\n decrease(name: string = this.DEFAULT_TAG): void {\n this.loaders.update((loaders) => {\n const current = loaders[name] ?? 0;\n return { ...loaders, [name]: Math.max(0, current - 1) };\n });\n }\n}\n","import { Component, inject } from '@angular/core';\nimport { ToastModule } from 'primeng/toast';\nimport { ConfirmDialog } from 'primeng/confirmdialog';\nimport { ApiLoaderService } from '../../services/api-loader.service';\nimport { ProgressBarModule } from 'primeng/progressbar';\n\n/**\n * Root configuration component providing global UI elements.\n * Include once at app root for global toast, confirm dialog, and loader.\n */\n@Component({\n selector: 'lib-app-config',\n imports: [ConfirmDialog, ProgressBarModule, ToastModule],\n templateUrl: './lib-app-config.component.html',\n styleUrl: './lib-app-config.component.scss',\n})\nexport class LibAppConfigComponent {\n readonly apiLoaderService = inject(ApiLoaderService);\n}\n","@if (apiLoaderService.show()) {\n<p-progressBar mode=\"indeterminate\" />\n}\n<p-toast />\n<p-confirmdialog />\n","import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { ApiLoaderService } from '../services/api-loader.service';\nimport { Observable } from 'rxjs';\nimport { finalize } from 'rxjs/operators';\n\n/**\n * Interceptor that tracks loading state for HTTP requests.\n * Uses 'x-loader-tag' header to group related requests.\n *\n * @example\n * ```typescript\n * http.get('/api/users', {\n * headers: new HttpHeaders({ 'x-loader-tag': 'users-list' })\n * });\n * // In template: apiLoaderService.getLoader('users-list')\n * ```\n */\nexport function apiLoaderInterceptor(\n req: HttpRequest<unknown>,\n next: HttpHandlerFn\n): Observable<HttpEvent<unknown>> {\n const loaderService = inject(ApiLoaderService);\n const loaderTag = req.headers.get('x-loader-tag') ?? loaderService.DEFAULT_TAG;\n\n loaderService.increase(loaderTag);\n\n const cleanReq = req.clone({\n headers: req.headers.delete('x-loader-tag'),\n });\n\n return next(cleanReq).pipe(\n finalize(() => loaderService.decrease(loaderTag))\n );\n}\n","import { InjectionToken, Signal } from '@angular/core';\n\nexport interface ITranslateAdapter {\n /** Current language code signal - read this to create reactive dependency */\n readonly languageCode: Signal<string>;\n translate(key: string, params?: Record<string, string | number>): string;\n}\n\nexport const TRANSLATE_ADAPTER = new InjectionToken<ITranslateAdapter>('TRANSLATE_ADAPTER');\n\n/** Registry for fallback messages when localization is not active */\nexport const FALLBACK_MESSAGES_REGISTRY = new InjectionToken<Record<string, string>>(\n 'FALLBACK_MESSAGES_REGISTRY',\n);\n","import {\n HttpErrorResponse,\n HttpEvent,\n HttpHandlerFn,\n HttpRequest,\n HttpContextToken,\n} from '@angular/common/http';\nimport { inject, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { MessageService } from 'primeng/api';\nimport { catchError, Observable, throwError } from 'rxjs';\nimport { ITranslateAdapter, TRANSLATE_ADAPTER } from '../interfaces/translate-adapter.interface';\n\n/**\n * HTTP context token to skip global error toast.\n * Use when component handles error display locally.\n *\n * @example\n * ```typescript\n * this.http.post(url, data, {\n * context: new HttpContext().set(SKIP_ERROR_TOAST, true)\n * });\n * ```\n */\nexport const SKIP_ERROR_TOAST = new HttpContextToken<boolean>(() => false);\n\n/**\n * Error Catching Interceptor\n * Displays error messages for failed HTTP requests using backend response.\n *\n * Features:\n * - Uses backend's error title (error.error.error) as summary\n * - Uses backend's error message (error.error.message) as detail\n * - Falls back to sensible defaults based on status code\n * - Skip with SKIP_ERROR_TOAST context token for local handling\n *\n * NOTE: This interceptor only handles error display.\n * For 401 handling and token refresh, use tokenRefreshInterceptor from @flusys/ng-auth.\n *\n * @example\n * ```typescript\n * // In app.config.ts\n * provideHttpClient(\n * withFetch(),\n * withInterceptors([\n * authInterceptor, // from @flusys/ng-auth\n * tokenRefreshInterceptor, // from @flusys/ng-auth\n * errorCatchingInterceptor, // from @flusys/ng-core\n * apiLoaderInterceptor, // from @flusys/ng-core\n * ])\n * )\n * ```\n */\nexport function errorCatchingInterceptor(\n req: HttpRequest<unknown>,\n next: HttpHandlerFn\n): Observable<HttpEvent<unknown>> {\n const messageService = inject(MessageService);\n const platformId = inject(PLATFORM_ID);\n const translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });\n const isBrowser = isPlatformBrowser(platformId);\n\n // Skip error display for check-login (expected to fail when not logged in)\n const isCheckLogin = req.url.includes('/check-login');\n // Skip if component handles error locally\n const skipErrorToast = req.context.get(SKIP_ERROR_TOAST);\n\n return next(req).pipe(\n catchError((error: HttpErrorResponse) => {\n // Skip 401 errors - handled by tokenRefreshInterceptor in ng-auth\n // Skip 403 errors - handled by permission denied UI in components\n // Skip check-login errors - expected behavior\n // Skip if component handles error locally\n if (error.status === 401 || error.status === 403 || isCheckLogin || skipErrorToast) {\n return throwError(() => error);\n }\n\n // Display error message for other errors\n if (isBrowser) {\n const { summary, detail } = extractErrorInfo(error, translateAdapter);\n messageService.add({\n severity: 'error',\n summary,\n detail,\n });\n }\n\n return throwError(() => error);\n })\n );\n}\n\n/**\n * Extract error summary and detail from HTTP error response.\n * Uses backend response format: { message, messageKey, messageParams, error, statusCode }\n * If messageKey is present and translate adapter is available, translates the message.\n */\nfunction extractErrorInfo(\n error: HttpErrorResponse,\n translateAdapter: ITranslateAdapter | null\n): { summary: string; detail: string } {\n // Check if we have a messageKey for translation\n const messageKey = error.error?.messageKey;\n const messageParams = error.error?.messageParams ?? error.error?.messageVariables;\n\n let detail: string;\n if (messageKey && translateAdapter) {\n // Use translated message if available\n detail = translateAdapter.translate(messageKey, messageParams);\n } else if (typeof error.error?.message === 'string') {\n detail = error.error.message;\n } else if (Array.isArray(error.error?.message)) {\n // NestJS validation errors come as array\n detail = error.error.message[0];\n } else if (error.message) {\n detail = error.message;\n } else {\n detail = translateAdapter\n ? translateAdapter.translate('shared.unexpected.error')\n : 'shared.unexpected.error';\n }\n\n // Extract summary/title from backend response or use default based on status\n let summary: string;\n if (typeof error.error?.error === 'string') {\n // Backend sends error type (e.g., \"Not Found\", \"Bad Request\", \"Validation Error\")\n summary = error.error.error;\n } else {\n // Fallback to status-based title\n summary = getDefaultSummary(error.status, translateAdapter);\n }\n\n return { summary, detail };\n}\n\n/**\n * Get default error summary based on HTTP status code.\n * Uses TRANSLATE_ADAPTER for localization when available.\n */\nfunction getDefaultSummary(status: number, translateAdapter: ITranslateAdapter | null): string {\n const keyMap: Record<number, string> = {\n 400: 'shared.error.bad.request',\n 404: 'shared.error.not.found',\n 409: 'shared.error.conflict',\n 422: 'shared.error.validation.error',\n 500: 'shared.error.server.error',\n 502: 'shared.error.service.unavailable',\n 503: 'shared.error.service.unavailable',\n 504: 'shared.error.service.unavailable',\n };\n\n const key = keyMap[status] || 'shared.error';\n\n if (translateAdapter) {\n return translateAdapter.translate(key);\n }\n\n // Fallback to key when no translator available\n return key;\n}\n","import { InjectionToken } from '@angular/core';\n\nexport type PermissionMode = 'rbac' | 'direct' | 'full';\n\nexport interface IServiceConfig {\n baseUrl: string;\n enabled: boolean;\n}\n\nexport interface IStorageServiceConfig extends IServiceConfig {\n /** Default storage config ID (falls back to company/branch default if not set) */\n defaultStorageConfigId?: string;\n}\n\nexport interface INotificationServiceConfig extends IServiceConfig {\n /** WebSocket URL (defaults to baseUrl if not set) */\n socketUrl?: string;\n}\n\nexport interface IAuthorConfig {\n name: string;\n url: string;\n}\n\nexport interface IRegistrationDefaults {\n companySlug?: string;\n branchSlug?: string;\n}\n\nexport interface IAppConfig {\n appName: string;\n appLogo: string;\n author?: IAuthorConfig;\n apiBaseUrl: string;\n enableCompanyFeature: boolean;\n /** @default true */\n enableSignUp?: boolean;\n registrationDefaults?: IRegistrationDefaults;\n services: {\n auth: IServiceConfig;\n administration: IServiceConfig;\n iam: IServiceConfig;\n storage: IStorageServiceConfig;\n formBuilder: IServiceConfig;\n email: IServiceConfig;\n eventManager: IServiceConfig;\n notification?: INotificationServiceConfig;\n };\n multiTenant: {\n enabled: boolean;\n tenantHeader: string;\n tenantId?: string;\n };\n /** 'rbac' | 'direct' | 'full' (default: 'full') */\n permissionMode?: PermissionMode;\n}\n\nexport const APP_CONFIG = new InjectionToken<IAppConfig>('APP_CONFIG');\n\nexport function getDatabaseMode(config: IAppConfig): 'single' | 'multi-tenant' {\n return config.multiTenant.enabled ? 'multi-tenant' : 'single';\n}\n\nexport function isServiceEnabled(\n config: IAppConfig,\n serviceName: 'auth' | 'iam' | 'storage' | (string & {}),\n): boolean {\n const service = config.services[serviceName as keyof typeof config.services];\n return service?.enabled ?? false;\n}\n\nexport function isFeatureEnabled(\n config: IAppConfig,\n feature:\n | 'auth'\n | 'administration'\n | 'iam'\n | 'storage'\n | 'formBuilder'\n | 'email'\n | 'eventManager'\n | 'notification',\n): boolean {\n return isServiceEnabled(config, feature);\n}\n\nexport function getServiceUrl(\n config: IAppConfig,\n serviceName:\n | 'auth'\n | 'administration'\n | 'iam'\n | 'storage'\n | 'formBuilder'\n | 'email'\n | 'eventManager'\n | 'notification'\n | (string & {}),\n): string {\n if (isServiceEnabled(config, serviceName)) {\n const service =\n config.services[serviceName as keyof typeof config.services];\n return service?.baseUrl ?? config.apiBaseUrl;\n }\n return config.apiBaseUrl;\n}\n\nexport function isCompanyFeatureEnabled(config: IAppConfig): boolean {\n return config.enableCompanyFeature ?? false;\n}\n\nexport function isStorageFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'storage');\n}\n\nexport function isFormBuilderFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'formBuilder');\n}\n\nexport function isEmailFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'email');\n}\n\nexport function isEventManagerFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'eventManager');\n}\n\nexport function isNotificationFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'notification');\n}\n\nexport function getNotificationSocketUrl(config: IAppConfig): string | null {\n const service = config.services.notification;\n if (!service?.enabled) return null;\n return service.socketUrl ?? service.baseUrl;\n}\n\nexport function getPermissionMode(config: IAppConfig): PermissionMode {\n return config.permissionMode ?? 'full';\n}\n\nexport function getDefaultStorageConfigId(\n config: IAppConfig,\n): string | undefined {\n return config.services.storage?.defaultStorageConfigId;\n}\n\nexport function isSignUpEnabled(config: IAppConfig): boolean {\n return config.enableSignUp ?? true;\n}\n\nexport function getDefaultCompanySlug(config: IAppConfig): string | undefined {\n return config.registrationDefaults?.companySlug;\n}\n\nexport function getDefaultBranchSlug(config: IAppConfig): string | undefined {\n return config.registrationDefaults?.branchSlug;\n}\n\nexport function getRegistrationDefaults(\n config: IAppConfig,\n): IRegistrationDefaults | undefined {\n return config.registrationDefaults;\n}\n\nexport const DEFAULT_APP_NAME = 'FLUSYS';\nexport const DEFAULT_AUTHOR: IAuthorConfig = {\n name: 'Flusys',\n url: 'https://flusys.vercel.app',\n};\n","import { inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { APP_CONFIG, getServiceUrl } from '../interfaces/app-config.interface';\n\n/**\n * Base API Service\n * Provides service-specific base URL resolution\n *\n * Note: This project uses POST-only RPC - all endpoints use POST, not REST verbs.\n *\n * Usage:\n * ```typescript\n * @Injectable({ providedIn: 'root' })\n * export class MyService extends BaseApiService {\n * constructor() {\n * super('my-service'); // Service name from config\n * }\n *\n * getData() {\n * return this.http.post(`${this.baseUrl}/data/get`, {});\n * }\n * }\n * ```\n */\nexport abstract class BaseApiService {\n protected readonly http = inject(HttpClient);\n protected readonly appConfig = inject(APP_CONFIG);\n protected readonly baseUrl: string;\n\n /**\n * @param serviceName - Service name from config (e.g., 'auth', 'iam', 'storage')\n */\n constructor(serviceName: string) {\n this.baseUrl = getServiceUrl(this.appConfig, serviceName);\n }\n\n /**\n * Get full URL for an endpoint\n * @param endpoint - API endpoint (e.g., '/users/get-all')\n * @returns Full URL (e.g., 'http://localhost:2002/users/get-all')\n */\n protected getUrl(endpoint: string): string {\n // Remove leading slash if present to avoid double slashes\n const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;\n return `${this.baseUrl}/${cleanEndpoint}`;\n }\n}\n","// Components\nexport * from './components/public-api';\n\n// Interceptors\nexport * from './interceptors/public-api';\n\n// Interfaces\nexport * from './interfaces/public-api';\n\n// Services\nexport * from './services/public-api';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"flusys-ng-core.mjs","sources":["../../../projects/ng-core/services/api-loader.service.ts","../../../projects/ng-core/components/lib-app-config/lib-app-config.component.ts","../../../projects/ng-core/components/lib-app-config/lib-app-config.component.html","../../../projects/ng-core/interceptors/api-loader.interceptor.ts","../../../projects/ng-core/interfaces/translate-adapter.interface.ts","../../../projects/ng-core/interceptors/error-catching.interceptor.ts","../../../projects/ng-core/interfaces/app-config.interface.ts","../../../projects/ng-core/services/base-api.service.ts","../../../projects/ng-core/public-api.ts","../../../projects/ng-core/flusys-ng-core.ts"],"sourcesContent":["import { computed, Injectable, Signal, signal } from '@angular/core';\n\n@Injectable({ providedIn: 'root' })\nexport class ApiLoaderService {\n private readonly loaders = signal<Record<string, number>>({});\n readonly DEFAULT_TAG = 'default';\n\n // Global loading state\n readonly show: Signal<boolean> = computed(() => {\n const loaders = this.loaders();\n return Object.values(loaders).some((count) => count > 0);\n });\n\n // Get specific loader as a signal\n getLoader(name: string): Signal<boolean> {\n return computed(() => (this.loaders()[name] ?? 0) > 0);\n }\n\n increase(name: string = this.DEFAULT_TAG): void {\n this.loaders.update((loaders) => ({\n ...loaders,\n [name]: (loaders[name] ?? 0) + 1,\n }));\n }\n\n decrease(name: string = this.DEFAULT_TAG): void {\n this.loaders.update((loaders) => {\n const current = loaders[name] ?? 0;\n return { ...loaders, [name]: Math.max(0, current - 1) };\n });\n }\n}\n","import { Component, inject } from '@angular/core';\nimport { ToastModule } from 'primeng/toast';\nimport { ConfirmDialog } from 'primeng/confirmdialog';\nimport { ApiLoaderService } from '../../services/api-loader.service';\nimport { ProgressBarModule } from 'primeng/progressbar';\n\n/**\n * Root configuration component providing global UI elements.\n * Include once at app root for global toast, confirm dialog, and loader.\n */\n@Component({\n selector: 'lib-app-config',\n imports: [ConfirmDialog, ProgressBarModule, ToastModule],\n templateUrl: './lib-app-config.component.html',\n styleUrl: './lib-app-config.component.scss',\n})\nexport class LibAppConfigComponent {\n readonly apiLoaderService = inject(ApiLoaderService);\n}\n","@if (apiLoaderService.show()) {\n<p-progressBar mode=\"indeterminate\" />\n}\n<p-toast />\n<p-confirmdialog />\n","import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { ApiLoaderService } from '../services/api-loader.service';\nimport { Observable } from 'rxjs';\nimport { finalize } from 'rxjs/operators';\n\n/**\n * Interceptor that tracks loading state for HTTP requests.\n * Uses 'x-loader-tag' header to group related requests.\n *\n * @example\n * ```typescript\n * http.get('/api/users', {\n * headers: new HttpHeaders({ 'x-loader-tag': 'users-list' })\n * });\n * // In template: apiLoaderService.getLoader('users-list')\n * ```\n */\nexport function apiLoaderInterceptor(\n req: HttpRequest<unknown>,\n next: HttpHandlerFn\n): Observable<HttpEvent<unknown>> {\n const loaderService = inject(ApiLoaderService);\n const loaderTag = req.headers.get('x-loader-tag') ?? loaderService.DEFAULT_TAG;\n\n loaderService.increase(loaderTag);\n\n const cleanReq = req.clone({\n headers: req.headers.delete('x-loader-tag'),\n });\n\n return next(cleanReq).pipe(\n finalize(() => loaderService.decrease(loaderTag))\n );\n}\n","import { InjectionToken, Signal } from '@angular/core';\n\nexport interface ITranslateAdapter {\n /** Current language code signal - read this to create reactive dependency */\n readonly languageCode: Signal<string>;\n translate(key: string, params?: Record<string, unknown>): string;\n}\n\nexport const TRANSLATE_ADAPTER = new InjectionToken<ITranslateAdapter>(\n 'TRANSLATE_ADAPTER',\n);\n\n/** Registry for fallback messages when localization is not active */\nexport const FALLBACK_MESSAGES_REGISTRY = new InjectionToken<\n Record<string, string>\n>('FALLBACK_MESSAGES_REGISTRY');\n","import {\n HttpErrorResponse,\n HttpEvent,\n HttpHandlerFn,\n HttpRequest,\n HttpContextToken,\n} from '@angular/common/http';\nimport { inject, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { MessageService } from 'primeng/api';\nimport { catchError, Observable, throwError } from 'rxjs';\nimport { ITranslateAdapter, TRANSLATE_ADAPTER } from '../interfaces/translate-adapter.interface';\n\n/**\n * HTTP context token to skip global error toast.\n * Use when component handles error display locally.\n *\n * @example\n * ```typescript\n * this.http.post(url, data, {\n * context: new HttpContext().set(SKIP_ERROR_TOAST, true)\n * });\n * ```\n */\nexport const SKIP_ERROR_TOAST = new HttpContextToken<boolean>(() => false);\n\n/**\n * Error Catching Interceptor\n * Displays error messages for failed HTTP requests using backend response.\n *\n * Features:\n * - Uses backend's error title (error.error.error) as summary\n * - Uses backend's error message (error.error.message) as detail\n * - Falls back to sensible defaults based on status code\n * - Skip with SKIP_ERROR_TOAST context token for local handling\n *\n * NOTE: This interceptor only handles error display.\n * For 401 handling and token refresh, use tokenRefreshInterceptor from @flusys/ng-auth.\n *\n * @example\n * ```typescript\n * // In app.config.ts\n * provideHttpClient(\n * withFetch(),\n * withInterceptors([\n * authInterceptor, // from @flusys/ng-auth\n * tokenRefreshInterceptor, // from @flusys/ng-auth\n * errorCatchingInterceptor, // from @flusys/ng-core\n * apiLoaderInterceptor, // from @flusys/ng-core\n * ])\n * )\n * ```\n */\nexport function errorCatchingInterceptor(\n req: HttpRequest<unknown>,\n next: HttpHandlerFn\n): Observable<HttpEvent<unknown>> {\n const messageService = inject(MessageService);\n const platformId = inject(PLATFORM_ID);\n const translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });\n const isBrowser = isPlatformBrowser(platformId);\n\n // Skip error display for check-login (expected to fail when not logged in)\n const isCheckLogin = req.url.includes('/check-login');\n // Skip if component handles error locally\n const skipErrorToast = req.context.get(SKIP_ERROR_TOAST);\n\n return next(req).pipe(\n catchError((error: HttpErrorResponse) => {\n // Skip 401 errors - handled by tokenRefreshInterceptor in ng-auth\n // Skip 403 errors - handled by permission denied UI in components\n // Skip check-login errors - expected behavior\n // Skip if component handles error locally\n if (error.status === 401 || error.status === 403 || isCheckLogin || skipErrorToast) {\n return throwError(() => error);\n }\n\n // Display error message for other errors\n if (isBrowser) {\n const { summary, detail } = extractErrorInfo(error, translateAdapter);\n messageService.add({\n severity: 'error',\n summary,\n detail,\n });\n }\n\n return throwError(() => error);\n })\n );\n}\n\n/**\n * Extract error summary and detail from HTTP error response.\n * Uses backend response format: { message, messageKey, messageVariables, error, statusCode }\n * If messageKey is present and translate adapter is available, translates the message.\n */\nfunction extractErrorInfo(\n error: HttpErrorResponse,\n translateAdapter: ITranslateAdapter | null\n): { summary: string; detail: string } {\n // Check if we have a messageKey for translation\n const messageKey = error.error?.messageKey;\n const messageVariables = error.error?.messageVariables;\n\n let detail: string;\n if (messageKey && translateAdapter) {\n // Use translated message if available\n detail = translateAdapter.translate(messageKey, messageVariables);\n } else if (typeof error.error?.message === 'string') {\n detail = error.error.message;\n } else if (Array.isArray(error.error?.message)) {\n // NestJS validation errors come as array\n detail = error.error.message[0];\n } else if (error.message) {\n detail = error.message;\n } else {\n detail = translateAdapter\n ? translateAdapter.translate('shared.unexpected.error')\n : 'shared.unexpected.error';\n }\n\n // Extract summary/title from backend response or use default based on status\n let summary: string;\n if (typeof error.error?.error === 'string') {\n // Backend sends error type (e.g., \"Not Found\", \"Bad Request\", \"Validation Error\")\n summary = error.error.error;\n } else {\n // Fallback to status-based title\n summary = getDefaultSummary(error.status, translateAdapter);\n }\n\n return { summary, detail };\n}\n\n/**\n * Get default error summary based on HTTP status code.\n * Uses TRANSLATE_ADAPTER for localization when available.\n */\nfunction getDefaultSummary(status: number, translateAdapter: ITranslateAdapter | null): string {\n const keyMap: Record<number, string> = {\n 400: 'shared.error.bad.request',\n 404: 'shared.error.not.found',\n 409: 'shared.error.conflict',\n 422: 'shared.error.validation.error',\n 500: 'shared.error.server.error',\n 502: 'shared.error.service.unavailable',\n 503: 'shared.error.service.unavailable',\n 504: 'shared.error.service.unavailable',\n };\n\n const key = keyMap[status] || 'shared.error';\n\n if (translateAdapter) {\n return translateAdapter.translate(key);\n }\n\n // Fallback to key when no translator available\n return key;\n}\n","import { InjectionToken } from '@angular/core';\n\nexport type PermissionMode = 'rbac' | 'direct' | 'full';\n\nexport interface IServiceConfig {\n baseUrl: string;\n enabled: boolean;\n}\n\nexport interface IStorageServiceConfig extends IServiceConfig {\n /** Default storage config ID (falls back to company/branch default if not set) */\n defaultStorageConfigId?: string;\n}\n\nexport interface INotificationServiceConfig extends IServiceConfig {\n /** WebSocket URL (defaults to baseUrl if not set) */\n socketUrl?: string;\n}\n\nexport interface IAuthorConfig {\n name: string;\n url: string;\n}\n\nexport interface IRegistrationDefaults {\n companySlug?: string;\n branchSlug?: string;\n}\n\nexport interface IAppConfig {\n appName: string;\n appLogo: string;\n author?: IAuthorConfig;\n apiBaseUrl: string;\n enableCompanyFeature: boolean;\n /** @default true */\n enableSignUp?: boolean;\n registrationDefaults?: IRegistrationDefaults;\n services: {\n auth: IServiceConfig;\n administration: IServiceConfig;\n iam: IServiceConfig;\n storage: IStorageServiceConfig;\n formBuilder: IServiceConfig;\n email: IServiceConfig;\n eventManager: IServiceConfig;\n notification?: INotificationServiceConfig;\n localization?: IServiceConfig;\n };\n multiTenant: {\n enabled: boolean;\n tenantHeader: string;\n tenantId?: string;\n };\n /** 'rbac' | 'direct' | 'full' (default: 'full') */\n permissionMode?: PermissionMode;\n}\n\nexport const APP_CONFIG = new InjectionToken<IAppConfig>('APP_CONFIG');\n\nexport function getDatabaseMode(config: IAppConfig): 'single' | 'multi-tenant' {\n return config.multiTenant.enabled ? 'multi-tenant' : 'single';\n}\n\nexport function isServiceEnabled(\n config: IAppConfig,\n serviceName: 'auth' | 'iam' | 'storage' | (string & {}),\n): boolean {\n const service = config.services[serviceName as keyof typeof config.services];\n return service?.enabled ?? false;\n}\n\nexport function isFeatureEnabled(\n config: IAppConfig,\n feature:\n | 'auth'\n | 'administration'\n | 'iam'\n | 'storage'\n | 'formBuilder'\n | 'email'\n | 'eventManager'\n | 'notification',\n): boolean {\n return isServiceEnabled(config, feature);\n}\n\nexport function getServiceUrl(\n config: IAppConfig,\n serviceName:\n | 'auth'\n | 'administration'\n | 'iam'\n | 'storage'\n | 'formBuilder'\n | 'email'\n | 'eventManager'\n | 'notification'\n | (string & {}),\n): string {\n if (isServiceEnabled(config, serviceName)) {\n const service =\n config.services[serviceName as keyof typeof config.services];\n return service?.baseUrl ?? config.apiBaseUrl;\n }\n return config.apiBaseUrl;\n}\n\nexport function isCompanyFeatureEnabled(config: IAppConfig): boolean {\n return config.enableCompanyFeature ?? false;\n}\n\nexport function isStorageFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'storage');\n}\n\nexport function isFormBuilderFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'formBuilder');\n}\n\nexport function isEmailFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'email');\n}\n\nexport function isEventManagerFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'eventManager');\n}\n\nexport function isNotificationFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'notification');\n}\n\nexport function getNotificationSocketUrl(config: IAppConfig): string | null {\n const service = config.services.notification;\n if (!service?.enabled) return null;\n return service.socketUrl ?? service.baseUrl;\n}\n\nexport function getPermissionMode(config: IAppConfig): PermissionMode {\n return config.permissionMode ?? 'full';\n}\n\nexport function getDefaultStorageConfigId(\n config: IAppConfig,\n): string | undefined {\n return config.services.storage?.defaultStorageConfigId;\n}\n\nexport function isSignUpEnabled(config: IAppConfig): boolean {\n return config.enableSignUp ?? true;\n}\n\nexport function getDefaultCompanySlug(config: IAppConfig): string | undefined {\n return config.registrationDefaults?.companySlug;\n}\n\nexport function getDefaultBranchSlug(config: IAppConfig): string | undefined {\n return config.registrationDefaults?.branchSlug;\n}\n\nexport function getRegistrationDefaults(\n config: IAppConfig,\n): IRegistrationDefaults | undefined {\n return config.registrationDefaults;\n}\n\nexport const DEFAULT_APP_NAME = 'FLUSYS';\nexport const DEFAULT_AUTHOR: IAuthorConfig = {\n name: 'Flusys',\n url: 'https://flusys.vercel.app',\n};\n","import { inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { APP_CONFIG, getServiceUrl } from '../interfaces/app-config.interface';\n\n/**\n * Base API Service\n * Provides service-specific base URL resolution\n *\n * Note: This project uses POST-only RPC - all endpoints use POST, not REST verbs.\n *\n * Usage:\n * ```typescript\n * @Injectable({ providedIn: 'root' })\n * export class MyService extends BaseApiService {\n * constructor() {\n * super('my-service'); // Service name from config\n * }\n *\n * getData() {\n * return this.http.post(`${this.baseUrl}/data/get`, {});\n * }\n * }\n * ```\n */\nexport abstract class BaseApiService {\n protected readonly http = inject(HttpClient);\n protected readonly appConfig = inject(APP_CONFIG);\n protected readonly baseUrl: string;\n\n /**\n * @param serviceName - Service name from config (e.g., 'auth', 'iam', 'storage')\n */\n constructor(serviceName: string) {\n this.baseUrl = getServiceUrl(this.appConfig, serviceName);\n }\n\n /**\n * Get full URL for an endpoint\n * @param endpoint - API endpoint (e.g., '/users/get-all')\n * @returns Full URL (e.g., 'http://localhost:2002/users/get-all')\n */\n protected getUrl(endpoint: string): string {\n // Remove leading slash if present to avoid double slashes\n const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;\n return `${this.baseUrl}/${cleanEndpoint}`;\n }\n}\n","// Components\nexport * from './components/public-api';\n\n// Interceptors\nexport * from './interceptors/public-api';\n\n// Interfaces\nexport * from './interfaces/public-api';\n\n// Services\nexport * from './services/public-api';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -119,6 +119,7 @@ interface IAppConfig {
|
|
|
119
119
|
email: IServiceConfig;
|
|
120
120
|
eventManager: IServiceConfig;
|
|
121
121
|
notification?: INotificationServiceConfig;
|
|
122
|
+
localization?: IServiceConfig;
|
|
122
123
|
};
|
|
123
124
|
multiTenant: {
|
|
124
125
|
enabled: boolean;
|
|
@@ -152,7 +153,7 @@ declare const DEFAULT_AUTHOR: IAuthorConfig;
|
|
|
152
153
|
interface ITranslateAdapter {
|
|
153
154
|
/** Current language code signal - read this to create reactive dependency */
|
|
154
155
|
readonly languageCode: Signal<string>;
|
|
155
|
-
translate(key: string, params?: Record<string,
|
|
156
|
+
translate(key: string, params?: Record<string, unknown>): string;
|
|
156
157
|
}
|
|
157
158
|
declare const TRANSLATE_ADAPTER: InjectionToken<ITranslateAdapter>;
|
|
158
159
|
/** Registry for fallback messages when localization is not active */
|