@flusys/ng-core 1.0.0-rc → 1.0.1

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
@@ -82,8 +82,11 @@ export interface IAppConfig {
82
82
 
83
83
  services: {
84
84
  auth: IServiceConfig;
85
+ administration: IServiceConfig;
85
86
  iam: IServiceConfig;
86
- storage: IServiceConfig;
87
+ storage: IStorageServiceConfig; // Extends IServiceConfig with defaultStorageConfigId
88
+ formBuilder: IServiceConfig;
89
+ email: IServiceConfig;
87
90
  [key: string]: IServiceConfig; // Extensible for custom services
88
91
  };
89
92
 
@@ -137,8 +140,11 @@ export const environment = {
137
140
  apiBaseUrl: 'http://localhost:2002',
138
141
  services: {
139
142
  auth: { baseUrl: 'http://localhost:2002/auth', enabled: true },
143
+ administration: { baseUrl: 'http://localhost:2002/administration', enabled: true },
140
144
  iam: { baseUrl: 'http://localhost:2002/iam', enabled: true },
141
- storage: { baseUrl: 'http://localhost:2002/storage', enabled: true },
145
+ storage: { baseUrl: 'http://localhost:2002/storage', enabled: true, defaultStorageConfigId: '...' },
146
+ formBuilder: { baseUrl: 'http://localhost:2002/form-builder', enabled: true },
147
+ email: { baseUrl: 'http://localhost:2002/email', enabled: true },
142
148
  },
143
149
  };
144
150
  ```
@@ -153,6 +159,8 @@ import {
153
159
  isServiceEnabled,
154
160
  isCompanyFeatureEnabled,
155
161
  isStorageFeatureEnabled,
162
+ isFormBuilderFeatureEnabled,
163
+ isEmailFeatureEnabled,
156
164
  getPermissionMode,
157
165
  } from '@flusys/ng-core';
158
166
  ```
@@ -160,13 +168,17 @@ import {
160
168
  | Function | Signature | Description |
161
169
  |----------|-----------|-------------|
162
170
  | `getDatabaseMode` | `(config) → 'single' \| 'multi-tenant'` | Get database mode from `multiTenant.enabled` |
163
- | `isFeatureEnabled` | `(config, feature: 'auth' \| 'iam' \| 'storage') → boolean` | Check if a service feature is enabled |
164
- | `getServiceUrl` | `(config, serviceName) → string` | Get service URL or fallback to `apiBaseUrl` |
165
- | `isServiceEnabled` | `(config, serviceName) → boolean` | Check if a service is enabled |
171
+ | `isFeatureEnabled` | `(config, feature: ServiceName) → boolean` | Check if a service feature is enabled |
172
+ | `getServiceUrl` | `(config, serviceName: ServiceName) → string` | Get service URL or fallback to `apiBaseUrl` |
173
+ | `isServiceEnabled` | `(config, serviceName: ServiceName) → boolean` | Check if a service is enabled |
166
174
  | `isCompanyFeatureEnabled` | `(config) → boolean` | Check `enableCompanyFeature` flag |
167
175
  | `isStorageFeatureEnabled` | `(config) → boolean` | Check `services.storage.enabled` |
176
+ | `isFormBuilderFeatureEnabled` | `(config) → boolean` | Check `services.formBuilder.enabled` |
177
+ | `isEmailFeatureEnabled` | `(config) → boolean` | Check `services.email.enabled` |
168
178
  | `getPermissionMode` | `(config) → PermissionMode` | Get permission mode (defaults to `'full'`) |
169
179
 
180
+ **ServiceName Type:** `'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email'`
181
+
170
182
  #### Constants
171
183
 
172
184
  ```typescript
@@ -186,8 +198,11 @@ export const DEFAULT_AUTHOR: IAuthorConfig = {
186
198
  multiTenant: { enabled: false, tenantHeader: 'x-tenant-id' },
187
199
  services: {
188
200
  auth: { baseUrl: '...', enabled: false },
201
+ administration: { baseUrl: '...', enabled: false },
189
202
  iam: { baseUrl: '...', enabled: false },
190
203
  storage: { baseUrl: '...', enabled: false },
204
+ formBuilder: { baseUrl: '...', enabled: false },
205
+ email: { baseUrl: '...', enabled: false },
191
206
  }
192
207
  }
193
208
  ```
@@ -199,8 +214,11 @@ export const DEFAULT_AUTHOR: IAuthorConfig = {
199
214
  multiTenant: { enabled: false, tenantHeader: 'x-tenant-id' },
200
215
  services: {
201
216
  auth: { baseUrl: 'http://localhost:2002/auth', enabled: true },
217
+ administration: { baseUrl: 'http://localhost:2002/administration', enabled: true },
202
218
  iam: { baseUrl: 'http://localhost:2002/iam', enabled: true },
203
- storage: { baseUrl: 'http://localhost:2002/storage', enabled: true },
219
+ storage: { baseUrl: 'http://localhost:2002/storage', enabled: true, defaultStorageConfigId: '...' },
220
+ formBuilder: { baseUrl: 'http://localhost:2002/form-builder', enabled: true },
221
+ email: { baseUrl: 'http://localhost:2002/email', enabled: true },
204
222
  }
205
223
  }
206
224
  ```
@@ -212,8 +230,11 @@ export const DEFAULT_AUTHOR: IAuthorConfig = {
212
230
  multiTenant: { enabled: true, tenantHeader: 'x-tenant-id', tenantId: 'tenant-123' },
213
231
  services: {
214
232
  auth: { baseUrl: 'http://localhost:2002/auth', enabled: true },
233
+ administration: { baseUrl: 'http://localhost:2002/administration', enabled: true },
215
234
  iam: { baseUrl: 'http://localhost:2002/iam', enabled: true },
216
- storage: { baseUrl: 'http://localhost:2002/storage', enabled: true },
235
+ storage: { baseUrl: 'http://localhost:2002/storage', enabled: true, defaultStorageConfigId: '...' },
236
+ formBuilder: { baseUrl: 'http://localhost:2002/form-builder', enabled: true },
237
+ email: { baseUrl: 'http://localhost:2002/email', enabled: true },
217
238
  }
218
239
  }
219
240
  ```
@@ -532,13 +553,17 @@ readonly usersLoading = this.apiLoaderService.getLoader('users-list');
532
553
  | Function | Description |
533
554
  |----------|-------------|
534
555
  | `getDatabaseMode(config)` | Returns `'single'` or `'multi-tenant'` |
535
- | `isFeatureEnabled(config, feature)` | Check service feature enabled (`'auth' \| 'iam' \| 'storage'`) |
556
+ | `isFeatureEnabled(config, feature)` | Check service feature enabled (ServiceName) |
536
557
  | `getServiceUrl(config, serviceName)` | Get service URL with fallback |
537
558
  | `isServiceEnabled(config, serviceName)` | Check if service is enabled |
538
559
  | `isCompanyFeatureEnabled(config)` | Check company feature flag |
539
560
  | `isStorageFeatureEnabled(config)` | Check storage service enabled |
561
+ | `isFormBuilderFeatureEnabled(config)` | Check formBuilder service enabled |
562
+ | `isEmailFeatureEnabled(config)` | Check email service enabled |
540
563
  | `getPermissionMode(config)` | Get permission mode (default `'full'`) |
541
564
 
565
+ **ServiceName:** `'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email'`
566
+
542
567
  ### Constants
543
568
 
544
569
  | Constant | Value | Description |
@@ -561,5 +586,5 @@ readonly usersLoading = this.apiLoaderService.getLoader('users-list');
561
586
 
562
587
  ---
563
588
 
564
- **Last Updated:** 2026-02-18
589
+ **Last Updated:** 2026-02-22
565
590
  **Angular Version:** 21
@@ -35,10 +35,10 @@ class ApiLoaderService {
35
35
  return { ...loaders, [name]: Math.max(0, current - 1) };
36
36
  });
37
37
  }
38
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ApiLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
39
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ApiLoaderService, providedIn: 'root' });
38
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ApiLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
39
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ApiLoaderService, providedIn: 'root' });
40
40
  }
41
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ApiLoaderService, decorators: [{
41
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ApiLoaderService, decorators: [{
42
42
  type: Injectable,
43
43
  args: [{ providedIn: 'root' }]
44
44
  }] });
@@ -49,12 +49,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
49
49
  */
50
50
  class LibAppConfigComponent {
51
51
  apiLoaderService = inject(ApiLoaderService);
52
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LibAppConfigComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
53
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: LibAppConfigComponent, isStandalone: true, selector: "lib-app-config", ngImport: i0, template: "@if (apiLoaderService.show()) {\n<p-progressBar mode=\"indeterminate\" />\n}\n<p-toast />\n<p-confirmdialog />\n", styles: [":host ::ng-deep .p-progressbar{position:fixed;height:4px;z-index:998;left:0;right:0}:host ::ng-deep .p-toast-message-success{background:color-mix(in srgb,var(--p-primary-color),transparent 84%)!important;border-color:var(--p-primary-color)!important;color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon{color:var(--p-primary-color)!important;fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon svg{color:var(--p-primary-color)!important;fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon path{fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button{color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button .p-toast-close-icon{color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button:hover{background:color-mix(in srgb,var(--p-primary-color),transparent 70%)!important;color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button:focus{box-shadow:0 0 0 .2rem color-mix(in srgb,var(--p-primary-color),transparent 50%)!important}:host ::ng-deep .p-toast-message-success .p-toast-detail{color:var(--p-text-color)!important}\n"], dependencies: [{ kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "component", type: i1.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "unit", "mode", "color"] }, { kind: "ngmodule", type: ToastModule }, { kind: "component", type: i2.Toast, selector: "p-toast", inputs: ["key", "autoZIndex", "baseZIndex", "life", "styleClass", "position", "preventOpenDuplicates", "preventDuplicates", "showTransformOptions", "hideTransformOptions", "showTransitionOptions", "hideTransitionOptions", "motionOptions", "breakpoints"], outputs: ["onClose"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
52
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LibAppConfigComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
53
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: LibAppConfigComponent, isStandalone: true, selector: "lib-app-config", ngImport: i0, template: "@if (apiLoaderService.show()) {\n<p-progressBar mode=\"indeterminate\" />\n}\n<p-toast />\n<p-confirmdialog />\n", styles: [":host ::ng-deep .p-progressbar{position:fixed;height:4px;z-index:998;left:0;right:0}:host ::ng-deep .p-toast-message-success{background:color-mix(in srgb,var(--p-primary-color),transparent 84%)!important;border-color:var(--p-primary-color)!important;color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon{color:var(--p-primary-color)!important;fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon svg{color:var(--p-primary-color)!important;fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon path{fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button{color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button .p-toast-close-icon{color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button:hover{background:color-mix(in srgb,var(--p-primary-color),transparent 70%)!important;color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button:focus{box-shadow:0 0 0 .2rem color-mix(in srgb,var(--p-primary-color),transparent 50%)!important}:host ::ng-deep .p-toast-message-success .p-toast-detail{color:var(--p-text-color)!important}\n"], dependencies: [{ kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "component", type: i1.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "unit", "mode", "color"] }, { kind: "ngmodule", type: ToastModule }, { kind: "component", type: i2.Toast, selector: "p-toast", inputs: ["key", "autoZIndex", "baseZIndex", "life", "styleClass", "position", "preventOpenDuplicates", "preventDuplicates", "showTransformOptions", "hideTransformOptions", "showTransitionOptions", "hideTransitionOptions", "motionOptions", "breakpoints"], outputs: ["onClose"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
54
54
  }
55
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LibAppConfigComponent, decorators: [{
55
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LibAppConfigComponent, decorators: [{
56
56
  type: Component,
57
- args: [{ selector: 'lib-app-config', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ConfirmDialog, ProgressBarModule, ToastModule], template: "@if (apiLoaderService.show()) {\n<p-progressBar mode=\"indeterminate\" />\n}\n<p-toast />\n<p-confirmdialog />\n", styles: [":host ::ng-deep .p-progressbar{position:fixed;height:4px;z-index:998;left:0;right:0}:host ::ng-deep .p-toast-message-success{background:color-mix(in srgb,var(--p-primary-color),transparent 84%)!important;border-color:var(--p-primary-color)!important;color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon{color:var(--p-primary-color)!important;fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon svg{color:var(--p-primary-color)!important;fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon path{fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button{color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button .p-toast-close-icon{color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button:hover{background:color-mix(in srgb,var(--p-primary-color),transparent 70%)!important;color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button:focus{box-shadow:0 0 0 .2rem color-mix(in srgb,var(--p-primary-color),transparent 50%)!important}:host ::ng-deep .p-toast-message-success .p-toast-detail{color:var(--p-text-color)!important}\n"] }]
57
+ args: [{ selector: 'lib-app-config', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ConfirmDialog, ProgressBarModule, ToastModule], template: "@if (apiLoaderService.show()) {\n<p-progressBar mode=\"indeterminate\" />\n}\n<p-toast />\n<p-confirmdialog />\n", styles: [":host ::ng-deep .p-progressbar{position:fixed;height:4px;z-index:998;left:0;right:0}:host ::ng-deep .p-toast-message-success{background:color-mix(in srgb,var(--p-primary-color),transparent 84%)!important;border-color:var(--p-primary-color)!important;color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon{color:var(--p-primary-color)!important;fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon svg{color:var(--p-primary-color)!important;fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon path{fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button{color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button .p-toast-close-icon{color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button:hover{background:color-mix(in srgb,var(--p-primary-color),transparent 70%)!important;color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button:focus{box-shadow:0 0 0 .2rem color-mix(in srgb,var(--p-primary-color),transparent 50%)!important}:host ::ng-deep .p-toast-message-success .p-toast-detail{color:var(--p-text-color)!important}\n"] }]
58
58
  }] });
59
59
 
60
60
  /**
@@ -215,45 +215,51 @@ function getDatabaseMode(config) {
215
215
  return config.multiTenant.enabled ? 'multi-tenant' : 'single';
216
216
  }
217
217
  /**
218
- * Check if a feature is enabled
219
- * Features are enabled if their corresponding service exists and is enabled
218
+ * Check if a service is enabled
220
219
  */
221
- function isFeatureEnabled(config, feature) {
222
- const service = config.services[feature];
220
+ function isServiceEnabled(config, serviceName) {
221
+ const service = config.services[serviceName];
223
222
  return service?.enabled ?? false;
224
223
  }
225
224
  /**
226
- * Helper function to get service base URL
227
- * Returns service-specific URL or falls back to default apiBaseUrl
225
+ * Check if a feature is enabled (alias for isServiceEnabled with strict types)
226
+ */
227
+ function isFeatureEnabled(config, feature) {
228
+ return isServiceEnabled(config, feature);
229
+ }
230
+ /**
231
+ * Get service base URL or fallback to default apiBaseUrl
228
232
  */
229
233
  function getServiceUrl(config, serviceName) {
230
- const service = config.services[serviceName];
231
- if (service?.enabled) {
234
+ if (isServiceEnabled(config, serviceName)) {
235
+ const service = config.services[serviceName];
232
236
  return service.baseUrl;
233
237
  }
234
- // Fallback to default API base URL
235
238
  return config.apiBaseUrl;
236
239
  }
237
- /**
238
- * Helper function to check if a service is enabled
239
- */
240
- function isServiceEnabled(config, serviceName) {
241
- const service = config.services[serviceName];
242
- return service?.enabled ?? false;
243
- }
244
240
  /**
245
241
  * Check if company feature is enabled
246
- * When enabled, company selection/switching is available across the app
247
242
  */
248
243
  function isCompanyFeatureEnabled(config) {
249
244
  return config.enableCompanyFeature ?? false;
250
245
  }
251
246
  /**
252
- * Check if storage feature is enabled (backward compatible)
253
- * Storage feature is enabled if storage service is enabled
247
+ * Check if storage feature is enabled
254
248
  */
255
249
  function isStorageFeatureEnabled(config) {
256
- return config.services.storage?.enabled ?? false;
250
+ return isServiceEnabled(config, 'storage');
251
+ }
252
+ /**
253
+ * Check if form-builder feature is enabled
254
+ */
255
+ function isFormBuilderFeatureEnabled(config) {
256
+ return isServiceEnabled(config, 'formBuilder');
257
+ }
258
+ /**
259
+ * Check if email feature is enabled
260
+ */
261
+ function isEmailFeatureEnabled(config) {
262
+ return isServiceEnabled(config, 'email');
257
263
  }
258
264
  /**
259
265
  * Get permission mode from config
@@ -324,5 +330,5 @@ class BaseApiService {
324
330
  * Generated bundle index. Do not edit.
325
331
  */
326
332
 
327
- export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, SKIP_ERROR_TOAST, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultStorageConfigId, getPermissionMode, getServiceUrl, isCompanyFeatureEnabled, isFeatureEnabled, isServiceEnabled, isStorageFeatureEnabled };
333
+ export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, SKIP_ERROR_TOAST, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultStorageConfigId, getPermissionMode, getServiceUrl, isCompanyFeatureEnabled, isEmailFeatureEnabled, isFeatureEnabled, isFormBuilderFeatureEnabled, isServiceEnabled, isStorageFeatureEnabled };
328
334
  //# 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/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 { ChangeDetectionStrategy, 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 standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\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 {\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';\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 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);\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, error, statusCode }\n */\nfunction extractErrorInfo(error: HttpErrorResponse): { summary: string; detail: string } {\n // Extract message from backend response\n let detail: string;\n 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 = 'An unexpected error occurred.';\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);\n }\n\n return { summary, detail };\n}\n\n/**\n * Get default error summary based on HTTP status code\n */\nfunction getDefaultSummary(status: number): string {\n switch (status) {\n case 400:\n return 'Bad Request';\n case 404:\n return 'Not Found';\n case 409:\n return 'Conflict';\n case 422:\n return 'Validation Error';\n case 500:\n return 'Server Error';\n case 502:\n case 503:\n case 504:\n return 'Service Unavailable';\n default:\n return 'Error';\n }\n}\n","import { InjectionToken } from '@angular/core';\n\n/**\n * IAM Permission Mode - controls permission system behavior\n */\nexport type PermissionMode = 'rbac' | 'direct' | 'full';\n\n/**\n * Service Configuration Interface\n */\nexport interface IServiceConfig {\n baseUrl: string;\n enabled: boolean;\n}\n\n/**\n * Storage Service Configuration Interface\n * Extends base service config with storage-specific options\n */\nexport interface IStorageServiceConfig extends IServiceConfig {\n /**\n * Default storage config ID from backend storage_config table\n * Used when no storageConfigId is provided to upload operations\n * If not set, backend will use company/branch default config\n */\n defaultStorageConfigId?: string;\n}\n\n/**\n * Author/Brand Configuration\n */\nexport interface IAuthorConfig {\n name: string;\n url: string;\n}\n\n/**\n * App Configuration Interface\n * Centralized configuration for feature toggles and behavior control\n */\nexport interface IAppConfig {\n /** Application name */\n appName: string;\n\n /** Author/Brand info for footer attribution */\n author?: IAuthorConfig;\n\n /** Default API base URL (fallback) */\n apiBaseUrl: string; \n \n /**\n * Enable company feature across the app\n * When true, enables company selection/switching in layout and feature modules\n */\n enableCompanyFeature: boolean;\n\n /**\n * Service-specific base URLs\n * If a service exists in this object, its feature is enabled\n * Database mode is determined by multiTenant.enabled\n */\n services: {\n auth: IServiceConfig;\n iam: IServiceConfig;\n storage: IStorageServiceConfig;\n };\n\n /**\n * Multi-tenant configuration\n * enabled: true → Database mode = multi-tenant\n * enabled: false → Database mode = single\n */\n multiTenant: {\n enabled: boolean;\n tenantHeader: string;\n tenantId?: string;\n };\n\n /**\n * IAM Permission Mode - controls permission system behavior\n * - 'rbac': Role-Based Access Control only (users get permissions via roles)\n * - 'direct': Direct action assignments only (actions assigned directly to users)\n * - 'full': Both RBAC and direct permissions (most flexible, default)\n */\n permissionMode?: PermissionMode;\n}\n\n/**\n * App Configuration Token\n * Use this to inject app config in services/components\n */\nexport const APP_CONFIG = new InjectionToken<IAppConfig>('APP_CONFIG');\n\n/**\n * Get database mode from config\n * Derived from multiTenant.enabled\n */\nexport function getDatabaseMode(config: IAppConfig): 'single' | 'multi-tenant' {\n return config.multiTenant.enabled ? 'multi-tenant' : 'single';\n}\n\n/**\n * Check if a feature is enabled\n * Features are enabled if their corresponding service exists and is enabled\n */\nexport function isFeatureEnabled(\n config: IAppConfig,\n feature: 'auth' | 'iam' | 'storage',\n): boolean {\n const service = config.services[feature];\n return service?.enabled ?? false;\n}\n\n/**\n * Helper function to get service base URL\n * Returns service-specific URL or falls back to default apiBaseUrl\n */\nexport function getServiceUrl(\n config: IAppConfig,\n serviceName: 'auth' | 'iam' | 'storage' | (string & {}),\n): string {\n const service = config.services[serviceName as keyof typeof config.services];\n if (service?.enabled) {\n return service.baseUrl;\n }\n // Fallback to default API base URL\n return config.apiBaseUrl;\n}\n\n/**\n * Helper function to check if a service is enabled\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\n/**\n * Check if company feature is enabled\n * When enabled, company selection/switching is available across the app\n */\nexport function isCompanyFeatureEnabled(config: IAppConfig): boolean {\n return config.enableCompanyFeature ?? false;\n}\n\n/**\n * Check if storage feature is enabled (backward compatible)\n * Storage feature is enabled if storage service is enabled\n */\nexport function isStorageFeatureEnabled(config: IAppConfig): boolean {\n return config.services.storage?.enabled ?? false;\n}\n\n/**\n * Get permission mode from config\n * Defaults to 'full' if not specified\n */\nexport function getPermissionMode(config: IAppConfig): PermissionMode {\n return config.permissionMode ?? 'full';\n}\n\n/**\n * Get default storage config ID from config\n * Used for file upload operations when no storageConfigId is provided\n */\nexport function getDefaultStorageConfigId(config: IAppConfig): string | undefined {\n return config.services.storage?.defaultStorageConfigId;\n}\n\n// Default branding constants\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/lib-app-config/lib-app-config.component';\n\n// Interceptors\nexport * from './interceptors/api-loader.interceptor';\nexport * from './interceptors/error-catching.interceptor';\n\n// Interfaces\nexport * from './interfaces/app-config.interface';\n\n// Services\nexport * from './services/api-loader.service';\nexport * from './services/base-api.service';\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/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 { ChangeDetectionStrategy, 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 changeDetection: ChangeDetectionStrategy.OnPush,\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 {\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';\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 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);\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, error, statusCode }\n */\nfunction extractErrorInfo(error: HttpErrorResponse): { summary: string; detail: string } {\n // Extract message from backend response\n let detail: string;\n 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 = 'An unexpected error occurred.';\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);\n }\n\n return { summary, detail };\n}\n\n/**\n * Get default error summary based on HTTP status code\n */\nfunction getDefaultSummary(status: number): string {\n switch (status) {\n case 400:\n return 'Bad Request';\n case 404:\n return 'Not Found';\n case 409:\n return 'Conflict';\n case 422:\n return 'Validation Error';\n case 500:\n return 'Server Error';\n case 502:\n case 503:\n case 504:\n return 'Service Unavailable';\n default:\n return 'Error';\n }\n}\n","import { InjectionToken } from '@angular/core';\n\n/**\n * IAM Permission Mode - controls permission system behavior\n */\nexport type PermissionMode = 'rbac' | 'direct' | 'full';\n\n/**\n * Service Configuration Interface\n */\nexport interface IServiceConfig {\n baseUrl: string;\n enabled: boolean;\n}\n\n/**\n * Storage Service Configuration Interface\n * Extends base service config with storage-specific options\n */\nexport interface IStorageServiceConfig extends IServiceConfig {\n /**\n * Default storage config ID from backend storage_config table\n * Used when no storageConfigId is provided to upload operations\n * If not set, backend will use company/branch default config\n */\n defaultStorageConfigId?: string;\n}\n\n/**\n * Author/Brand Configuration\n */\nexport interface IAuthorConfig {\n name: string;\n url: string;\n}\n\n/**\n * App Configuration Interface\n * Centralized configuration for feature toggles and behavior control\n */\nexport interface IAppConfig {\n /** Application name */\n appName: string;\n\n /** Author/Brand info for footer attribution */\n author?: IAuthorConfig;\n\n /** Default API base URL (fallback) */\n apiBaseUrl: string; \n \n /**\n * Enable company feature across the app\n * When true, enables company selection/switching in layout and feature modules\n */\n enableCompanyFeature: boolean;\n\n /**\n * Service-specific base URLs\n * If a service exists in this object, its feature is enabled\n * Database mode is determined by multiTenant.enabled\n */\n services: {\n auth: IServiceConfig;\n administration: IServiceConfig;\n iam: IServiceConfig;\n storage: IStorageServiceConfig;\n formBuilder: IServiceConfig;\n email: IServiceConfig;\n };\n\n /**\n * Multi-tenant configuration\n * enabled: true → Database mode = multi-tenant\n * enabled: false → Database mode = single\n */\n multiTenant: {\n enabled: boolean;\n tenantHeader: string;\n tenantId?: string;\n };\n\n /**\n * IAM Permission Mode - controls permission system behavior\n * - 'rbac': Role-Based Access Control only (users get permissions via roles)\n * - 'direct': Direct action assignments only (actions assigned directly to users)\n * - 'full': Both RBAC and direct permissions (most flexible, default)\n */\n permissionMode?: PermissionMode;\n}\n\n/**\n * App Configuration Token\n * Use this to inject app config in services/components\n */\nexport const APP_CONFIG = new InjectionToken<IAppConfig>('APP_CONFIG');\n\n/**\n * Get database mode from config\n * Derived from multiTenant.enabled\n */\nexport function getDatabaseMode(config: IAppConfig): 'single' | 'multi-tenant' {\n return config.multiTenant.enabled ? 'multi-tenant' : 'single';\n}\n\n/**\n * Check if a service is enabled\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\n/**\n * Check if a feature is enabled (alias for isServiceEnabled with strict types)\n */\nexport function isFeatureEnabled(\n config: IAppConfig,\n feature: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email',\n): boolean {\n return isServiceEnabled(config, feature);\n}\n\n/**\n * Get service base URL or fallback to default apiBaseUrl\n */\nexport function getServiceUrl(\n config: IAppConfig,\n serviceName: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | (string & {}),\n): string {\n if (isServiceEnabled(config, serviceName)) {\n const service = config.services[serviceName as keyof typeof config.services];\n return service.baseUrl;\n }\n return config.apiBaseUrl;\n}\n\n/**\n * Check if company feature is enabled\n */\nexport function isCompanyFeatureEnabled(config: IAppConfig): boolean {\n return config.enableCompanyFeature ?? false;\n}\n\n/**\n * Check if storage feature is enabled\n */\nexport function isStorageFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'storage');\n}\n\n/**\n * Check if form-builder feature is enabled\n */\nexport function isFormBuilderFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'formBuilder');\n}\n\n/**\n * Check if email feature is enabled\n */\nexport function isEmailFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'email');\n}\n\n/**\n * Get permission mode from config\n * Defaults to 'full' if not specified\n */\nexport function getPermissionMode(config: IAppConfig): PermissionMode {\n return config.permissionMode ?? 'full';\n}\n\n/**\n * Get default storage config ID from config\n * Used for file upload operations when no storageConfigId is provided\n */\nexport function getDefaultStorageConfigId(config: IAppConfig): string | undefined {\n return config.services.storage?.defaultStorageConfigId;\n}\n\n// Default branding constants\nexport const DEFAULT_APP_NAME = 'FLUSYS';\nexport const DEFAULT_AUTHOR: IAuthorConfig = {\n name: 'Flusys',\n url: 'https://flusys.vercel.app',\n};\n","import { inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { APP_CONFIG, getServiceUrl } from '../interfaces/app-config.interface';\n\n/**\n * Base API Service\n * Provides service-specific base URL resolution\n *\n * Note: This project uses POST-only RPC - all endpoints use POST, not REST verbs.\n *\n * Usage:\n * ```typescript\n * @Injectable({ providedIn: 'root' })\n * export class MyService extends BaseApiService {\n * constructor() {\n * super('my-service'); // Service name from config\n * }\n *\n * getData() {\n * return this.http.post(`${this.baseUrl}/data/get`, {});\n * }\n * }\n * ```\n */\nexport abstract class BaseApiService {\n protected readonly http = inject(HttpClient);\n protected readonly appConfig = inject(APP_CONFIG);\n protected readonly baseUrl: string;\n\n /**\n * @param serviceName - Service name from config (e.g., 'auth', 'iam', 'storage')\n */\n constructor(serviceName: string) {\n this.baseUrl = getServiceUrl(this.appConfig, serviceName);\n }\n\n /**\n * Get full URL for an endpoint\n * @param endpoint - API endpoint (e.g., '/users/get-all')\n * @returns Full URL (e.g., 'http://localhost:2002/users/get-all')\n */\n protected getUrl(endpoint: string): string {\n // Remove leading slash if present to avoid double slashes\n const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;\n return `${this.baseUrl}/${cleanEndpoint}`;\n }\n}\n","// Components\nexport * from './components/public-api';\n\n// Interceptors\nexport * from './interceptors/public-api';\n\n// Interfaces\nexport * from './interfaces/public-api';\n\n// Services\nexport * from './services/public-api';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@flusys/ng-core",
3
- "version": "1.0.0-rc",
3
+ "version": "1.0.1",
4
4
  "description": "Core utilities and services for FLUSYS Angular packages",
5
5
  "license": "MIT",
6
6
  "peerDependencies": {
7
- "@angular/common": "^21.0.0",
8
- "@angular/core": "^21.0.0",
9
- "@primeuix/themes": "^1.0.0",
10
- "primeicons": "^7.0.0",
11
- "primeng": "^21.0.0"
7
+ "@angular/common": ">=21.0.0",
8
+ "@angular/core": ">=21.0.0",
9
+ "@primeuix/themes": ">=1.0.0",
10
+ "primeicons": ">=7.0.0",
11
+ "primeng": ">=21.0.0"
12
12
  },
13
13
  "sideEffects": false,
14
14
  "module": "fesm2022/flusys-ng-core.mjs",
@@ -25,4 +25,4 @@
25
25
  "dependencies": {
26
26
  "tslib": "^2.3.0"
27
27
  }
28
- }
28
+ }
@@ -133,8 +133,11 @@ interface IAppConfig {
133
133
  */
134
134
  services: {
135
135
  auth: IServiceConfig;
136
+ administration: IServiceConfig;
136
137
  iam: IServiceConfig;
137
138
  storage: IStorageServiceConfig;
139
+ formBuilder: IServiceConfig;
140
+ email: IServiceConfig;
138
141
  };
139
142
  /**
140
143
  * Multi-tenant configuration
@@ -165,29 +168,33 @@ declare const APP_CONFIG: InjectionToken<IAppConfig>;
165
168
  */
166
169
  declare function getDatabaseMode(config: IAppConfig): 'single' | 'multi-tenant';
167
170
  /**
168
- * Check if a feature is enabled
169
- * Features are enabled if their corresponding service exists and is enabled
171
+ * Check if a service is enabled
170
172
  */
171
- declare function isFeatureEnabled(config: IAppConfig, feature: 'auth' | 'iam' | 'storage'): boolean;
173
+ declare function isServiceEnabled(config: IAppConfig, serviceName: 'auth' | 'iam' | 'storage' | (string & {})): boolean;
172
174
  /**
173
- * Helper function to get service base URL
174
- * Returns service-specific URL or falls back to default apiBaseUrl
175
+ * Check if a feature is enabled (alias for isServiceEnabled with strict types)
175
176
  */
176
- declare function getServiceUrl(config: IAppConfig, serviceName: 'auth' | 'iam' | 'storage' | (string & {})): string;
177
+ declare function isFeatureEnabled(config: IAppConfig, feature: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email'): boolean;
177
178
  /**
178
- * Helper function to check if a service is enabled
179
+ * Get service base URL or fallback to default apiBaseUrl
179
180
  */
180
- declare function isServiceEnabled(config: IAppConfig, serviceName: 'auth' | 'iam' | 'storage' | (string & {})): boolean;
181
+ declare function getServiceUrl(config: IAppConfig, serviceName: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | (string & {})): string;
181
182
  /**
182
183
  * Check if company feature is enabled
183
- * When enabled, company selection/switching is available across the app
184
184
  */
185
185
  declare function isCompanyFeatureEnabled(config: IAppConfig): boolean;
186
186
  /**
187
- * Check if storage feature is enabled (backward compatible)
188
- * Storage feature is enabled if storage service is enabled
187
+ * Check if storage feature is enabled
189
188
  */
190
189
  declare function isStorageFeatureEnabled(config: IAppConfig): boolean;
190
+ /**
191
+ * Check if form-builder feature is enabled
192
+ */
193
+ declare function isFormBuilderFeatureEnabled(config: IAppConfig): boolean;
194
+ /**
195
+ * Check if email feature is enabled
196
+ */
197
+ declare function isEmailFeatureEnabled(config: IAppConfig): boolean;
191
198
  /**
192
199
  * Get permission mode from config
193
200
  * Defaults to 'full' if not specified
@@ -237,5 +244,5 @@ declare abstract class BaseApiService {
237
244
  protected getUrl(endpoint: string): string;
238
245
  }
239
246
 
240
- export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, SKIP_ERROR_TOAST, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultStorageConfigId, getPermissionMode, getServiceUrl, isCompanyFeatureEnabled, isFeatureEnabled, isServiceEnabled, isStorageFeatureEnabled };
247
+ export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, SKIP_ERROR_TOAST, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultStorageConfigId, getPermissionMode, getServiceUrl, isCompanyFeatureEnabled, isEmailFeatureEnabled, isFeatureEnabled, isFormBuilderFeatureEnabled, isServiceEnabled, isStorageFeatureEnabled };
241
248
  export type { IAppConfig, IAuthorConfig, IServiceConfig, IStorageServiceConfig, PermissionMode };