@flusys/ng-core 5.0.1 → 5.0.4
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 +28 -51
- package/fesm2022/flusys-ng-core.mjs +16 -167
- package/fesm2022/flusys-ng-core.mjs.map +1 -1
- package/package.json +1 -1
- package/types/flusys-ng-core.d.ts +27 -57
package/README.md
CHANGED
|
@@ -20,24 +20,20 @@ npm install @flusys/ng-core
|
|
|
20
20
|
Provide the config token and HTTP interceptors once in `app.config.ts`. Every FLUSYS package reads from `APP_CONFIG`.
|
|
21
21
|
|
|
22
22
|
```typescript
|
|
23
|
-
import { ApplicationConfig } from
|
|
24
|
-
import { provideHttpClient, withFetch, withInterceptors } from
|
|
25
|
-
import {
|
|
26
|
-
APP_CONFIG,
|
|
27
|
-
errorCatchingInterceptor,
|
|
28
|
-
apiLoaderInterceptor,
|
|
29
|
-
} from '@flusys/ng-core';
|
|
23
|
+
import { ApplicationConfig } from "@angular/core";
|
|
24
|
+
import { provideHttpClient, withFetch, withInterceptors } from "@angular/common/http";
|
|
25
|
+
import { APP_CONFIG, errorCatchingInterceptor, apiLoaderInterceptor } from "@flusys/ng-core";
|
|
30
26
|
|
|
31
27
|
export const appConfig: ApplicationConfig = {
|
|
32
28
|
providers: [
|
|
33
29
|
{
|
|
34
30
|
provide: APP_CONFIG,
|
|
35
31
|
useValue: {
|
|
36
|
-
appName:
|
|
37
|
-
appLogo:
|
|
38
|
-
apiBaseUrl:
|
|
32
|
+
appName: "My App",
|
|
33
|
+
appLogo: "/assets/logo.png",
|
|
34
|
+
apiBaseUrl: "http://localhost:2002",
|
|
39
35
|
production: false,
|
|
40
|
-
permissionMode:
|
|
36
|
+
permissionMode: "FULL", // 'RBAC' | 'DIRECT' | 'FULL'
|
|
41
37
|
multiTenant: false,
|
|
42
38
|
services: {
|
|
43
39
|
auth: {
|
|
@@ -48,13 +44,13 @@ export const appConfig: ApplicationConfig = {
|
|
|
48
44
|
companySelection: true,
|
|
49
45
|
},
|
|
50
46
|
},
|
|
51
|
-
iam:
|
|
52
|
-
storage:
|
|
53
|
-
formBuilder:
|
|
54
|
-
email:
|
|
55
|
-
eventManager:
|
|
56
|
-
notification:
|
|
57
|
-
localization:
|
|
47
|
+
iam: { enabled: true },
|
|
48
|
+
storage: { enabled: true },
|
|
49
|
+
formBuilder: { enabled: false },
|
|
50
|
+
email: { enabled: false },
|
|
51
|
+
eventManager: { enabled: false },
|
|
52
|
+
notification: { enabled: false, socketUrl: "ws://localhost:2002" }, // optional
|
|
53
|
+
localization: { enabled: false },
|
|
58
54
|
},
|
|
59
55
|
},
|
|
60
56
|
},
|
|
@@ -63,8 +59,8 @@ export const appConfig: ApplicationConfig = {
|
|
|
63
59
|
withInterceptors([
|
|
64
60
|
// authInterceptor, // from @flusys/ng-auth — add first
|
|
65
61
|
// tokenRefreshInterceptor, // from @flusys/ng-auth — add second
|
|
66
|
-
errorCatchingInterceptor,
|
|
67
|
-
apiLoaderInterceptor,
|
|
62
|
+
errorCatchingInterceptor, // shows PrimeNG toast on non-401/403 errors
|
|
63
|
+
apiLoaderInterceptor, // drives global progress bar
|
|
68
64
|
]),
|
|
69
65
|
),
|
|
70
66
|
],
|
|
@@ -80,10 +76,10 @@ Add `<lib-app-config />` once in your app root template. It renders the global P
|
|
|
80
76
|
Extend to create a typed service for any POST-only RPC endpoint. The base URL is derived from `APP_CONFIG.apiBaseUrl`.
|
|
81
77
|
|
|
82
78
|
```typescript
|
|
83
|
-
import { Injectable } from
|
|
84
|
-
import { BaseApiService } from
|
|
79
|
+
import { Injectable } from "@angular/core";
|
|
80
|
+
import { BaseApiService } from "@flusys/ng-core";
|
|
85
81
|
|
|
86
|
-
@Injectable({ providedIn:
|
|
82
|
+
@Injectable({ providedIn: "root" })
|
|
87
83
|
export class ProductService extends BaseApiService {
|
|
88
84
|
getAll() {
|
|
89
85
|
return this.http.post(`${this.baseUrl}/products/get-all`, {});
|
|
@@ -111,13 +107,13 @@ readonly loader = inject(ApiLoaderService);
|
|
|
111
107
|
Tag requests to get per-section loading signals:
|
|
112
108
|
|
|
113
109
|
```typescript
|
|
114
|
-
import { HttpHeaders } from
|
|
110
|
+
import { HttpHeaders } from "@angular/common/http";
|
|
115
111
|
|
|
116
112
|
this.http.post(url, body, {
|
|
117
|
-
headers: new HttpHeaders({
|
|
113
|
+
headers: new HttpHeaders({ "x-loader-tag": "user-list" }),
|
|
118
114
|
});
|
|
119
115
|
|
|
120
|
-
const isUserListLoading = this.loader.getLoader(
|
|
116
|
+
const isUserListLoading = this.loader.getLoader("user-list"); // Signal<boolean>
|
|
121
117
|
```
|
|
122
118
|
|
|
123
119
|
---
|
|
@@ -127,8 +123,8 @@ const isUserListLoading = this.loader.getLoader('user-list'); // Signal<boolean>
|
|
|
127
123
|
Opt out of the global error toast for requests you handle locally:
|
|
128
124
|
|
|
129
125
|
```typescript
|
|
130
|
-
import { SKIP_ERROR_TOAST } from
|
|
131
|
-
import { HttpContext } from
|
|
126
|
+
import { SKIP_ERROR_TOAST } from "@flusys/ng-core";
|
|
127
|
+
import { HttpContext } from "@angular/common/http";
|
|
132
128
|
|
|
133
129
|
this.http.post(url, body, {
|
|
134
130
|
context: new HttpContext().set(SKIP_ERROR_TOAST, true),
|
|
@@ -137,36 +133,17 @@ this.http.post(url, body, {
|
|
|
137
133
|
|
|
138
134
|
---
|
|
139
135
|
|
|
140
|
-
## 5. TRANSLATE_ADAPTER
|
|
141
|
-
|
|
142
|
-
Register an adapter so the error interceptor translates error messages. Implemented and provided by `@flusys/ng-localization`.
|
|
143
|
-
|
|
144
|
-
```typescript
|
|
145
|
-
import { TRANSLATE_ADAPTER } from '@flusys/ng-core';
|
|
146
|
-
|
|
147
|
-
// In app.config.ts providers:
|
|
148
|
-
{ provide: TRANSLATE_ADAPTER, useClass: MyTranslateAdapter }
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Required interface: `languageCode: Signal<string>` and `translate(key: string, params?: Record<string, unknown>): string`.
|
|
152
|
-
|
|
153
|
-
---
|
|
154
|
-
|
|
155
136
|
## 6. Feature Helper Functions
|
|
156
137
|
|
|
157
138
|
Read flags without injecting `APP_CONFIG` directly:
|
|
158
139
|
|
|
159
140
|
```typescript
|
|
160
|
-
import {
|
|
161
|
-
isSignUpEnabled,
|
|
162
|
-
getPermissionMode,
|
|
163
|
-
getNotificationSocketUrl,
|
|
164
|
-
} from '@flusys/ng-core';
|
|
141
|
+
import { isSignUpEnabled, getPermissionMode, getNotificationSocketUrl } from "@flusys/ng-core";
|
|
165
142
|
|
|
166
143
|
const config = inject(APP_CONFIG);
|
|
167
|
-
isSignUpEnabled(config);
|
|
168
|
-
getPermissionMode(config);
|
|
169
|
-
getNotificationSocketUrl(config);
|
|
144
|
+
isSignUpEnabled(config); // boolean — reads services.auth.features.signUp
|
|
145
|
+
getPermissionMode(config); // 'RBAC' | 'DIRECT' | 'FULL'
|
|
146
|
+
getNotificationSocketUrl(config); // string | undefined
|
|
170
147
|
```
|
|
171
148
|
|
|
172
149
|
---
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, computed, Injectable, inject, Component, InjectionToken
|
|
2
|
+
import { signal, computed, Injectable, inject, Component, InjectionToken } from '@angular/core';
|
|
3
3
|
import * as i2 from 'primeng/toast';
|
|
4
4
|
import { ToastModule } from 'primeng/toast';
|
|
5
5
|
import { ConfirmDialog } from 'primeng/confirmdialog';
|
|
6
6
|
import * as i1 from 'primeng/progressbar';
|
|
7
7
|
import { ProgressBarModule } from 'primeng/progressbar';
|
|
8
8
|
import { finalize } from 'rxjs/operators';
|
|
9
|
-
import {
|
|
10
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
11
|
-
import { MessageService } from 'primeng/api';
|
|
12
|
-
import { catchError, throwError } from 'rxjs';
|
|
9
|
+
import { HttpClient } from '@angular/common/http';
|
|
13
10
|
|
|
14
11
|
class ApiLoaderService {
|
|
15
12
|
loaders = signal({}, ...(ngDevMode ? [{ debugName: "loaders" }] : []));
|
|
@@ -79,153 +76,11 @@ function apiLoaderInterceptor(req, next) {
|
|
|
79
76
|
return next(cleanReq).pipe(finalize(() => loaderService.decrease(loaderTag)));
|
|
80
77
|
}
|
|
81
78
|
|
|
82
|
-
const TRANSLATE_ADAPTER = new InjectionToken('TRANSLATE_ADAPTER');
|
|
83
|
-
/** Registry for fallback messages when localization is not active */
|
|
84
|
-
const FALLBACK_MESSAGES_REGISTRY = new InjectionToken('FALLBACK_MESSAGES_REGISTRY');
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* HTTP context token to skip global error toast.
|
|
88
|
-
* Use when component handles error display locally.
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* ```typescript
|
|
92
|
-
* this.http.post(url, data, {
|
|
93
|
-
* context: new HttpContext().set(SKIP_ERROR_TOAST, true)
|
|
94
|
-
* });
|
|
95
|
-
* ```
|
|
96
|
-
*/
|
|
97
|
-
const SKIP_ERROR_TOAST = new HttpContextToken(() => false);
|
|
98
|
-
/**
|
|
99
|
-
* Error Catching Interceptor
|
|
100
|
-
* Displays error messages for failed HTTP requests using backend response.
|
|
101
|
-
*
|
|
102
|
-
* Features:
|
|
103
|
-
* - Uses backend's error title (error.error.error) as summary
|
|
104
|
-
* - Uses backend's error message (error.error.message) as detail
|
|
105
|
-
* - Falls back to sensible defaults based on status code
|
|
106
|
-
* - Skip with SKIP_ERROR_TOAST context token for local handling
|
|
107
|
-
*
|
|
108
|
-
* NOTE: This interceptor only handles error display.
|
|
109
|
-
* For 401 handling and token refresh, use tokenRefreshInterceptor from @flusys/ng-auth.
|
|
110
|
-
*
|
|
111
|
-
* @example
|
|
112
|
-
* ```typescript
|
|
113
|
-
* // In app.config.ts
|
|
114
|
-
* provideHttpClient(
|
|
115
|
-
* withFetch(),
|
|
116
|
-
* withInterceptors([
|
|
117
|
-
* authInterceptor, // from @flusys/ng-auth
|
|
118
|
-
* tokenRefreshInterceptor, // from @flusys/ng-auth
|
|
119
|
-
* errorCatchingInterceptor, // from @flusys/ng-core
|
|
120
|
-
* apiLoaderInterceptor, // from @flusys/ng-core
|
|
121
|
-
* ])
|
|
122
|
-
* )
|
|
123
|
-
* ```
|
|
124
|
-
*/
|
|
125
|
-
function errorCatchingInterceptor(req, next) {
|
|
126
|
-
const messageService = inject(MessageService);
|
|
127
|
-
const platformId = inject(PLATFORM_ID);
|
|
128
|
-
const translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
|
|
129
|
-
const isBrowser = isPlatformBrowser(platformId);
|
|
130
|
-
// Skip error display for check-login (expected to fail when not logged in)
|
|
131
|
-
const isCheckLogin = req.url.includes('/check-login');
|
|
132
|
-
// Skip if component handles error locally
|
|
133
|
-
const skipErrorToast = req.context.get(SKIP_ERROR_TOAST);
|
|
134
|
-
return next(req).pipe(catchError((error) => {
|
|
135
|
-
// Skip 401 errors - handled by tokenRefreshInterceptor in ng-auth
|
|
136
|
-
// Skip 403 errors - handled by permission denied UI in components
|
|
137
|
-
// Skip check-login errors - expected behavior
|
|
138
|
-
// Skip if component handles error locally
|
|
139
|
-
if (error.status === 401 || error.status === 403 || isCheckLogin || skipErrorToast) {
|
|
140
|
-
return throwError(() => error);
|
|
141
|
-
}
|
|
142
|
-
// Display error message for other errors
|
|
143
|
-
if (isBrowser) {
|
|
144
|
-
const { summary, detail } = extractErrorInfo(error, translateAdapter);
|
|
145
|
-
messageService.add({
|
|
146
|
-
severity: 'error',
|
|
147
|
-
summary,
|
|
148
|
-
detail,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
return throwError(() => error);
|
|
152
|
-
}));
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Extract error summary and detail from HTTP error response.
|
|
156
|
-
* Uses backend response format: { message, messageKey, messageVariables, error, statusCode }
|
|
157
|
-
* If messageKey is present and translate adapter is available, translates the message.
|
|
158
|
-
*/
|
|
159
|
-
function extractErrorInfo(error, translateAdapter) {
|
|
160
|
-
// Check if we have a messageKey for translation
|
|
161
|
-
const messageKey = error.error?.messageKey;
|
|
162
|
-
const messageVariables = error.error?.messageVariables;
|
|
163
|
-
let detail;
|
|
164
|
-
if (messageKey && translateAdapter) {
|
|
165
|
-
// Use translated message if available
|
|
166
|
-
detail = translateAdapter.translate(messageKey, messageVariables);
|
|
167
|
-
}
|
|
168
|
-
else if (typeof error.error?.message === 'string') {
|
|
169
|
-
detail = error.error.message;
|
|
170
|
-
}
|
|
171
|
-
else if (Array.isArray(error.error?.message)) {
|
|
172
|
-
// NestJS validation errors come as array
|
|
173
|
-
detail = error.error.message[0];
|
|
174
|
-
}
|
|
175
|
-
else if (error.message) {
|
|
176
|
-
detail = error.message;
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
detail = translateAdapter
|
|
180
|
-
? translateAdapter.translate('shared.unexpected.error')
|
|
181
|
-
: 'shared.unexpected.error';
|
|
182
|
-
}
|
|
183
|
-
// Extract summary/title from backend response or use default based on status
|
|
184
|
-
let summary;
|
|
185
|
-
if (typeof error.error?.error === 'string') {
|
|
186
|
-
// Backend sends error type (e.g., "Not Found", "Bad Request", "Validation Error")
|
|
187
|
-
summary = error.error.error;
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
// Fallback to status-based title
|
|
191
|
-
summary = getDefaultSummary(error.status, translateAdapter);
|
|
192
|
-
}
|
|
193
|
-
return { summary, detail };
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Get default error summary based on HTTP status code.
|
|
197
|
-
* Uses TRANSLATE_ADAPTER for localization when available.
|
|
198
|
-
*/
|
|
199
|
-
function getDefaultSummary(status, translateAdapter) {
|
|
200
|
-
const keyMap = {
|
|
201
|
-
400: 'shared.error.bad.request',
|
|
202
|
-
404: 'shared.error.not.found',
|
|
203
|
-
409: 'shared.error.conflict',
|
|
204
|
-
422: 'shared.error.validation.error',
|
|
205
|
-
500: 'shared.error.server.error',
|
|
206
|
-
502: 'shared.error.service.unavailable',
|
|
207
|
-
503: 'shared.error.service.unavailable',
|
|
208
|
-
504: 'shared.error.service.unavailable',
|
|
209
|
-
};
|
|
210
|
-
const key = keyMap[status] || 'shared.error';
|
|
211
|
-
if (translateAdapter) {
|
|
212
|
-
return translateAdapter.translate(key);
|
|
213
|
-
}
|
|
214
|
-
// Fallback to key when no translator available
|
|
215
|
-
return key;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
79
|
const APP_CONFIG = new InjectionToken('APP_CONFIG');
|
|
219
|
-
function getDatabaseMode(config) {
|
|
220
|
-
return config.multiTenant.enabled ? 'multi-tenant' : 'single';
|
|
221
|
-
}
|
|
222
80
|
function isServiceEnabled(config, serviceName) {
|
|
223
81
|
const service = config.services[serviceName];
|
|
224
82
|
return service?.enabled ?? false;
|
|
225
83
|
}
|
|
226
|
-
function isFeatureEnabled(config, feature) {
|
|
227
|
-
return isServiceEnabled(config, feature);
|
|
228
|
-
}
|
|
229
84
|
function getServiceUrl(config, serviceName) {
|
|
230
85
|
if (isServiceEnabled(config, serviceName)) {
|
|
231
86
|
const service = config.services[serviceName];
|
|
@@ -236,21 +91,6 @@ function getServiceUrl(config, serviceName) {
|
|
|
236
91
|
function isCompanyFeatureEnabled(config) {
|
|
237
92
|
return config.enableCompanyFeature ?? false;
|
|
238
93
|
}
|
|
239
|
-
function isStorageFeatureEnabled(config) {
|
|
240
|
-
return isServiceEnabled(config, 'storage');
|
|
241
|
-
}
|
|
242
|
-
function isFormBuilderFeatureEnabled(config) {
|
|
243
|
-
return isServiceEnabled(config, 'formBuilder');
|
|
244
|
-
}
|
|
245
|
-
function isEmailFeatureEnabled(config) {
|
|
246
|
-
return isServiceEnabled(config, 'email');
|
|
247
|
-
}
|
|
248
|
-
function isEventManagerFeatureEnabled(config) {
|
|
249
|
-
return isServiceEnabled(config, 'eventManager');
|
|
250
|
-
}
|
|
251
|
-
function isNotificationFeatureEnabled(config) {
|
|
252
|
-
return isServiceEnabled(config, 'notification');
|
|
253
|
-
}
|
|
254
94
|
function getNotificationSocketUrl(config) {
|
|
255
95
|
const service = config.services.notification;
|
|
256
96
|
if (!service?.enabled)
|
|
@@ -272,15 +112,22 @@ function getDefaultCompanySlug(config) {
|
|
|
272
112
|
function getDefaultBranchSlug(config) {
|
|
273
113
|
return config.registrationDefaults?.branchSlug;
|
|
274
114
|
}
|
|
275
|
-
function getRegistrationDefaults(config) {
|
|
276
|
-
return config.registrationDefaults;
|
|
277
|
-
}
|
|
278
115
|
const DEFAULT_APP_NAME = 'FLUSYS';
|
|
279
116
|
const DEFAULT_AUTHOR = {
|
|
280
117
|
name: 'Flusys',
|
|
281
118
|
url: 'https://flusys.vercel.app',
|
|
282
119
|
};
|
|
283
120
|
|
|
121
|
+
/** Registry for fallback messages when localization is not active */
|
|
122
|
+
const FALLBACK_MESSAGES_REGISTRY = new InjectionToken('FALLBACK_MESSAGES_REGISTRY');
|
|
123
|
+
// ✅ Provide it once at root as a single shared object
|
|
124
|
+
const provideFallbackMessagesRegistry = () => ({
|
|
125
|
+
provide: FALLBACK_MESSAGES_REGISTRY,
|
|
126
|
+
useValue: {}, // Single object instance shared across all injections
|
|
127
|
+
});
|
|
128
|
+
const LOCALIZATION_STATE_SERVICE = new InjectionToken('LOCALIZATION_STATE_SERVICE');
|
|
129
|
+
const LOCALIZATION_API_SERVICE = new InjectionToken('LOCALIZATION_API_SERVICE');
|
|
130
|
+
|
|
284
131
|
/**
|
|
285
132
|
* Base API Service
|
|
286
133
|
* Provides service-specific base URL resolution
|
|
@@ -318,7 +165,9 @@ class BaseApiService {
|
|
|
318
165
|
*/
|
|
319
166
|
getUrl(endpoint) {
|
|
320
167
|
// Remove leading slash if present to avoid double slashes
|
|
321
|
-
const cleanEndpoint = endpoint.startsWith('/')
|
|
168
|
+
const cleanEndpoint = endpoint.startsWith('/')
|
|
169
|
+
? endpoint.substring(1)
|
|
170
|
+
: endpoint;
|
|
322
171
|
return `${this.baseUrl}/${cleanEndpoint}`;
|
|
323
172
|
}
|
|
324
173
|
}
|
|
@@ -329,5 +178,5 @@ class BaseApiService {
|
|
|
329
178
|
* Generated bundle index. Do not edit.
|
|
330
179
|
*/
|
|
331
180
|
|
|
332
|
-
export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, FALLBACK_MESSAGES_REGISTRY,
|
|
181
|
+
export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, FALLBACK_MESSAGES_REGISTRY, LOCALIZATION_API_SERVICE, LOCALIZATION_STATE_SERVICE, LibAppConfigComponent, apiLoaderInterceptor, getDefaultBranchSlug, getDefaultCompanySlug, getDefaultStorageConfigId, getNotificationSocketUrl, getPermissionMode, getServiceUrl, isCompanyFeatureEnabled, isServiceEnabled, isSignUpEnabled, provideFallbackMessagesRegistry };
|
|
333
182
|
//# sourceMappingURL=flusys-ng-core.mjs.map
|
|
@@ -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, 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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"}
|
|
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/app-config.interface.ts","../../../projects/ng-core/interfaces/translate-adapter.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 { Observable } from 'rxjs';\nimport { finalize } from 'rxjs/operators';\nimport { ApiLoaderService } from '../services/api-loader.service';\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 =\n 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(finalize(() => loaderService.decrease(loaderTag)));\n}\n","import { InjectionToken } from '@angular/core';\n\nexport type PermissionMode = 'rbac' | 'direct' | 'full';\n\nexport type ServiceName =\n | 'auth'\n | 'administration'\n | 'iam'\n | 'storage'\n | 'formBuilder'\n | 'email'\n | 'eventManager'\n | 'notification'\n | 'localization';\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}\nexport const APP_CONFIG = new InjectionToken<IAppConfig>('APP_CONFIG');\n\nexport function isServiceEnabled(\n config: IAppConfig,\n serviceName: ServiceName,\n): boolean {\n const service = config.services[serviceName as keyof typeof config.services];\n return service?.enabled ?? false;\n}\n\nexport function getServiceUrl(\n config: IAppConfig,\n serviceName: ServiceName,\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 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 const DEFAULT_APP_NAME = 'FLUSYS';\nexport const DEFAULT_AUTHOR: IAuthorConfig = {\n name: 'Flusys',\n url: 'https://flusys.vercel.app',\n};\n","import { InjectionToken, Signal } from '@angular/core';\n\nexport interface ITranslateAdapter {\n /** Current language code signal - read this to create reactive dependency */\n translate(key: string, params?: Record<string, unknown>): string;\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// ✅ Provide it once at root as a single shared object\nexport const provideFallbackMessagesRegistry = () => ({\n provide: FALLBACK_MESSAGES_REGISTRY,\n useValue: {}, // Single object instance shared across all injections\n});\n\n// Injection Token for localization services used in the resolver\nexport interface ILocalizationStateServicesAdapter {\n hasModuleFallbacks(module: string): boolean;\n registerModuleFallbacks(\n module: string,\n messages: Record<string, string>,\n ): void;\n currentLanguageCode: Signal<string>;\n isModuleLoaded(module: string): boolean;\n mergeTranslations(translations: Record<string, string>): void;\n markModuleLoaded(module: string): void;\n currentLanguage: Signal<string>;\n translations: Signal<Record<string, string>>;\n fallbackMessages: Signal<Record<string, string>>;\n}\nexport interface ILocalizationApiServicesAdapter {\n getTranslationsByLanguage(languageCode: string, modules: string[]): any;\n}\nexport const LOCALIZATION_STATE_SERVICE =\n new InjectionToken<ILocalizationStateServicesAdapter>(\n 'LOCALIZATION_STATE_SERVICE',\n );\nexport const LOCALIZATION_API_SERVICE =\n new InjectionToken<ILocalizationApiServicesAdapter>(\n 'LOCALIZATION_API_SERVICE',\n );\n","import { HttpClient } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport {\n APP_CONFIG,\n getServiceUrl,\n ServiceName,\n} 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: ServiceName) {\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('/')\n ? endpoint.substring(1)\n : 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { Signal, InjectionToken } from '@angular/core';
|
|
3
|
-
import { HttpRequest, HttpHandlerFn, HttpEvent,
|
|
3
|
+
import { HttpRequest, HttpHandlerFn, HttpEvent, HttpClient } from '@angular/common/http';
|
|
4
4
|
import { Observable } from 'rxjs';
|
|
5
5
|
import * as _flusys_ng_core from '@flusys/ng-core';
|
|
6
6
|
|
|
@@ -39,48 +39,8 @@ declare class LibAppConfigComponent {
|
|
|
39
39
|
*/
|
|
40
40
|
declare function apiLoaderInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>>;
|
|
41
41
|
|
|
42
|
-
/**
|
|
43
|
-
* HTTP context token to skip global error toast.
|
|
44
|
-
* Use when component handles error display locally.
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* ```typescript
|
|
48
|
-
* this.http.post(url, data, {
|
|
49
|
-
* context: new HttpContext().set(SKIP_ERROR_TOAST, true)
|
|
50
|
-
* });
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
declare const SKIP_ERROR_TOAST: HttpContextToken<boolean>;
|
|
54
|
-
/**
|
|
55
|
-
* Error Catching Interceptor
|
|
56
|
-
* Displays error messages for failed HTTP requests using backend response.
|
|
57
|
-
*
|
|
58
|
-
* Features:
|
|
59
|
-
* - Uses backend's error title (error.error.error) as summary
|
|
60
|
-
* - Uses backend's error message (error.error.message) as detail
|
|
61
|
-
* - Falls back to sensible defaults based on status code
|
|
62
|
-
* - Skip with SKIP_ERROR_TOAST context token for local handling
|
|
63
|
-
*
|
|
64
|
-
* NOTE: This interceptor only handles error display.
|
|
65
|
-
* For 401 handling and token refresh, use tokenRefreshInterceptor from @flusys/ng-auth.
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* ```typescript
|
|
69
|
-
* // In app.config.ts
|
|
70
|
-
* provideHttpClient(
|
|
71
|
-
* withFetch(),
|
|
72
|
-
* withInterceptors([
|
|
73
|
-
* authInterceptor, // from @flusys/ng-auth
|
|
74
|
-
* tokenRefreshInterceptor, // from @flusys/ng-auth
|
|
75
|
-
* errorCatchingInterceptor, // from @flusys/ng-core
|
|
76
|
-
* apiLoaderInterceptor, // from @flusys/ng-core
|
|
77
|
-
* ])
|
|
78
|
-
* )
|
|
79
|
-
* ```
|
|
80
|
-
*/
|
|
81
|
-
declare function errorCatchingInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>>;
|
|
82
|
-
|
|
83
42
|
type PermissionMode = 'rbac' | 'direct' | 'full';
|
|
43
|
+
type ServiceName = 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | 'eventManager' | 'notification' | 'localization';
|
|
84
44
|
interface IServiceConfig {
|
|
85
45
|
baseUrl: string;
|
|
86
46
|
enabled: boolean;
|
|
@@ -130,34 +90,44 @@ interface IAppConfig {
|
|
|
130
90
|
permissionMode?: PermissionMode;
|
|
131
91
|
}
|
|
132
92
|
declare const APP_CONFIG: InjectionToken<IAppConfig>;
|
|
133
|
-
declare function
|
|
134
|
-
declare function
|
|
135
|
-
declare function isFeatureEnabled(config: IAppConfig, feature: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | 'eventManager' | 'notification'): boolean;
|
|
136
|
-
declare function getServiceUrl(config: IAppConfig, serviceName: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | 'eventManager' | 'notification' | (string & {})): string;
|
|
93
|
+
declare function isServiceEnabled(config: IAppConfig, serviceName: ServiceName): boolean;
|
|
94
|
+
declare function getServiceUrl(config: IAppConfig, serviceName: ServiceName): string;
|
|
137
95
|
declare function isCompanyFeatureEnabled(config: IAppConfig): boolean;
|
|
138
|
-
declare function isStorageFeatureEnabled(config: IAppConfig): boolean;
|
|
139
|
-
declare function isFormBuilderFeatureEnabled(config: IAppConfig): boolean;
|
|
140
|
-
declare function isEmailFeatureEnabled(config: IAppConfig): boolean;
|
|
141
|
-
declare function isEventManagerFeatureEnabled(config: IAppConfig): boolean;
|
|
142
|
-
declare function isNotificationFeatureEnabled(config: IAppConfig): boolean;
|
|
143
96
|
declare function getNotificationSocketUrl(config: IAppConfig): string | null;
|
|
144
97
|
declare function getPermissionMode(config: IAppConfig): PermissionMode;
|
|
145
98
|
declare function getDefaultStorageConfigId(config: IAppConfig): string | undefined;
|
|
146
99
|
declare function isSignUpEnabled(config: IAppConfig): boolean;
|
|
147
100
|
declare function getDefaultCompanySlug(config: IAppConfig): string | undefined;
|
|
148
101
|
declare function getDefaultBranchSlug(config: IAppConfig): string | undefined;
|
|
149
|
-
declare function getRegistrationDefaults(config: IAppConfig): IRegistrationDefaults | undefined;
|
|
150
102
|
declare const DEFAULT_APP_NAME = "FLUSYS";
|
|
151
103
|
declare const DEFAULT_AUTHOR: IAuthorConfig;
|
|
152
104
|
|
|
153
105
|
interface ITranslateAdapter {
|
|
154
106
|
/** Current language code signal - read this to create reactive dependency */
|
|
155
|
-
readonly languageCode: Signal<string>;
|
|
156
107
|
translate(key: string, params?: Record<string, unknown>): string;
|
|
157
108
|
}
|
|
158
|
-
declare const TRANSLATE_ADAPTER: InjectionToken<ITranslateAdapter>;
|
|
159
109
|
/** Registry for fallback messages when localization is not active */
|
|
160
110
|
declare const FALLBACK_MESSAGES_REGISTRY: InjectionToken<Record<string, string>>;
|
|
111
|
+
declare const provideFallbackMessagesRegistry: () => {
|
|
112
|
+
provide: InjectionToken<Record<string, string>>;
|
|
113
|
+
useValue: {};
|
|
114
|
+
};
|
|
115
|
+
interface ILocalizationStateServicesAdapter {
|
|
116
|
+
hasModuleFallbacks(module: string): boolean;
|
|
117
|
+
registerModuleFallbacks(module: string, messages: Record<string, string>): void;
|
|
118
|
+
currentLanguageCode: Signal<string>;
|
|
119
|
+
isModuleLoaded(module: string): boolean;
|
|
120
|
+
mergeTranslations(translations: Record<string, string>): void;
|
|
121
|
+
markModuleLoaded(module: string): void;
|
|
122
|
+
currentLanguage: Signal<string>;
|
|
123
|
+
translations: Signal<Record<string, string>>;
|
|
124
|
+
fallbackMessages: Signal<Record<string, string>>;
|
|
125
|
+
}
|
|
126
|
+
interface ILocalizationApiServicesAdapter {
|
|
127
|
+
getTranslationsByLanguage(languageCode: string, modules: string[]): any;
|
|
128
|
+
}
|
|
129
|
+
declare const LOCALIZATION_STATE_SERVICE: InjectionToken<ILocalizationStateServicesAdapter>;
|
|
130
|
+
declare const LOCALIZATION_API_SERVICE: InjectionToken<ILocalizationApiServicesAdapter>;
|
|
161
131
|
|
|
162
132
|
/**
|
|
163
133
|
* Base API Service
|
|
@@ -186,7 +156,7 @@ declare abstract class BaseApiService {
|
|
|
186
156
|
/**
|
|
187
157
|
* @param serviceName - Service name from config (e.g., 'auth', 'iam', 'storage')
|
|
188
158
|
*/
|
|
189
|
-
constructor(serviceName:
|
|
159
|
+
constructor(serviceName: ServiceName);
|
|
190
160
|
/**
|
|
191
161
|
* Get full URL for an endpoint
|
|
192
162
|
* @param endpoint - API endpoint (e.g., '/users/get-all')
|
|
@@ -195,5 +165,5 @@ declare abstract class BaseApiService {
|
|
|
195
165
|
protected getUrl(endpoint: string): string;
|
|
196
166
|
}
|
|
197
167
|
|
|
198
|
-
export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, FALLBACK_MESSAGES_REGISTRY,
|
|
199
|
-
export type { IAppConfig, IAuthorConfig, INotificationServiceConfig, IRegistrationDefaults, IServiceConfig, IStorageServiceConfig, ITranslateAdapter, PermissionMode };
|
|
168
|
+
export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, FALLBACK_MESSAGES_REGISTRY, LOCALIZATION_API_SERVICE, LOCALIZATION_STATE_SERVICE, LibAppConfigComponent, apiLoaderInterceptor, getDefaultBranchSlug, getDefaultCompanySlug, getDefaultStorageConfigId, getNotificationSocketUrl, getPermissionMode, getServiceUrl, isCompanyFeatureEnabled, isServiceEnabled, isSignUpEnabled, provideFallbackMessagesRegistry };
|
|
169
|
+
export type { IAppConfig, IAuthorConfig, ILocalizationApiServicesAdapter, ILocalizationStateServicesAdapter, INotificationServiceConfig, IRegistrationDefaults, IServiceConfig, IStorageServiceConfig, ITranslateAdapter, PermissionMode, ServiceName };
|