@flusys/ng-core 4.0.1 → 4.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 +278 -233
- package/fesm2022/flusys-ng-core.mjs.map +1 -1
- package/package.json +1 -1
- package/types/flusys-ng-core.d.ts +1 -0
package/README.md
CHANGED
|
@@ -1,341 +1,386 @@
|
|
|
1
|
-
# @flusys/ng-core
|
|
1
|
+
# @flusys/ng-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Foundation library for the FLUSYS Angular platform — configuration injection, base HTTP services, API loaders, and HTTP interceptors.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@flusys/ng-core)
|
|
6
|
+
[](https://angular.io)
|
|
7
|
+
[](https://www.typescriptlang.org)
|
|
8
|
+
[](LICENSE)
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
---
|
|
8
11
|
|
|
9
|
-
##
|
|
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)
|
|
10
30
|
|
|
11
|
-
|
|
12
|
-
- **Version:** 4.0.1
|
|
13
|
-
- **Peer Dependencies:** `@angular/*: >=21.0.0`, `primeng: >=21.0.0`
|
|
14
|
-
- **Build Command:** `npm run build:ng-core`
|
|
31
|
+
---
|
|
15
32
|
|
|
16
|
-
##
|
|
33
|
+
## Overview
|
|
17
34
|
|
|
18
|
-
|
|
19
|
-
ng-core/
|
|
20
|
-
├── components/
|
|
21
|
-
│ └── lib-app-config/ # Global toast, confirm, loader
|
|
22
|
-
├── interceptors/
|
|
23
|
-
│ ├── api-loader.interceptor.ts
|
|
24
|
-
│ └── error-catching.interceptor.ts
|
|
25
|
-
├── interfaces/
|
|
26
|
-
│ └── app-config.interface.ts # IAppConfig, helpers, constants
|
|
27
|
-
├── services/
|
|
28
|
-
│ ├── api-loader.service.ts
|
|
29
|
-
│ └── base-api.service.ts
|
|
30
|
-
└── public-api.ts
|
|
31
|
-
```
|
|
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:
|
|
32
36
|
|
|
33
|
-
|
|
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
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
// Components
|
|
37
|
-
export { LibAppConfigComponent } from './components/public-api';
|
|
38
|
-
|
|
39
|
-
// Services
|
|
40
|
-
export { ApiLoaderService, BaseApiService } from './services/public-api';
|
|
41
|
-
|
|
42
|
-
// Interceptors
|
|
43
|
-
export { apiLoaderInterceptor, errorCatchingInterceptor, SKIP_ERROR_TOAST } from './interceptors/public-api';
|
|
44
|
-
|
|
45
|
-
// Interfaces & Types
|
|
46
|
-
export {
|
|
47
|
-
APP_CONFIG, IAppConfig, IServiceConfig, IStorageServiceConfig,
|
|
48
|
-
IAuthorConfig, IRegistrationDefaults, PermissionMode,
|
|
49
|
-
} from './interfaces/public-api';
|
|
50
|
-
|
|
51
|
-
// Helper Functions
|
|
52
|
-
export {
|
|
53
|
-
getDatabaseMode, isServiceEnabled, isFeatureEnabled, getServiceUrl,
|
|
54
|
-
isCompanyFeatureEnabled, isStorageFeatureEnabled, isFormBuilderFeatureEnabled,
|
|
55
|
-
isEmailFeatureEnabled, getPermissionMode, getDefaultStorageConfigId,
|
|
56
|
-
isSignUpEnabled, getDefaultCompanySlug, getDefaultBranchSlug, getRegistrationDefaults,
|
|
57
|
-
} from './interfaces/public-api';
|
|
58
|
-
|
|
59
|
-
// Constants
|
|
60
|
-
export { DEFAULT_APP_NAME, DEFAULT_AUTHOR } from './interfaces/public-api';
|
|
61
|
-
```
|
|
43
|
+
This package has **no FLUSYS dependencies** — it only depends on Angular core.
|
|
62
44
|
|
|
63
45
|
---
|
|
64
46
|
|
|
65
|
-
##
|
|
47
|
+
## Features
|
|
66
48
|
|
|
67
|
-
|
|
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
|
|
68
57
|
|
|
69
|
-
|
|
58
|
+
---
|
|
70
59
|
|
|
71
|
-
|
|
72
|
-
export type PermissionMode = 'rbac' | 'direct' | 'full';
|
|
60
|
+
## Compatibility
|
|
73
61
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
62
|
+
| Package | Version |
|
|
63
|
+
|---------|---------|
|
|
64
|
+
| Angular | 21+ |
|
|
65
|
+
| TypeScript | 5.x |
|
|
66
|
+
| RxJS | 7.x |
|
|
78
67
|
|
|
79
|
-
|
|
80
|
-
defaultStorageConfigId?: string; // Default storage config for uploads
|
|
81
|
-
}
|
|
68
|
+
---
|
|
82
69
|
|
|
83
|
-
|
|
70
|
+
## Installation
|
|
84
71
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
72
|
+
```bash
|
|
73
|
+
npm install @flusys/ng-core
|
|
74
|
+
```
|
|
89
75
|
|
|
90
|
-
|
|
91
|
-
appName: string;
|
|
92
|
-
author?: IAuthorConfig;
|
|
93
|
-
apiBaseUrl: string; // Default fallback URL
|
|
94
|
-
enableCompanyFeature: boolean; // Company selection/switching
|
|
95
|
-
enableSignUp?: boolean; // Registration enabled (default: true)
|
|
96
|
-
registrationDefaults?: IRegistrationDefaults;
|
|
76
|
+
---
|
|
97
77
|
|
|
98
|
-
|
|
99
|
-
auth: IServiceConfig;
|
|
100
|
-
administration: IServiceConfig;
|
|
101
|
-
iam: IServiceConfig;
|
|
102
|
-
storage: IStorageServiceConfig;
|
|
103
|
-
formBuilder: IServiceConfig;
|
|
104
|
-
email: IServiceConfig;
|
|
105
|
-
};
|
|
78
|
+
## Quick Start
|
|
106
79
|
|
|
107
|
-
|
|
108
|
-
enabled: boolean; // true = multi-tenant DB, false = single DB
|
|
109
|
-
tenantHeader: string; // Header name (e.g., 'x-tenant-id')
|
|
110
|
-
tenantId?: string;
|
|
111
|
-
};
|
|
80
|
+
### 1. Define App Config
|
|
112
81
|
|
|
113
|
-
|
|
114
|
-
|
|
82
|
+
```typescript
|
|
83
|
+
// environments/environment.ts
|
|
84
|
+
import { IAppConfig } from '@flusys/ng-core';
|
|
115
85
|
|
|
116
|
-
export const
|
|
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
|
+
};
|
|
117
109
|
```
|
|
118
110
|
|
|
119
|
-
###
|
|
120
|
-
|
|
121
|
-
| Function | Returns | Description |
|
|
122
|
-
|----------|---------|-------------|
|
|
123
|
-
| `getDatabaseMode(config)` | `'single' \| 'multi-tenant'` | Database mode from `multiTenant.enabled` |
|
|
124
|
-
| `isServiceEnabled(config, name)` | `boolean` | Check if service is enabled |
|
|
125
|
-
| `isFeatureEnabled(config, feature)` | `boolean` | Same as above with strict ServiceName type |
|
|
126
|
-
| `getServiceUrl(config, name)` | `string` | Service URL or fallback to `apiBaseUrl` |
|
|
127
|
-
| `isCompanyFeatureEnabled(config)` | `boolean` | Check `enableCompanyFeature` |
|
|
128
|
-
| `isStorageFeatureEnabled(config)` | `boolean` | Check `services.storage.enabled` |
|
|
129
|
-
| `isFormBuilderFeatureEnabled(config)` | `boolean` | Check `services.formBuilder.enabled` |
|
|
130
|
-
| `isEmailFeatureEnabled(config)` | `boolean` | Check `services.email.enabled` |
|
|
131
|
-
| `getPermissionMode(config)` | `PermissionMode` | Permission mode (default: `'full'`) |
|
|
132
|
-
| `getDefaultStorageConfigId(config)` | `string \| undefined` | Default storage config ID |
|
|
133
|
-
| `isSignUpEnabled(config)` | `boolean` | Sign-up enabled (default: `true`) |
|
|
134
|
-
| `getDefaultCompanySlug(config)` | `string \| undefined` | Registration default company |
|
|
135
|
-
| `getDefaultBranchSlug(config)` | `string \| undefined` | Registration default branch |
|
|
136
|
-
| `getRegistrationDefaults(config)` | `IRegistrationDefaults \| undefined` | Full registration defaults |
|
|
137
|
-
|
|
138
|
-
**ServiceName:** `'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email'`
|
|
139
|
-
|
|
140
|
-
### Constants
|
|
111
|
+
### 2. Provide Config in `app.config.ts`
|
|
141
112
|
|
|
142
113
|
```typescript
|
|
143
|
-
|
|
144
|
-
|
|
114
|
+
import { ApplicationConfig } from '@angular/core';
|
|
115
|
+
import { APP_CONFIG } from '@flusys/ng-core';
|
|
116
|
+
import { environment } from './environments/environment';
|
|
117
|
+
|
|
118
|
+
export const appConfig: ApplicationConfig = {
|
|
119
|
+
providers: [
|
|
120
|
+
{ provide: APP_CONFIG, useValue: environment },
|
|
121
|
+
// ... other providers
|
|
122
|
+
],
|
|
123
|
+
};
|
|
145
124
|
```
|
|
146
125
|
|
|
147
|
-
###
|
|
126
|
+
### 3. Inject Config Anywhere
|
|
148
127
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
| Single DB + Multi-Company | `true` | `false` | Enabled |
|
|
153
|
-
| Multi-Tenant | `true` | `true` | Enabled |
|
|
128
|
+
```typescript
|
|
129
|
+
import { inject } from '@angular/core';
|
|
130
|
+
import { APP_CONFIG, IAppConfig } from '@flusys/ng-core';
|
|
154
131
|
|
|
155
|
-
|
|
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
|
+
```
|
|
156
141
|
|
|
157
|
-
|
|
142
|
+
---
|
|
158
143
|
|
|
159
|
-
|
|
144
|
+
## Configuration Reference
|
|
160
145
|
|
|
161
|
-
###
|
|
146
|
+
### IAppConfig
|
|
162
147
|
|
|
163
|
-
|
|
148
|
+
Complete configuration interface for the FLUSYS platform:
|
|
164
149
|
|
|
165
150
|
```typescript
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
151
|
+
interface IAppConfig {
|
|
152
|
+
/** Application display name */
|
|
153
|
+
appName: string;
|
|
169
154
|
|
|
170
|
-
|
|
171
|
-
|
|
155
|
+
/** Backend API base URL */
|
|
156
|
+
apiBaseUrl: string;
|
|
172
157
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
```
|
|
158
|
+
/** Production mode flag */
|
|
159
|
+
production: boolean;
|
|
176
160
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
this.http.post('/api/users/get-all', filter, {
|
|
180
|
-
headers: new HttpHeaders({ 'x-loader-tag': 'users-list' })
|
|
181
|
-
});
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### errorCatchingInterceptor
|
|
161
|
+
/** Multi-tenant mode — isolates data per company */
|
|
162
|
+
multiTenant?: boolean;
|
|
185
163
|
|
|
186
|
-
|
|
164
|
+
/** IAM permission evaluation mode */
|
|
165
|
+
permissionMode?: 'RBAC' | 'DIRECT' | 'FULL';
|
|
187
166
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
+
```
|
|
194
189
|
|
|
195
|
-
|
|
190
|
+
### APP_CONFIG Token
|
|
196
191
|
|
|
197
|
-
**Skip Toast for Local Handling:**
|
|
198
192
|
```typescript
|
|
199
|
-
|
|
193
|
+
import { APP_CONFIG } from '@flusys/ng-core';
|
|
194
|
+
|
|
195
|
+
// In component/service
|
|
196
|
+
const config = inject<IAppConfig>(APP_CONFIG);
|
|
200
197
|
```
|
|
201
198
|
|
|
202
199
|
---
|
|
203
200
|
|
|
204
|
-
##
|
|
201
|
+
## Base Services
|
|
205
202
|
|
|
206
|
-
###
|
|
203
|
+
### BaseApiService
|
|
207
204
|
|
|
208
|
-
|
|
205
|
+
Typed HTTP client configured for POST-only RPC endpoints. All FLUSYS API services extend this class.
|
|
209
206
|
|
|
210
207
|
```typescript
|
|
208
|
+
import { BaseApiService, ISingleResponse, IListResponse } from '@flusys/ng-core';
|
|
209
|
+
|
|
211
210
|
@Injectable({ providedIn: 'root' })
|
|
212
|
-
export class
|
|
213
|
-
|
|
214
|
-
readonly DEFAULT_TAG = 'default';
|
|
211
|
+
export class ProductApiService extends BaseApiService {
|
|
212
|
+
protected resource = 'product';
|
|
215
213
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
214
|
+
getAll(params: IListParams): Observable<IListResponse<Product>> {
|
|
215
|
+
return this.post(`${this.resource}/get-all`, params);
|
|
216
|
+
}
|
|
219
217
|
|
|
220
|
-
|
|
221
|
-
return
|
|
218
|
+
getById(id: string): Observable<ISingleResponse<Product>> {
|
|
219
|
+
return this.post(`${this.resource}/get/${id}`, {});
|
|
222
220
|
}
|
|
223
221
|
|
|
224
|
-
|
|
225
|
-
|
|
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 });
|
|
232
|
+
}
|
|
226
233
|
}
|
|
227
234
|
```
|
|
228
235
|
|
|
229
|
-
**
|
|
230
|
-
```typescript
|
|
231
|
-
@if (apiLoaderService.show()) { <p-progressBar mode="indeterminate" /> }
|
|
232
|
-
@if (apiLoaderService.getLoader('users-list')()) { <p-skeleton /> }
|
|
233
|
-
```
|
|
236
|
+
**Key Methods:**
|
|
234
237
|
|
|
235
|
-
|
|
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`
|
|
249
|
+
|
|
250
|
+
### ApiLoaderService
|
|
236
251
|
|
|
237
|
-
|
|
252
|
+
Tracks concurrent HTTP requests to show/hide global loading indicators.
|
|
238
253
|
|
|
239
254
|
```typescript
|
|
240
|
-
|
|
241
|
-
protected readonly http = inject(HttpClient);
|
|
242
|
-
protected readonly appConfig = inject(APP_CONFIG);
|
|
243
|
-
protected readonly baseUrl: string;
|
|
255
|
+
import { ApiLoaderService } from '@flusys/ng-core';
|
|
244
256
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
257
|
+
@Component({ ... })
|
|
258
|
+
export class AppComponent {
|
|
259
|
+
private loader = inject(ApiLoaderService);
|
|
248
260
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return `${this.baseUrl}/${clean}`;
|
|
252
|
-
}
|
|
261
|
+
// Reactive loading state (signal)
|
|
262
|
+
isLoading = this.loader.isLoading;
|
|
253
263
|
}
|
|
254
264
|
```
|
|
255
265
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
@
|
|
259
|
-
|
|
260
|
-
constructor() { super('auth'); }
|
|
261
|
-
getAll(filter: any) { return this.http.post(this.getUrl('user/get-all'), filter); }
|
|
266
|
+
```html
|
|
267
|
+
<!-- Use in template -->
|
|
268
|
+
@if (isLoading()) {
|
|
269
|
+
<p-progressBar mode="indeterminate" />
|
|
262
270
|
}
|
|
263
271
|
```
|
|
264
272
|
|
|
265
|
-
|
|
273
|
+
**ApiLoaderService API:**
|
|
266
274
|
|
|
267
|
-
|
|
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 |
|
|
268
280
|
|
|
269
|
-
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## HTTP Interceptors
|
|
270
284
|
|
|
271
|
-
|
|
285
|
+
`@flusys/ng-core` provides base HTTP interceptors registered via `provideHttpClient`:
|
|
272
286
|
|
|
273
|
-
|
|
287
|
+
### Base URL Interceptor
|
|
274
288
|
|
|
275
|
-
|
|
289
|
+
Automatically prepends `APP_CONFIG.apiBaseUrl` to all relative HTTP requests.
|
|
276
290
|
|
|
277
|
-
**Usage:**
|
|
278
291
|
```typescript
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
imports: [LibAppConfigComponent, RouterOutlet],
|
|
282
|
-
template: `<lib-app-config /><router-outlet />`
|
|
283
|
-
})
|
|
284
|
-
export class AppComponent {}
|
|
292
|
+
// Absolute URLs are passed through unchanged
|
|
293
|
+
// Relative URLs like '/auth/login' become 'http://localhost:2002/auth/login'
|
|
285
294
|
```
|
|
286
295
|
|
|
287
|
-
|
|
296
|
+
### Error Normalizer Interceptor
|
|
288
297
|
|
|
289
|
-
|
|
298
|
+
Normalizes all HTTP error responses into a consistent `IErrorResponse` shape before they reach your services.
|
|
299
|
+
|
|
300
|
+
### Registering Interceptors
|
|
290
301
|
|
|
291
302
|
```typescript
|
|
292
|
-
|
|
293
|
-
import {
|
|
294
|
-
import { authInterceptor, tokenRefreshInterceptor } from '@flusys/ng-auth';
|
|
303
|
+
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
|
304
|
+
import { baseUrlInterceptor, errorNormalizerInterceptor } from '@flusys/ng-core';
|
|
295
305
|
|
|
296
306
|
export const appConfig: ApplicationConfig = {
|
|
297
307
|
providers: [
|
|
298
308
|
provideHttpClient(
|
|
299
|
-
withFetch(),
|
|
300
309
|
withInterceptors([
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
errorCatchingInterceptor, // ng-core: error toasts
|
|
304
|
-
apiLoaderInterceptor, // ng-core: loading state
|
|
310
|
+
baseUrlInterceptor,
|
|
311
|
+
errorNormalizerInterceptor,
|
|
305
312
|
])
|
|
306
313
|
),
|
|
307
|
-
|
|
308
|
-
]
|
|
314
|
+
],
|
|
309
315
|
};
|
|
310
316
|
```
|
|
311
317
|
|
|
312
318
|
---
|
|
313
319
|
|
|
314
|
-
##
|
|
320
|
+
## Helper Functions
|
|
315
321
|
|
|
316
|
-
|
|
317
|
-
2. **Match backend mode** - `multiTenant.enabled` should align with FLUSYS_NEST
|
|
318
|
-
3. **Never import other @flusys packages** in ng-core
|
|
319
|
-
4. **Use `BaseApiService`** for all API services
|
|
320
|
-
5. **Use tagged loaders** for granular loading states
|
|
322
|
+
Feature detection helpers that read from `APP_CONFIG`:
|
|
321
323
|
|
|
322
|
-
|
|
324
|
+
```typescript
|
|
325
|
+
import {
|
|
326
|
+
isAuthEnabled,
|
|
327
|
+
isIamEnabled,
|
|
328
|
+
isStorageEnabled,
|
|
329
|
+
isSignUpEnabled,
|
|
330
|
+
isEmailVerificationEnabled,
|
|
331
|
+
isCompanySelectionEnabled,
|
|
332
|
+
isLocalizationEnabled,
|
|
333
|
+
isNotificationEnabled,
|
|
334
|
+
} from '@flusys/ng-core';
|
|
335
|
+
|
|
336
|
+
// Usage in a service or guard
|
|
337
|
+
const authOn = isAuthEnabled(config); // boolean
|
|
338
|
+
const iamOn = isIamEnabled(config); // boolean
|
|
339
|
+
const storageOn = isStorageEnabled(config); // boolean
|
|
340
|
+
const signUpOn = isSignUpEnabled(config); // boolean
|
|
341
|
+
```
|
|
323
342
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
|
329
|
-
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Exported Tokens
|
|
346
|
+
|
|
347
|
+
| Token | Type | Description |
|
|
348
|
+
|-------|------|-------------|
|
|
349
|
+
| `APP_CONFIG` | `InjectionToken<IAppConfig>` | Global app configuration |
|
|
350
|
+
| `TRANSLATE_ADAPTER` | `InjectionToken<ITranslateAdapter>` | Localization adapter interface |
|
|
330
351
|
|
|
331
352
|
---
|
|
332
353
|
|
|
333
|
-
##
|
|
354
|
+
## Troubleshooting
|
|
355
|
+
|
|
356
|
+
**`NullInjectorError: No provider for APP_CONFIG`**
|
|
357
|
+
|
|
358
|
+
You forgot to provide `APP_CONFIG` in `app.config.ts`:
|
|
334
359
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
360
|
+
```typescript
|
|
361
|
+
providers: [
|
|
362
|
+
{ provide: APP_CONFIG, useValue: environment },
|
|
363
|
+
]
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**`Cannot read properties of undefined (reading 'apiBaseUrl')`**
|
|
367
|
+
|
|
368
|
+
`APP_CONFIG` was provided but the environment object is missing the required field. Check that your environment file implements the full `IAppConfig` interface.
|
|
369
|
+
|
|
370
|
+
**`HTTP 404 on API requests`**
|
|
371
|
+
|
|
372
|
+
Check that `apiBaseUrl` does not have a trailing slash:
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
// ❌ Wrong
|
|
376
|
+
apiBaseUrl: 'http://localhost:2002/'
|
|
377
|
+
|
|
378
|
+
// ✅ Correct
|
|
379
|
+
apiBaseUrl: 'http://localhost:2002'
|
|
380
|
+
```
|
|
338
381
|
|
|
339
382
|
---
|
|
340
383
|
|
|
341
|
-
|
|
384
|
+
## License
|
|
385
|
+
|
|
386
|
+
MIT © FLUSYS
|
|
@@ -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 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: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | 'eventManager' | 'notification',\n): boolean {\n return isServiceEnabled(config, feature);\n}\n\nexport function getServiceUrl(\n config: IAppConfig,\n serviceName: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | 'eventManager' | 'notification' | (string & {}),\n): string {\n if (isServiceEnabled(config, serviceName)) {\n const service = 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(config: IAppConfig): 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(config: IAppConfig): 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, 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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"}
|
package/package.json
CHANGED