@flusys/ng-core 5.0.3 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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 '@angular/core';
24
- import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
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: 'My App',
37
- appLogo: '/assets/logo.png',
38
- apiBaseUrl: 'http://localhost:2002',
32
+ appName: "My App",
33
+ appLogo: "/assets/logo.png",
34
+ apiBaseUrl: "http://localhost:2002",
39
35
  production: false,
40
- permissionMode: 'FULL', // 'RBAC' | 'DIRECT' | 'FULL'
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: { enabled: true },
52
- storage: { enabled: true },
53
- formBuilder: { enabled: false },
54
- email: { enabled: false },
55
- eventManager: { enabled: false },
56
- notification: { enabled: false, socketUrl: 'ws://localhost:2002' }, // optional
57
- localization: { enabled: false },
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, // shows PrimeNG toast on non-401/403 errors
67
- apiLoaderInterceptor, // drives global progress bar
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 '@angular/core';
84
- import { BaseApiService } from '@flusys/ng-core';
79
+ import { Injectable } from "@angular/core";
80
+ import { BaseApiService } from "@flusys/ng-core";
85
81
 
86
- @Injectable({ providedIn: 'root' })
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 '@angular/common/http';
110
+ import { HttpHeaders } from "@angular/common/http";
115
111
 
116
112
  this.http.post(url, body, {
117
- headers: new HttpHeaders({ 'x-loader-tag': 'user-list' }),
113
+ headers: new HttpHeaders({ "x-loader-tag": "user-list" }),
118
114
  });
119
115
 
120
- const isUserListLoading = this.loader.getLoader('user-list'); // Signal<boolean>
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 '@flusys/ng-core';
131
- import { HttpContext } from '@angular/common/http';
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); // boolean — reads services.auth.features.signUp
168
- getPermissionMode(config); // 'RBAC' | 'DIRECT' | 'FULL'
169
- getNotificationSocketUrl(config); // string | undefined
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, PLATFORM_ID } from '@angular/core';
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 { HttpContextToken, HttpClient } from '@angular/common/http';
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('/') ? endpoint.substring(1) : endpoint;
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, LibAppConfigComponent, SKIP_ERROR_TOAST, TRANSLATE_ADAPTER, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultBranchSlug, getDefaultCompanySlug, getDefaultStorageConfigId, getNotificationSocketUrl, getPermissionMode, getRegistrationDefaults, getServiceUrl, isCompanyFeatureEnabled, isEmailFeatureEnabled, isEventManagerFeatureEnabled, isFeatureEnabled, isFormBuilderFeatureEnabled, isNotificationFeatureEnabled, isServiceEnabled, isSignUpEnabled, isStorageFeatureEnabled };
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
  {
2
2
  "name": "@flusys/ng-core",
3
- "version": "5.0.3",
3
+ "version": "5.1.0",
4
4
  "description": "Core utilities and services for FLUSYS Angular packages",
5
5
  "license": "MIT",
6
6
  "peerDependencies": {
@@ -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, HttpContextToken, HttpClient } from '@angular/common/http';
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 getDatabaseMode(config: IAppConfig): 'single' | 'multi-tenant';
134
- declare function isServiceEnabled(config: IAppConfig, serviceName: 'auth' | 'iam' | 'storage' | (string & {})): boolean;
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: string);
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, LibAppConfigComponent, SKIP_ERROR_TOAST, TRANSLATE_ADAPTER, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultBranchSlug, getDefaultCompanySlug, getDefaultStorageConfigId, getNotificationSocketUrl, getPermissionMode, getRegistrationDefaults, getServiceUrl, isCompanyFeatureEnabled, isEmailFeatureEnabled, isEventManagerFeatureEnabled, isFeatureEnabled, isFormBuilderFeatureEnabled, isNotificationFeatureEnabled, isServiceEnabled, isSignUpEnabled, isStorageFeatureEnabled };
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 };