@mysteryinfosolutions/api-core 1.8.0 → 1.9.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/index.d.ts CHANGED
@@ -1,8 +1,36 @@
1
1
  import * as i0 from '@angular/core';
2
+ import { InjectionToken } from '@angular/core';
2
3
  import { Observable } from 'rxjs';
3
- import { HttpClient } from '@angular/common/http';
4
+ import { HttpClient, HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
4
5
 
5
- interface TableColumn<T = any> {
6
+ /**
7
+ * Configuration for a table column.
8
+ *
9
+ * @template T The type of data row this column applies to
10
+ *
11
+ * @example
12
+ * const columns: TableColumn<User>[] = [
13
+ * {
14
+ * key: 'name',
15
+ * label: 'Full Name',
16
+ * isSortable: true,
17
+ * width: '200px'
18
+ * },
19
+ * {
20
+ * key: 'email',
21
+ * label: 'Email Address',
22
+ * type: 'text',
23
+ * hideOnMobile: true
24
+ * },
25
+ * {
26
+ * key: 'createdAt',
27
+ * label: 'Created',
28
+ * pipe: 'date',
29
+ * valueGetter: (row) => row.createdAt
30
+ * }
31
+ * ];
32
+ */
33
+ interface TableColumn<T = Record<string, unknown>> {
6
34
  /** Unique key used to identify the column */
7
35
  key: keyof T | string;
8
36
  /** Display label for the column header */
@@ -13,74 +41,252 @@ interface TableColumn<T = any> {
13
41
  width?: string;
14
42
  /** Type of cell rendering */
15
43
  type?: 'text' | 'checkbox' | 'action' | 'custom';
16
- /** Function to extract value from row */
17
- valueGetter?: (row: T) => any;
44
+ /** Function to extract value from row - return type can be any displayable value */
45
+ valueGetter?: (row: T) => string | number | boolean | Date | null | undefined;
18
46
  /** Whether to hide the column on mobile screen sizes */
19
47
  hideOnMobile?: boolean;
20
48
  /** Optional Angular pipe to apply to the value */
21
- pipe?: 'date' | 'currency' | 'uppercase' | 'lowercase' | string;
49
+ pipe?: 'date' | 'currency' | 'uppercase' | 'lowercase' | 'number' | 'percent' | string;
22
50
  /** CSS class to apply to cell */
23
51
  cellClass?: string;
24
52
  /** CSS class to apply to header */
25
53
  headerClass?: string;
26
54
  /** Whether the column should be visible at all (default: true) */
27
55
  visible?: boolean;
56
+ /** Alignment for cell content */
57
+ align?: 'left' | 'center' | 'right';
58
+ /** Whether the column is sticky (remains visible when scrolling) */
59
+ sticky?: boolean;
28
60
  }
29
61
 
62
+ /**
63
+ * Enum defining column selection modes for API queries.
64
+ *
65
+ * @remarks
66
+ * Used to control which columns are returned in API responses,
67
+ * useful for optimizing payload size and performance.
68
+ *
69
+ * @example
70
+ * const filter = {
71
+ * selectMode: SELECT_MODE.ONLYCOLUMNS,
72
+ * selectColumns: 'id,name,email'
73
+ * };
74
+ */
30
75
  declare enum SELECT_MODE {
76
+ /** Return all columns (default behavior) */
31
77
  ALL = 1,
78
+ /** Return only specified columns via selectColumns parameter */
32
79
  ONLYCOLUMNS = 2
33
80
  }
34
81
 
82
+ /**
83
+ * Base filter class providing common filtering, pagination, and sorting capabilities.
84
+ *
85
+ * @remarks
86
+ * Extend this class in your custom filter types to add domain-specific filter properties.
87
+ * The base filter handles pagination, sorting, searching, and date range filtering.
88
+ *
89
+ * @example
90
+ * interface UserFilter extends Filter {
91
+ * role?: string;
92
+ * isActive?: boolean;
93
+ * departmentId?: number;
94
+ * }
95
+ *
96
+ * const filter: UserFilter = {
97
+ * page: 1,
98
+ * pageLength: 25,
99
+ * search: 'john',
100
+ * role: 'admin',
101
+ * sort: [new SortItem('name', 'ASC')]
102
+ * };
103
+ */
35
104
  declare abstract class Filter {
105
+ /** Array of specific IDs to filter by */
36
106
  ids?: number[];
107
+ /** Column name to use for date range filtering */
37
108
  dateRangeColumn?: string;
109
+ /** Start date for date range filter (ISO format) */
38
110
  dateRangeFrom?: string;
111
+ /** End date for date range filter (ISO format) */
39
112
  dateRangeTo?: string;
113
+ /** Current page number (1-based) */
40
114
  page?: number;
115
+ /** Number of records per page */
41
116
  pageLength?: number;
117
+ /** Array of sort configurations */
42
118
  sort?: SortItem[];
119
+ /** Search query string */
43
120
  search?: string;
121
+ /** Comma-separated list of columns to search in */
44
122
  searchColumns?: string;
123
+ /** Comma-separated list of columns to select/return */
45
124
  selectColumns?: string;
125
+ /** Mode for column selection */
46
126
  selectMode?: SELECT_MODE;
47
- [key: string]: string | number | number[] | SortItem[] | SELECT_MODE | boolean | undefined;
48
127
  }
49
128
 
129
+ /**
130
+ * Standard error object returned by the API.
131
+ *
132
+ * @example
133
+ * {
134
+ * code: 'VALIDATION_ERROR',
135
+ * message: 'Invalid email address',
136
+ * details: 'Email must be in valid format',
137
+ * showMessageToUser: true
138
+ * }
139
+ */
50
140
  interface IMisError {
141
+ /** Short error identifier */
51
142
  error?: string;
143
+ /** Error code for programmatic handling */
52
144
  code?: string;
145
+ /** Human-readable error message */
53
146
  message?: string;
147
+ /** Detailed error information */
54
148
  details?: string;
55
- stack?: any;
149
+ /** Stack trace (only in development) */
150
+ stack?: string | Record<string, unknown>;
151
+ /** Default fallback message */
56
152
  defaultMessage?: string;
153
+ /** Whether this error should be displayed to the user */
57
154
  showMessageToUser?: boolean;
58
155
  }
156
+ /**
157
+ * Pagination metadata returned with multi-result responses.
158
+ *
159
+ * @example
160
+ * {
161
+ * totalRecords: 150,
162
+ * currentPage: 2,
163
+ * lastPage: 15,
164
+ * perPage: 10,
165
+ * next: 3,
166
+ * previous: 1
167
+ * }
168
+ */
59
169
  interface IMultiresultMetaData {
170
+ /** Total number of records across all pages */
60
171
  totalRecords: number;
172
+ /** Previous page number (if exists) */
61
173
  previous?: number;
174
+ /** Current page number */
62
175
  currentPage?: number;
176
+ /** Next page number (if exists) */
63
177
  next?: number;
178
+ /** Number of records per page */
64
179
  perPage?: number;
180
+ /** Current segment/batch number */
65
181
  segment?: number;
182
+ /** Last page number */
66
183
  lastPage?: number;
67
184
  }
185
+ /**
186
+ * Container for paginated list responses.
187
+ *
188
+ * @template T The type of records in the list
189
+ *
190
+ * @example
191
+ * const response: IMultiresult<User> = {
192
+ * records: [
193
+ * { id: 1, name: 'John' },
194
+ * { id: 2, name: 'Jane' }
195
+ * ],
196
+ * pager: {
197
+ * totalRecords: 100,
198
+ * currentPage: 1,
199
+ * lastPage: 10,
200
+ * perPage: 10
201
+ * }
202
+ * };
203
+ */
68
204
  interface IMultiresult<T> {
205
+ /** Array of records for the current page */
69
206
  records: T[];
207
+ /** Pagination metadata */
70
208
  pager: IMultiresultMetaData;
71
209
  }
210
+ /**
211
+ * Standard API response wrapper.
212
+ *
213
+ * @template T The type of data contained in the response
214
+ *
215
+ * @example
216
+ * // Success response
217
+ * const response: IResponse<User> = {
218
+ * status: 200,
219
+ * data: { id: 1, name: 'John' },
220
+ * infoDtls: null
221
+ * };
222
+ *
223
+ * // Error response
224
+ * const errorResponse: IResponse<User> = {
225
+ * status: 400,
226
+ * data: null,
227
+ * error: {
228
+ * code: 'VALIDATION_ERROR',
229
+ * message: 'Invalid input'
230
+ * }
231
+ * };
232
+ */
72
233
  interface IResponse<T> {
234
+ /** HTTP status code */
73
235
  status?: number;
236
+ /** Response data (null if error occurred) */
74
237
  data?: T | null;
238
+ /** Error information (present if request failed) */
75
239
  error?: IMisError;
76
- infoDtls?: any;
240
+ /** Additional information or metadata */
241
+ infoDtls?: Record<string, unknown> | null;
77
242
  }
243
+ /**
244
+ * Type alias for paginated list responses.
245
+ *
246
+ * @template T The type of records in the list
247
+ */
78
248
  type PagedResponse<T> = IResponse<IMultiresult<T>>;
249
+ /**
250
+ * Type alias for single record responses.
251
+ *
252
+ * @template T The type of the record
253
+ */
79
254
  type SingleResponse<T> = IResponse<T>;
80
255
 
256
+ /**
257
+ * Represents a single sort configuration for a field.
258
+ *
259
+ * @remarks
260
+ * Used in filter objects to specify sorting criteria.
261
+ * Multiple SortItem instances can be combined for multi-column sorting.
262
+ *
263
+ * @example
264
+ * // Single sort
265
+ * const sort = new SortItem('name', 'ASC');
266
+ *
267
+ * @example
268
+ * // Multi-column sort
269
+ * const sorts = [
270
+ * new SortItem('priority', 'DESC'),
271
+ * new SortItem('createdAt', 'DESC'),
272
+ * new SortItem('name', 'ASC')
273
+ * ];
274
+ *
275
+ * const filter = {
276
+ * page: 1,
277
+ * pageLength: 25,
278
+ * sort: sorts
279
+ * };
280
+ */
81
281
  declare class SortItem {
82
282
  field: string;
83
283
  order: 'ASC' | 'DESC';
284
+ /**
285
+ * Creates a sort configuration.
286
+ *
287
+ * @param field The field/column name to sort by
288
+ * @param order Sort direction: 'ASC' (ascending) or 'DESC' (descending)
289
+ */
84
290
  constructor(field: string, order: 'ASC' | 'DESC');
85
291
  }
86
292
 
@@ -150,6 +356,143 @@ declare abstract class BaseResourceConfig<T> {
150
356
  permissions: Record<StandardPermission | string, string>;
151
357
  }
152
358
 
359
+ /**
360
+ * Configuration options for the api-core library.
361
+ *
362
+ * @example
363
+ * // In your app.config.ts or module providers
364
+ * import { API_CORE_CONFIG, ApiCoreConfig } from '@mysteryinfosolutions/api-core';
365
+ *
366
+ * export const appConfig: ApplicationConfig = {
367
+ * providers: [
368
+ * {
369
+ * provide: API_CORE_CONFIG,
370
+ * useValue: {
371
+ * pagination: {
372
+ * defaultPage: 1,
373
+ * defaultPageLength: 25,
374
+ * pageLengthOptions: [25, 50, 100]
375
+ * },
376
+ * api: {
377
+ * dateFormat: 'iso',
378
+ * includeCredentials: true
379
+ * }
380
+ * } as ApiCoreConfig
381
+ * }
382
+ * ]
383
+ * };
384
+ */
385
+ interface ApiCoreConfig {
386
+ /**
387
+ * Pagination configuration defaults
388
+ */
389
+ pagination?: {
390
+ /** Default page number (1-based) */
391
+ defaultPage?: number;
392
+ /** Default number of items per page */
393
+ defaultPageLength?: number;
394
+ /** Available page length options for user selection */
395
+ pageLengthOptions?: number[];
396
+ /** Maximum allowed page length (to prevent large requests) */
397
+ maxPageLength?: number;
398
+ };
399
+ /**
400
+ * API request/response configuration
401
+ */
402
+ api?: {
403
+ /** Date format for API requests ('iso' | 'timestamp' | 'custom') */
404
+ dateFormat?: 'iso' | 'timestamp' | 'custom';
405
+ /** Custom date formatter function (if dateFormat is 'custom') */
406
+ dateFormatter?: (date: Date) => string;
407
+ /** Whether to include credentials in requests */
408
+ includeCredentials?: boolean;
409
+ /** Default headers to include in all requests */
410
+ defaultHeaders?: Record<string, string>;
411
+ /** Request timeout in milliseconds */
412
+ timeout?: number;
413
+ };
414
+ /**
415
+ * Query string formatting options
416
+ */
417
+ queryString?: {
418
+ /** Format for array parameters ('brackets' | 'comma' | 'repeat') */
419
+ arrayFormat?: 'brackets' | 'comma' | 'repeat';
420
+ /** Custom array formatter function */
421
+ arrayFormatter?: (key: string, values: unknown[]) => string;
422
+ /** Whether to encode query parameters */
423
+ encode?: boolean;
424
+ /** Custom encoder function */
425
+ encoder?: (value: string) => string;
426
+ };
427
+ /**
428
+ * Sort configuration
429
+ */
430
+ sort?: {
431
+ /** Default sort order */
432
+ defaultOrder?: 'ASC' | 'DESC';
433
+ /** Separator between field and order in query string */
434
+ separator?: string;
435
+ /** Whether to allow multi-column sorting */
436
+ allowMultiSort?: boolean;
437
+ };
438
+ /**
439
+ * Error handling configuration
440
+ */
441
+ errorHandling?: {
442
+ /** Whether to log errors to console */
443
+ logErrors?: boolean;
444
+ /** Whether to show user-friendly error messages */
445
+ showUserMessages?: boolean;
446
+ /** Global error handler function */
447
+ onError?: (error: unknown) => void;
448
+ };
449
+ /**
450
+ * Loading state configuration
451
+ */
452
+ loading?: {
453
+ /** Minimum loading time in ms (prevents flash of loading state) */
454
+ minLoadingTime?: number;
455
+ /** Whether to show loading indicators by default */
456
+ showLoadingByDefault?: boolean;
457
+ };
458
+ }
459
+ /**
460
+ * Default configuration values for the api-core library.
461
+ */
462
+ declare const DEFAULT_API_CORE_CONFIG: Required<ApiCoreConfig>;
463
+ /**
464
+ * Injection token for api-core configuration.
465
+ *
466
+ * @example
467
+ * // Provide custom configuration
468
+ * providers: [
469
+ * {
470
+ * provide: API_CORE_CONFIG,
471
+ * useValue: {
472
+ * pagination: { defaultPageLength: 25 }
473
+ * } as ApiCoreConfig
474
+ * }
475
+ * ]
476
+ *
477
+ * @example
478
+ * // Inject in service
479
+ * constructor(@Inject(API_CORE_CONFIG) private config: ApiCoreConfig) {}
480
+ */
481
+ declare const API_CORE_CONFIG: InjectionToken<ApiCoreConfig>;
482
+ /**
483
+ * Merges user-provided configuration with default values.
484
+ *
485
+ * @param userConfig User-provided configuration
486
+ * @returns Merged configuration with defaults
487
+ *
488
+ * @example
489
+ * const config = mergeConfig({
490
+ * pagination: { defaultPageLength: 25 }
491
+ * });
492
+ * // Returns full config with defaultPageLength: 25 and all other defaults
493
+ */
494
+ declare function mergeConfig(userConfig?: ApiCoreConfig): Required<ApiCoreConfig>;
495
+
153
496
  declare class ApiCore {
154
497
  static ɵfac: i0.ɵɵFactoryDeclaration<ApiCore, never>;
155
498
  static ɵcmp: i0.ɵɵComponentDeclaration<ApiCore, "lib-api-core", never, {}, {}, never, never, true, never>;
@@ -174,12 +517,139 @@ declare class ApiCore {
174
517
  */
175
518
  declare function generatePermissions<T extends string = StandardPermission>(resource: string, extra?: T[]): Record<T | StandardPermission, string>;
176
519
 
520
+ /**
521
+ * Converts an array of SortItem objects into a comma-separated sort string.
522
+ *
523
+ * @param sortItems Array of sort configurations
524
+ * @returns Formatted sort string (e.g., "name:ASC,createdAt:DESC") or empty string
525
+ *
526
+ * @example
527
+ * const sorts = [
528
+ * new SortItem('name', 'ASC'),
529
+ * new SortItem('createdAt', 'DESC')
530
+ * ];
531
+ * const sortString = sortObjectToString(sorts);
532
+ * // Returns: "name:ASC,createdAt:DESC"
533
+ */
177
534
  declare const sortObjectToString: (sortItems: SortItem[]) => string;
178
- declare const jsonToQueryString: (json: any) => string;
179
- declare const isEmpty: (obj: any) => boolean;
535
+ /**
536
+ * Converts a filter object into a URL query string.
537
+ *
538
+ * @param filter Filter object with query parameters
539
+ * @returns URL query string with '?' prefix, or empty string if no parameters
540
+ *
541
+ * @remarks
542
+ * - Automatically converts SortItem arrays to sort strings
543
+ * - Encodes all values for URL safety
544
+ * - Filters out undefined and null values
545
+ * - Arrays are encoded as comma-separated values in brackets
546
+ *
547
+ * @example
548
+ * const filter = {
549
+ * page: 1,
550
+ * pageLength: 25,
551
+ * search: 'John Doe',
552
+ * roles: ['admin', 'user'],
553
+ * sort: [new SortItem('name', 'ASC')]
554
+ * };
555
+ * const queryString = jsonToQueryString(filter);
556
+ * // Returns: "?page=1&pageLength=25&search=John%20Doe&roles=[admin,user]&sort=name:ASC"
557
+ */
558
+ declare const jsonToQueryString: (filter: Record<string, unknown>) => string;
559
+ /**
560
+ * Checks if an object is empty (has no own properties).
561
+ *
562
+ * @param obj Object to check (any object type, null, or undefined)
563
+ * @returns True if object is null, undefined, or has no properties
564
+ *
565
+ * @example
566
+ * isEmpty({}); // true
567
+ * isEmpty(null); // true
568
+ * isEmpty(undefined); // true
569
+ * isEmpty({ page: 1 }); // false
570
+ * isEmpty([]); // true (arrays with no length)
571
+ *
572
+ * @example
573
+ * // Works with any object type
574
+ * interface MyType { id: number; name: string; }
575
+ * const myObj: MyType = { id: 1, name: 'test' };
576
+ * isEmpty(myObj); // false
577
+ */
578
+ declare const isEmpty: (obj: object | null | undefined) => boolean;
180
579
 
181
580
  /**
182
- * A generic state management service for handling filters, pagination, and records.
581
+ * Generic reactive state management service for data-driven features.
582
+ *
583
+ * @template TRecord The type of record/entity being managed
584
+ * @template TFilter Filter type extending Partial<TRecord> & Filter
585
+ *
586
+ * @remarks
587
+ * BaseStateService provides comprehensive state management for list-based views including:
588
+ * - Filter state with pagination and sorting
589
+ * - Records collection with CRUD helpers
590
+ * - Loading states (with context keys)
591
+ * - Error handling
592
+ * - Record selection
593
+ * - Pagination metadata
594
+ *
595
+ * All state is reactive using RxJS BehaviorSubjects, making it easy to integrate
596
+ * with Angular templates using the async pipe.
597
+ *
598
+ * @example
599
+ * // Basic setup in component
600
+ * @Component({
601
+ * selector: 'app-users',
602
+ * providers: [BaseStateService] // Component-level instance
603
+ * })
604
+ * export class UsersComponent implements OnInit, OnDestroy {
605
+ * state = new BaseStateService<User, UserFilter>();
606
+ *
607
+ * constructor(private userService: UserService) {}
608
+ *
609
+ * ngOnInit() {
610
+ * // Subscribe to filter changes
611
+ * this.state.filter$.subscribe(filter => {
612
+ * this.loadUsers(filter);
613
+ * });
614
+ *
615
+ * // Set initial filter
616
+ * this.state.setFilter({ page: 1, pageLength: 25 });
617
+ * }
618
+ *
619
+ * loadUsers(filter: UserFilter) {
620
+ * this.state.setLoading('list', true);
621
+ *
622
+ * this.userService.getAll(filter).subscribe({
623
+ * next: (response) => {
624
+ * if (response.data) {
625
+ * this.state.setApiResponse(response.data);
626
+ * }
627
+ * this.state.setLoading('list', false);
628
+ * },
629
+ * error: (err) => {
630
+ * this.state.setError('Failed to load users');
631
+ * this.state.setLoading('list', false);
632
+ * }
633
+ * });
634
+ * }
635
+ *
636
+ * ngOnDestroy() {
637
+ * this.state.destroy();
638
+ * }
639
+ * }
640
+ *
641
+ * @example
642
+ * // Using in template
643
+ * <div *ngIf="state.isLoading$('list') | async">Loading...</div>
644
+ * <div *ngIf="state.error$ | async as error">{{ error }}</div>
645
+ *
646
+ * <div *ngFor="let user of state.records$ | async">
647
+ * {{ user.name }}
648
+ * </div>
649
+ *
650
+ * <div *ngIf="state.pager$ | async as pager">
651
+ * Page {{ pager.currentPage }} of {{ pager.lastPage }}
652
+ * </div>
183
653
  */
184
654
  declare class BaseStateService<TRecord, TFilter extends Partial<TRecord> & Filter = Partial<TRecord> & Filter> {
185
655
  private readonly filterSubject;
@@ -188,17 +658,35 @@ declare class BaseStateService<TRecord, TFilter extends Partial<TRecord> & Filte
188
658
  private readonly selectedSubject;
189
659
  private readonly loadingMapSubject;
190
660
  private readonly errorSubject;
191
- /** Observable stream of current filter. */
661
+ /**
662
+ * Observable stream of current filter.
663
+ * Optimized with distinctUntilChanged to prevent duplicate emissions.
664
+ */
192
665
  filter$: Observable<TFilter>;
193
- /** Observable stream of current records. */
666
+ /**
667
+ * Observable stream of current records.
668
+ * Optimized with distinctUntilChanged for reference equality.
669
+ */
194
670
  records$: Observable<TRecord[]>;
195
- /** Observable stream of current pager metadata. */
671
+ /**
672
+ * Observable stream of current pager metadata.
673
+ * Optimized with distinctUntilChanged for deep equality.
674
+ */
196
675
  pager$: Observable<IMultiresultMetaData | null>;
197
- /** Observable stream of the currently selected record. */
676
+ /**
677
+ * Observable stream of the currently selected record.
678
+ * Optimized with distinctUntilChanged for reference equality.
679
+ */
198
680
  selected$: Observable<TRecord | null>;
199
- /** Observable stream of loading state. */
681
+ /**
682
+ * Observable stream of loading state.
683
+ * Optimized with distinctUntilChanged for deep equality.
684
+ */
200
685
  loading$: Observable<Record<string, boolean>>;
201
- /** Observable stream of current error message. */
686
+ /**
687
+ * Observable stream of current error message.
688
+ * Optimized with distinctUntilChanged for value equality.
689
+ */
202
690
  error$: Observable<string | null>;
203
691
  /** Returns the current filter. */
204
692
  get currentFilter(): TFilter;
@@ -232,10 +720,25 @@ declare class BaseStateService<TRecord, TFilter extends Partial<TRecord> & Filte
232
720
  */
233
721
  setApiResponse(response: IMultiresult<TRecord>): void;
234
722
  /**
235
- * Updates the sort order in the current filter.
236
- * Toggles order if column already exists, or adds it otherwise.
237
- * @param column Column name to sort by.
238
- */
723
+ * Updates the sort order in the current filter.
724
+ * Toggles order if column already exists and no explicit sort is provided, or adds it otherwise.
725
+ *
726
+ * @param column Column name to sort by
727
+ * @param sort Optional sort order. If not provided and column exists, toggles between ASC/DESC.
728
+ * If not provided and column doesn't exist, defaults to ASC.
729
+ *
730
+ * @example
731
+ * // Add new sort (defaults to ASC)
732
+ * state.setSort('name');
733
+ *
734
+ * @example
735
+ * // Toggle existing sort
736
+ * state.setSort('name'); // If already ASC, becomes DESC; if DESC, becomes ASC
737
+ *
738
+ * @example
739
+ * // Explicitly set sort order
740
+ * state.setSort('name', 'DESC'); // Always sets to DESC
741
+ */
239
742
  setSort(column: string, sort?: "ASC" | "DESC"): void;
240
743
  /**
241
744
  * Removes a specific column from the current sort.
@@ -252,6 +755,10 @@ declare class BaseStateService<TRecord, TFilter extends Partial<TRecord> & Filte
252
755
  *
253
756
  * @param key A unique key representing the loading context (e.g., "list", "detail")
254
757
  * @returns Observable emitting `true` if the given context is loading, `false` otherwise.
758
+ *
759
+ * @remarks
760
+ * Optimized with distinctUntilChanged to prevent duplicate emissions
761
+ * and shareReplay to share subscriptions.
255
762
  */
256
763
  isLoading$(key: string): Observable<boolean>;
257
764
  /**
@@ -329,19 +836,331 @@ declare class BaseStateService<TRecord, TFilter extends Partial<TRecord> & Filte
329
836
  destroySubscriptions(subjects?: Array<'filterSubject' | 'recordsSubject' | 'pagerSubject' | 'selectedSubject' | 'loadingMapSubject' | 'errorSubject'>): void;
330
837
  }
331
838
 
839
+ /**
840
+ * Abstract base service providing standard CRUD operations for REST APIs.
841
+ *
842
+ * @template T The full model type representing your entity
843
+ * @template TFilter Filter type extending Partial<T> & Filter for query parameters
844
+ * @template TCreate DTO type for creating new records (defaults to Partial<T>)
845
+ * @template TUpdate DTO type for updating records (defaults to Partial<T>)
846
+ *
847
+ * @remarks
848
+ * Extend this class in your feature services to get type-safe CRUD operations
849
+ * with minimal boilerplate. The service automatically handles:
850
+ * - Query string generation from filters
851
+ * - Pagination and sorting
852
+ * - Standardized response/error handling
853
+ * - Type safety throughout the request/response cycle
854
+ *
855
+ * @example
856
+ * // Define your model
857
+ * interface User {
858
+ * id: number;
859
+ * name: string;
860
+ * email: string;
861
+ * role: string;
862
+ * }
863
+ *
864
+ * // Define custom filter
865
+ * interface UserFilter extends Filter {
866
+ * role?: string;
867
+ * isActive?: boolean;
868
+ * }
869
+ *
870
+ * // Create service
871
+ * @Injectable({ providedIn: 'root' })
872
+ * export class UserService extends BaseService<User, UserFilter> {
873
+ * constructor(http: HttpClient) {
874
+ * super(http, '/api/users');
875
+ * }
876
+ *
877
+ * // Add custom methods
878
+ * activateUser(id: number): Observable<IResponse<User>> {
879
+ * return this.http.post<IResponse<User>>(`${this.baseUrl}/${id}/activate`, {});
880
+ * }
881
+ * }
882
+ *
883
+ * @example
884
+ * // With separate DTOs for create/update
885
+ * interface CreateUserDto {
886
+ * name: string;
887
+ * email: string;
888
+ * password: string;
889
+ * }
890
+ *
891
+ * interface UpdateUserDto {
892
+ * name?: string;
893
+ * email?: string;
894
+ * // Note: password excluded
895
+ * }
896
+ *
897
+ * @Injectable({ providedIn: 'root' })
898
+ * export class UserService extends BaseService<
899
+ * User,
900
+ * UserFilter,
901
+ * CreateUserDto,
902
+ * UpdateUserDto
903
+ * > {
904
+ * constructor(http: HttpClient) {
905
+ * super(http, '/api/users');
906
+ * }
907
+ * }
908
+ */
332
909
  declare abstract class BaseService<T, // Full model type
333
910
  TFilter extends Partial<T> & Filter = Partial<T> & Filter, // Filter: model fields + pagination/sorting
334
911
  TCreate = Partial<T>, // DTO for create
335
912
  TUpdate = Partial<T>> {
336
913
  protected http: HttpClient;
337
914
  protected baseUrl: string;
915
+ /**
916
+ * Creates an instance of BaseService.
917
+ *
918
+ * @param http Angular HttpClient for making HTTP requests
919
+ * @param baseUrl Base URL for the resource API endpoint (e.g., '/api/users')
920
+ */
338
921
  constructor(http: HttpClient, baseUrl: string);
922
+ /**
923
+ * Fetches a paginated list of records with optional filtering and sorting.
924
+ *
925
+ * @param filter Optional filter object containing pagination, sorting, and search criteria
926
+ * @returns Observable of response containing records array and pagination metadata
927
+ *
928
+ * @example
929
+ * // Basic usage
930
+ * userService.getAll().subscribe(response => {
931
+ * console.log(response.data?.records);
932
+ * console.log(response.data?.pager.totalRecords);
933
+ * });
934
+ *
935
+ * @example
936
+ * // With filters
937
+ * userService.getAll({
938
+ * page: 2,
939
+ * pageLength: 25,
940
+ * search: 'john',
941
+ * role: 'admin',
942
+ * sort: [new SortItem('name', 'ASC')]
943
+ * }).subscribe(response => {
944
+ * // Handle response
945
+ * });
946
+ */
339
947
  getAll(filter?: TFilter): Observable<IResponse<IMultiresult<T>>>;
948
+ /**
949
+ * Fetches a single record by its ID.
950
+ *
951
+ * @param id The unique identifier of the record
952
+ * @returns Observable of response containing the single record
953
+ *
954
+ * @example
955
+ * userService.getDetails(123).subscribe(response => {
956
+ * if (response.data) {
957
+ * console.log('User:', response.data);
958
+ * }
959
+ * });
960
+ */
340
961
  getDetails(id: number): Observable<IResponse<T>>;
962
+ /**
963
+ * Creates a new record.
964
+ *
965
+ * @param data Data transfer object containing fields for the new record
966
+ * @returns Observable of response containing the created record (usually with generated ID)
967
+ *
968
+ * @example
969
+ * userService.create({
970
+ * name: 'John Doe',
971
+ * email: 'john@example.com',
972
+ * password: 'secure123'
973
+ * }).subscribe(response => {
974
+ * if (response.data) {
975
+ * console.log('Created user:', response.data);
976
+ * }
977
+ * });
978
+ */
341
979
  create(data: TCreate): Observable<IResponse<T>>;
980
+ /**
981
+ * Updates an existing record.
982
+ *
983
+ * @param id The unique identifier of the record to update
984
+ * @param data Data transfer object containing fields to update (partial update supported)
985
+ * @returns Observable of response containing the updated record
986
+ *
987
+ * @example
988
+ * userService.update(123, {
989
+ * name: 'Jane Doe',
990
+ * email: 'jane@example.com'
991
+ * }).subscribe(response => {
992
+ * if (response.data) {
993
+ * console.log('Updated user:', response.data);
994
+ * }
995
+ * });
996
+ */
342
997
  update(id: number, data: TUpdate): Observable<IResponse<T>>;
343
- delete(id: number, method?: 'soft' | 'hard'): Observable<IResponse<any>>;
998
+ /**
999
+ * Deletes a record (soft or hard delete).
1000
+ *
1001
+ * @param id The unique identifier of the record to delete
1002
+ * @param method Deletion method: 'soft' (mark as deleted, reversible) or 'hard' (permanent removal)
1003
+ * @returns Observable of response confirming deletion
1004
+ *
1005
+ * @remarks
1006
+ * - Soft delete: Record is marked as deleted but can be restored later
1007
+ * - Hard delete: Record is permanently removed from the database
1008
+ * - Default is 'soft' for safety
1009
+ *
1010
+ * @example
1011
+ * // Soft delete (default)
1012
+ * userService.delete(123).subscribe(() => {
1013
+ * console.log('User soft deleted');
1014
+ * });
1015
+ *
1016
+ * @example
1017
+ * // Hard delete (permanent)
1018
+ * userService.delete(123, 'hard').subscribe(() => {
1019
+ * console.log('User permanently deleted');
1020
+ * });
1021
+ */
1022
+ delete(id: number, method?: 'soft' | 'hard'): Observable<IResponse<unknown>>;
1023
+ }
1024
+
1025
+ /**
1026
+ * Configuration options for the ApiErrorHandler.
1027
+ */
1028
+ interface ApiErrorHandlerConfig {
1029
+ /** Whether to log errors to console (default: true in dev, false in prod) */
1030
+ logErrors?: boolean;
1031
+ /** Custom error message transformer */
1032
+ errorMessageTransformer?: (error: HttpErrorResponse) => string;
1033
+ /** Callback to be called when an error occurs */
1034
+ onError?: (error: ProcessedError) => void;
1035
+ }
1036
+ /**
1037
+ * Processed error information with additional context.
1038
+ */
1039
+ interface ProcessedError {
1040
+ /** HTTP status code */
1041
+ status: number;
1042
+ /** User-friendly error message */
1043
+ message: string;
1044
+ /** Technical error details */
1045
+ details?: string;
1046
+ /** Error code from API */
1047
+ code?: string;
1048
+ /** Original HTTP error response */
1049
+ originalError: HttpErrorResponse;
1050
+ /** Whether this error should be shown to user */
1051
+ showToUser: boolean;
1052
+ /** Error type classification */
1053
+ type: 'network' | 'client' | 'server' | 'unknown';
344
1054
  }
1055
+ /**
1056
+ * Service for handling and processing API errors consistently.
1057
+ *
1058
+ * @example
1059
+ * // Basic usage in a service
1060
+ * constructor(private errorHandler: ApiErrorHandler) {}
1061
+ *
1062
+ * loadData() {
1063
+ * this.http.get('/api/data').subscribe({
1064
+ * error: (err: HttpErrorResponse) => {
1065
+ * const processed = this.errorHandler.handleError(err);
1066
+ * this.showErrorToUser(processed.message);
1067
+ * }
1068
+ * });
1069
+ * }
1070
+ *
1071
+ * @example
1072
+ * // Configure globally
1073
+ * providers: [
1074
+ * {
1075
+ * provide: ApiErrorHandler,
1076
+ * useFactory: () => {
1077
+ * const handler = new ApiErrorHandler();
1078
+ * handler.configure({
1079
+ * logErrors: true,
1080
+ * onError: (error) => {
1081
+ * // Send to logging service
1082
+ * loggingService.logError(error);
1083
+ * }
1084
+ * });
1085
+ * return handler;
1086
+ * }
1087
+ * }
1088
+ * ]
1089
+ */
1090
+ declare class ApiErrorHandler {
1091
+ private config;
1092
+ /**
1093
+ * Configure the error handler.
1094
+ *
1095
+ * @param config Configuration options
1096
+ */
1097
+ configure(config: ApiErrorHandlerConfig): void;
1098
+ /**
1099
+ * Process an HTTP error and return structured error information.
1100
+ *
1101
+ * @param error The HTTP error response
1102
+ * @returns Processed error with user-friendly message and metadata
1103
+ */
1104
+ handleError(error: HttpErrorResponse): ProcessedError;
1105
+ /**
1106
+ * Extract error message from various error formats.
1107
+ *
1108
+ * @param error The HTTP error response
1109
+ * @returns User-friendly error message
1110
+ */
1111
+ extractErrorMessage(error: HttpErrorResponse): string;
1112
+ /**
1113
+ * Get default error message based on HTTP status code.
1114
+ *
1115
+ * @param status HTTP status code
1116
+ * @returns Default error message
1117
+ */
1118
+ private getDefaultMessageForStatus;
1119
+ /**
1120
+ * Classify error type based on status code.
1121
+ *
1122
+ * @param status HTTP status code
1123
+ * @returns Error type classification
1124
+ */
1125
+ private getErrorType;
1126
+ /**
1127
+ * Determine if error should be shown to user.
1128
+ *
1129
+ * @param error HTTP error response
1130
+ * @returns Whether to show error to user
1131
+ */
1132
+ private shouldShowToUser;
1133
+ /**
1134
+ * Process the error into a structured format.
1135
+ *
1136
+ * @param error HTTP error response
1137
+ * @returns Processed error object
1138
+ */
1139
+ private processError;
1140
+ /**
1141
+ * Log error to console (in dev mode).
1142
+ *
1143
+ * @param error Processed error
1144
+ */
1145
+ private logError;
1146
+ static ɵfac: i0.ɵɵFactoryDeclaration<ApiErrorHandler, never>;
1147
+ static ɵprov: i0.ɵɵInjectableDeclaration<ApiErrorHandler>;
1148
+ }
1149
+
1150
+ /**
1151
+ * HTTP Interceptor that catches errors and processes them through the ApiErrorHandler.
1152
+ *
1153
+ * @example
1154
+ * // In your app.config.ts or main provider:
1155
+ * export const appConfig: ApplicationConfig = {
1156
+ * providers: [
1157
+ * provideHttpClient(
1158
+ * withInterceptors([apiErrorInterceptor])
1159
+ * )
1160
+ * ]
1161
+ * };
1162
+ */
1163
+ declare const apiErrorInterceptor: HttpInterceptorFn;
345
1164
 
346
- export { ApiCore, BaseResourceConfig, BaseService, BaseStateService, Filter, SELECT_MODE, SortItem, generatePermissions, isEmpty, jsonToQueryString, sortObjectToString };
347
- export type { IMisError, IMultiresult, IMultiresultMetaData, IResponse, PagedResponse, SingleResponse, StandardPermission, TableColumn };
1165
+ export { API_CORE_CONFIG, ApiCore, ApiErrorHandler, BaseResourceConfig, BaseService, BaseStateService, DEFAULT_API_CORE_CONFIG, Filter, SELECT_MODE, SortItem, apiErrorInterceptor, generatePermissions, isEmpty, jsonToQueryString, mergeConfig, sortObjectToString };
1166
+ export type { ApiCoreConfig, ApiErrorHandlerConfig, IMisError, IMultiresult, IMultiresultMetaData, IResponse, PagedResponse, ProcessedError, SingleResponse, StandardPermission, TableColumn };