@yuuvis/client-core 2.20.1 → 2.21.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.
@@ -4514,47 +4514,128 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
4514
4514
  }] });
4515
4515
 
4516
4516
  /**
4517
- * Service for managing Identity Management (IDM) operations.
4518
- * Provides functionality for querying users, roles, and organization entities,
4519
- * with built-in caching mechanisms to optimize performance.
4517
+ * Centralized service for Identity Management (IDM) operations.
4518
+ *
4519
+ * Provides a typed API for querying users, roles, and organization entities
4520
+ * from the IDM backend, with a built-in client-side caching layer that
4521
+ * minimizes redundant HTTP calls.
4522
+ *
4523
+ * **Key Features:**
4524
+ * - User lookup by ID with automatic 1-hour TTL cache
4525
+ * - Unified search across users and roles via a single method
4526
+ * - Role listing with optional name filter
4527
+ * - Cache persisted to IndexedDB/localStorage (survives page refresh)
4528
+ * - In-memory cache hydrated on service creation for instant access
4529
+ *
4530
+ * **Caching Strategy:**
4531
+ * All cached users are stored as a single map ({@link IdmUserCache}) under one
4532
+ * storage key. This enables atomic load on startup — one read populates the
4533
+ * entire in-memory cache — and avoids N+1 reads when resolving multiple users
4534
+ * (e.g. audit trails, comment threads, assignment lists). Per-entry TTL is
4535
+ * enforced via {@link IdmCachedUser.expires}.
4536
+ *
4537
+ * ```
4538
+ * getUserById("abc")
4539
+ * ├─ Cache hit & valid → return immediately (no HTTP)
4540
+ * └─ Cache miss/expired → GET /idm/users/abc
4541
+ * ├─ Update in-memory cache
4542
+ * ├─ Persist to ClientCacheService
4543
+ * └─ Return YuvUser
4544
+ * ```
4545
+ *
4546
+ * **API Endpoints:**
4547
+ * | Method | Endpoint |
4548
+ * |----------------------------|----------------------|
4549
+ * | `queryOrganizationEntity` | `GET /idm/search` |
4550
+ * | `getRoles` | `GET /idm/roles` |
4551
+ * | `getUserById` | `GET /idm/users/:id` |
4552
+ *
4553
+ * **Usage:**
4554
+ * ```ts
4555
+ * const idm = inject(IdmService);
4556
+ *
4557
+ * // Search users and roles
4558
+ * idm.queryOrganizationEntity('admin', ['user', 'role'])
4559
+ * .subscribe(entries => console.log(entries));
4560
+ *
4561
+ * // Get a user by ID (cached)
4562
+ * idm.getUserById('user-uuid').subscribe(user => console.log(user?.title));
4563
+ *
4564
+ * // List roles
4565
+ * idm.getRoles('ADMIN').subscribe(roles => console.log(roles));
4566
+ * ```
4567
+ *
4568
+ * @see {@link ClientCacheService} for the underlying persistence layer
4569
+ * @see {@link YuvUser} for the application-level user model
4520
4570
  */
4521
4571
  class IdmService {
4522
- #backend;
4523
- #clientCache;
4524
- #IDM_USER_CACHE_KEY;
4525
- #IDM_USER_CACHE_TTL; // 1 hour in milliseconds
4572
+ //#region Dependencies
4573
+ #backend = inject(BackendService);
4574
+ #clientCache = inject(ClientCacheService);
4575
+ //#endregion
4576
+ //#region Cache configuration
4577
+ #cacheKey = 'yuv-idm-user-cache';
4578
+ /**
4579
+ * Time-to-live for cached user entries (in milliseconds).
4580
+ *
4581
+ * After this period a cached entry is considered stale and the next
4582
+ * {@link getUserById} call for that user will re-fetch from the backend.
4583
+ *
4584
+ * **Timeline:**
4585
+ * ```
4586
+ * [User fetched] ─────────── 1 hour ──────────► [Entry expires]
4587
+ * cache hit returns instantly next access re-fetches
4588
+ * ```
4589
+ *
4590
+ * @default 1 hour (3600000 milliseconds)
4591
+ */
4592
+ #cacheTtl = 60 * 60 * 1000;
4593
+ #userCache = {};
4594
+ //#endregion
4526
4595
  constructor() {
4527
- this.#backend = inject(BackendService);
4528
- this.#clientCache = inject(ClientCacheService);
4529
- this.#IDM_USER_CACHE_KEY = 'yuv-idm-user-cache';
4530
- this.#IDM_USER_CACHE_TTL = 60 * 60 * 1000; // 1 hour in milliseconds
4531
- this.userCache = {};
4532
- this.#loadIdmCache();
4596
+ this.#loadCacheFromStorage();
4533
4597
  }
4598
+ //#region Public methods
4534
4599
  /**
4535
- * Queries organization entities (users and/or roles) based on search term.
4600
+ * Searches organization entities (users and/or roles) by a free-text term.
4601
+ *
4602
+ * Returns a unified {@link OrganizationSetEntry} array so consumers
4603
+ * (autocomplete fields, assignment dialogs, permission pickers) can
4604
+ * treat users and roles interchangeably.
4536
4605
  *
4537
- * @param term - The search term to filter entities
4538
- * @param targetTypes - Array of entity types to search for ('user', 'role')
4606
+ * @param term - Free-text search term to filter entities
4607
+ * @param targetTypes - Entity types to include in results (`'user'`, `'role'`, or both)
4539
4608
  * @param size - Optional maximum number of results to return
4540
- * @returns Observable array of organization set entries matching the search criteria
4609
+ * @returns Observable of matching organization entities
4610
+ *
4611
+ * @example
4612
+ * ```ts
4613
+ * // Search for users and roles matching "admin"
4614
+ * idmService.queryOrganizationEntity('admin', ['user', 'role'], 10)
4615
+ * .subscribe(entries => {
4616
+ * const users = entries.filter(e => e.type === 'user');
4617
+ * const roles = entries.filter(e => e.type === 'role');
4618
+ * });
4619
+ * ```
4541
4620
  */
4542
4621
  queryOrganizationEntity(term, targetTypes, size) {
4543
- const searchParams = new URLSearchParams();
4544
- searchParams.set('search', term);
4545
- size && searchParams.set('size', size.toString());
4546
- return this.#backend.get(`/idm/search?${searchParams.toString()}`).pipe(map$1((res) => [
4622
+ const params = new URLSearchParams();
4623
+ params.set('search', term);
4624
+ if (size) {
4625
+ params.set('size', size.toString());
4626
+ }
4627
+ return this.#backend.get(`/idm/search?${params.toString()}`).pipe(map$1((res) => [
4547
4628
  ...(targetTypes.includes('user')
4548
- ? res.users.map((u) => ({
4549
- id: u.id,
4550
- title: `${u.lastname}, ${u.firstname} (${u.username})`, // TODO: u.title,
4629
+ ? res.users.map((user) => ({
4630
+ id: user.id,
4631
+ title: `${user.lastname}, ${user.firstname} (${user.username})`,
4551
4632
  type: 'user'
4552
4633
  }))
4553
4634
  : []),
4554
4635
  ...(targetTypes.includes('role')
4555
- ? res.roles.map((u) => ({
4556
- id: u.name,
4557
- title: u.name,
4636
+ ? res.roles.map((role) => ({
4637
+ id: role.name,
4638
+ title: role.name,
4558
4639
  type: 'role'
4559
4640
  }))
4560
4641
  : [])
@@ -4563,42 +4644,66 @@ class IdmService {
4563
4644
  /**
4564
4645
  * Retrieves available roles, optionally filtered by a search term.
4565
4646
  *
4647
+ * Returns an empty array on error to ensure consumers can always
4648
+ * safely iterate the result without null-checking.
4649
+ *
4566
4650
  * @param role - Optional search term to filter roles by name
4567
- * @returns Observable array of role objects containing name and description
4651
+ * @returns Observable of role objects containing name and description
4652
+ *
4653
+ * @example
4654
+ * ```ts
4655
+ * // List all roles
4656
+ * idmService.getRoles().subscribe(roles => console.log(roles));
4657
+ *
4658
+ * // Filter roles by name
4659
+ * idmService.getRoles('ADMIN').subscribe(roles => console.log(roles));
4660
+ * ```
4568
4661
  */
4569
4662
  getRoles(role) {
4570
- const searchParams = new URLSearchParams();
4571
- role && searchParams.set('search', role);
4572
- return this.#backend.get(`/idm/roles?${searchParams.toString()}`).pipe(catchError$1(() => of([])));
4663
+ const params = new URLSearchParams();
4664
+ if (role) {
4665
+ params.set('search', role);
4666
+ }
4667
+ return this.#backend.get(`/idm/roles?${params.toString()}`).pipe(catchError$1(() => of([])));
4573
4668
  }
4574
4669
  /**
4575
- * Retrieves user information by user ID with automatic caching.
4576
- * Uses a 1-hour TTL cache to minimize backend requests.
4670
+ * Retrieves a user by ID with automatic caching.
4671
+ *
4672
+ * Checks the in-memory cache first. If a valid (non-expired) entry exists,
4673
+ * it is returned immediately without an HTTP call. Otherwise, the user is
4674
+ * fetched from the backend, stored in both the in-memory and persistent
4675
+ * cache, and returned as a {@link YuvUser}.
4577
4676
  *
4578
- * @param id - The unique identifier of the user
4579
- * @returns Observable of YuvUser object or null if user is not found
4677
+ * Returns `null` if the user is not found or the request fails,
4678
+ * so consumers can safely use the result without try/catch.
4679
+ *
4680
+ * @param id - Unique user identifier (UUID)
4681
+ * @returns Observable of the user model, or `null` if not found / on error
4682
+ *
4683
+ * @example
4684
+ * ```ts
4685
+ * idmService.getUserById('550e8400-e29b-41d4-a716-446655440000')
4686
+ * .subscribe(user => {
4687
+ * if (user) {
4688
+ * console.log(user.getFullName());
4689
+ * }
4690
+ * });
4691
+ * ```
4580
4692
  */
4581
4693
  getUserById(id) {
4582
- const cachedUser = this.userCache[id];
4583
- const isCacheValid = cachedUser && Date.now() < cachedUser.expires;
4584
- if (isCacheValid) {
4585
- return of(new YuvUser(cachedUser.user));
4586
- }
4587
- else {
4588
- return this.#backend.get(`/idm/users/${id}`).pipe(switchMap$1((user) => {
4589
- this.userCache[id] = {
4590
- expires: Date.now() + this.#IDM_USER_CACHE_TTL,
4591
- user
4592
- };
4593
- return this.#clientCache.updateCache(this.#IDM_USER_CACHE_KEY, this.userCache).pipe(map$1(() => user));
4594
- }), map$1((user) => (user ? new YuvUser(user) : null)), catchError$1(() => of(null)));
4694
+ if (id in this.#userCache && Date.now() < this.#userCache[id].expires) {
4695
+ return of(new YuvUser(this.#userCache[id].user));
4595
4696
  }
4697
+ return this.#backend.get(`/idm/users/${id}`).pipe(switchMap$1((user) => {
4698
+ this.#userCache[id] = { expires: Date.now() + this.#cacheTtl, user };
4699
+ return this.#clientCache.updateCache(this.#cacheKey, this.#userCache).pipe(map$1(() => user));
4700
+ }), map$1((user) => new YuvUser(user)), catchError$1(() => of(null)));
4596
4701
  }
4597
- #loadIdmCache() {
4598
- return this.#clientCache.getFromCache(this.#IDM_USER_CACHE_KEY).subscribe({
4599
- next: (cache) => {
4600
- this.userCache = cache || {};
4601
- }
4702
+ //#endregion
4703
+ //#region Cache
4704
+ #loadCacheFromStorage() {
4705
+ this.#clientCache.getFromCache(this.#cacheKey).subscribe({
4706
+ next: (cache) => (this.#userCache = cache || {})
4602
4707
  });
4603
4708
  }
4604
4709
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: IdmService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }