aril 2.0.1-dev.3 → 2.0.1-dev.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/boot/bridge/src/mfe-bridge.d.ts +33 -0
  2. package/esm2022/boot/bridge/src/mfe-bridge.mjs +18 -1
  3. package/esm2022/boot/mfe/src/bootstrap.mjs +5 -1
  4. package/esm2022/theme/layout/service/breadcrumb-publisher.service.mjs +12 -4
  5. package/esm2022/theme/layout/service/interfaces/interfaces.mjs +1 -1
  6. package/esm2022/theme/layout/service/selection-group.service.mjs +151 -21
  7. package/fesm2022/aril-boot-bridge.mjs +17 -0
  8. package/fesm2022/aril-boot-bridge.mjs.map +1 -1
  9. package/fesm2022/{aril-boot-mfe-app.component-AS4MzEo2.mjs → aril-boot-mfe-app.component-oxzMhRXe.mjs} +2 -2
  10. package/fesm2022/{aril-boot-mfe-app.component-AS4MzEo2.mjs.map → aril-boot-mfe-app.component-oxzMhRXe.mjs.map} +1 -1
  11. package/fesm2022/{aril-boot-mfe-aril-boot-mfe-Ui9Cv3UC.mjs → aril-boot-mfe-aril-boot-mfe-CKOM61xb.mjs} +6 -2
  12. package/fesm2022/aril-boot-mfe-aril-boot-mfe-CKOM61xb.mjs.map +1 -0
  13. package/fesm2022/aril-boot-mfe.mjs +1 -1
  14. package/fesm2022/aril-theme-layout.mjs +158 -21
  15. package/fesm2022/aril-theme-layout.mjs.map +1 -1
  16. package/fesm2022/aril.mjs +4 -0
  17. package/fesm2022/aril.mjs.map +1 -1
  18. package/package.json +203 -203
  19. package/scripts/mf-shared.js +47 -0
  20. package/theme/layout/service/breadcrumb-publisher.service.ts +18 -8
  21. package/theme/layout/service/interfaces/interfaces.d.ts +3 -17
  22. package/theme/layout/service/interfaces/interfaces.ts +6 -18
  23. package/theme/layout/service/selection-group.service.d.ts +8 -4
  24. package/theme/layout/service/selection-group.service.ts +161 -25
  25. package/fesm2022/aril-boot-mfe-aril-boot-mfe-Ui9Cv3UC.mjs.map +0 -1
@@ -3468,7 +3468,12 @@ class BreadcrumbPublisherService {
3468
3468
  // değerleri sonradan geldikçe listeyi yeniden yayınla.
3469
3469
  this.breadcrumbService.keyValues();
3470
3470
  this.publishFromCurrentRoute();
3471
- });
3471
+ },
3472
+ // `publishFromCurrentRoute` → `publish` içinde `bridge.breadcrumbs.set` / `bridge.pageTitle.set`
3473
+ // signal write yapar; effect içinden signal write default'ta NG0600 fırlatır. aril genelindeki
3474
+ // effect'lerle aynı şekilde `allowSignalWrites` ile izin veriyoruz. (MF shared core öncesi
3475
+ // cross-core split bu guard'ı maskeliyordu; tek core olunca yüzeye çıktı — davranış aynı.)
3476
+ { allowSignalWrites: true });
3472
3477
  }
3473
3478
  ngOnDestroy() {
3474
3479
  this.routerSubscription?.unsubscribe();
@@ -3503,7 +3508,10 @@ class BreadcrumbPublisherService {
3503
3508
  publish(breadcrumbs) {
3504
3509
  bridge.breadcrumbs.set(breadcrumbs);
3505
3510
  const lastLabel = breadcrumbs[breadcrumbs.length - 1]?.label ?? '';
3506
- if (bridge.pageTitle() || lastLabel) {
3511
+ // untracked ZORUNLU: publish, constructor'daki effect'in çağrı grafiğinde çalışır. Tracked
3512
+ // okuma effect'i kendi yazdığı pageTitle'a abone eder; tab başına bir publisher instance
3513
+ // olduğundan iki tab farklı title yazarak birbirini sonsuz döngüde tetiklerdi.
3514
+ if (untracked(bridge.pageTitle) || lastLabel) {
3507
3515
  bridge.pageTitle.set(lastLabel);
3508
3516
  }
3509
3517
  this.pubSubService.publish({
@@ -3562,34 +3570,148 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.1.2", ngImpor
3562
3570
  }]
3563
3571
  }], ctorParameters: () => [] });
3564
3572
 
3573
+ // Key başına maksimum ardışık fetch hatası: limit aşılınca o key oturum boyu yeniden denenmez.
3574
+ // Sınırsız retry'da kalıcı hata veren bir grup (örn. silinmiş grup → 404), diğer grupların her
3575
+ // başarılı yüklemesinde (map güncellemesi tüm enum computed'larını yeniden çalıştırır) yeniden
3576
+ // POST edilirdi → istek/console fırtınası.
3577
+ const MAX_FETCH_FAILURES = 3;
3565
3578
  class SelectionGroupService {
3566
3579
  constructor(http) {
3567
3580
  this.http = http;
3568
- this.selectionGroups = signal({});
3569
- this.loading = new Map();
3570
- this.loaded = new Map();
3571
- this.loadStatesFromLocalStorage();
3572
- effect(() => {
3573
- const encodedData = btoa(encodeURI(JSON.stringify(this.selectionGroups())));
3574
- localStorage.setItem('selection-groups', encodedData);
3575
- });
3581
+ // State cross-MFE paylaşımlı: bridge globalThis-singleton'ında yaşar (bkz. mfe-bridge.ts).
3582
+ // Servis instance'ı MFE/tab başına çoğalsa da tüm okuma/yazma tek bridge state'ine gider.
3583
+ this.selectionGroups = bridge.selectionGroups;
3584
+ this.loading = bridge.selectionLoading;
3585
+ this.loaded = bridge.selectionLoaded;
3586
+ this.failures = bridge.selectionFailures;
3587
+ // loadStates + invalidate yalnızca ilk instance'ta (cross-MFE bir kez): tek localStorage
3588
+ // yüklemesi, tek `/versions` çağrısı. effect her instance'ta kurulur ama hepsi aynı paylaşılan
3589
+ // state'i yazdığından ezme olmaz.
3590
+ if (!bridge.selectionInitialized) {
3591
+ bridge.selectionInitialized = true;
3592
+ this.loadStatesFromLocalStorage();
3593
+ this.invalidateStaleGroups();
3594
+ }
3595
+ }
3596
+ // localStorage'a slim cache yazar. `effect` YERİNE bilinçli olarak doğrudan çağrılır: signal'i
3597
+ // değiştiren her yer (fetchEnum, invalidateStaleGroups) persist'i açıkça tetikler. NOT: webpack MF
3598
+ // config'iyle `@angular/core` artık singleton shared olduğundan effect-tabanlı persist de çalışırdı;
3599
+ // yine de açık/öngörülebilir olması (effect içinde gizli signal-write + NG0600 riski olmaması) için
3600
+ // doğrudan çağrı tercih edildi.
3601
+ persistToLocalStorage() {
3602
+ try {
3603
+ const slim = this.slimForStorage(this.selectionGroups());
3604
+ localStorage.setItem('selection-groups', btoa(encodeURI(JSON.stringify(slim))));
3605
+ }
3606
+ catch {
3607
+ // QuotaExceededError vb. → eski cache'i koru, yeni yazımı sessizce atla.
3608
+ }
3576
3609
  }
3577
- async loadStatesFromLocalStorage() {
3610
+ loadStatesFromLocalStorage() {
3578
3611
  const selectionGroups = localStorage.getItem('selection-groups');
3579
3612
  if (selectionGroups) {
3580
3613
  try {
3581
3614
  const decodedData = decodeURI(atob(selectionGroups));
3582
- this.selectionGroups.set(JSON.parse(decodedData));
3615
+ // Dynamic gruplar zaten persist edilmiyor (slimForStorage onları atlar) → ekstra
3616
+ // filtre gerekmez; localStorage içeriği yalnızca Fixed gruplardır.
3617
+ const restored = JSON.parse(decodedData);
3618
+ this.selectionGroups.set(restored);
3619
+ // Restore edilen gruplar loaded sayılır: aksi halde koşulsuz fetchEnum çağıran
3620
+ // consumer'lar guard'ı geçip her oturum yeniden çeker (cache faydası kaybolur).
3621
+ // Stale'lik kontrolü invalidateStaleGroups'un işi. null entry'ler (legacy) loaded
3622
+ // işaretlenmez — işaretlenirse o grup bir daha hiç yüklenemezdi.
3623
+ Object.entries(restored).forEach(([key, group]) => {
3624
+ if (group)
3625
+ this.loaded.set(key, true);
3626
+ });
3583
3627
  }
3584
3628
  catch (e) {
3585
3629
  console.error(e);
3586
3630
  }
3587
3631
  }
3588
3632
  }
3633
+ // Dynamic grupları atlar + Fixed grupları yalnızca render (value/table/picker/export) ve invalidate
3634
+ // için gerekli alanlara indirger. localStorage'da audit/id/açıklama gibi kullanılmayan alanları tutmaz.
3635
+ slimForStorage(groups) {
3636
+ const out = {};
3637
+ for (const [key, group] of Object.entries(groups)) {
3638
+ if (!group || group.loadStrategy === 'Dynamic')
3639
+ continue;
3640
+ out[key] = {
3641
+ version: group.version,
3642
+ selectionItems: (group.selectionItems ?? []).map((it) => ({
3643
+ selectionKey: it.selectionKey,
3644
+ selectionDisplay: it.selectionDisplay,
3645
+ selectionMultiLanguageDisplays: it.selectionMultiLanguageDisplays,
3646
+ showOrder: it.showOrder,
3647
+ parentSelectionKey: it.parentSelectionKey
3648
+ }))
3649
+ };
3650
+ }
3651
+ return out;
3652
+ }
3653
+ // App init'te enum version'larını çekip localStorage'daki stale grupları temizler.
3654
+ // Silinen grup, tüketici component'lerin `if (!selectionGroup) fetchEnum()` guard'ıyla yeniden yüklenir.
3655
+ invalidateStaleGroups() {
3656
+ // App-init snapshot: yalnızca localStorage'dan yüklenen gruplar kontrol edilir. Yanıt gelene
3657
+ // kadar consumer'ların fetchEnum ile yüklediği taze gruplar taranmaz/silinmez (aksi halde
3658
+ // taze grup silinir ve kalıcı per-component fetch guard'ları onu bir daha çekmeyebilir).
3659
+ const keysToCheck = Object.keys(this.selectionGroups());
3660
+ if (keysToCheck.length === 0)
3661
+ return;
3662
+ const hostApi = API_CONFIGS.api.replace(/\/[^\/]+\/v1$/, `/host/v1`);
3663
+ this.http
3664
+ .get(hostApi + '/selection-group/versions')
3665
+ .pipe(take(1))
3666
+ .subscribe({
3667
+ next: (versions) => {
3668
+ // Boş/bozuk yanıt güvenlik şeridi: aşağıdaki "listede olmayanı sil" kuralı
3669
+ // boş listeyle tüm cache'i yanlışlıkla boşaltmasın.
3670
+ if (!versions?.length)
3671
+ return;
3672
+ const latest = new Map(versions.map((v) => [v.groupName, v.version]));
3673
+ let changed = false;
3674
+ const next = { ...this.selectionGroups() };
3675
+ keysToCheck.forEach((key) => {
3676
+ const group = next[key];
3677
+ if (!group)
3678
+ return;
3679
+ const separatorIndex = key.indexOf('~');
3680
+ if (separatorIndex === -1)
3681
+ return;
3682
+ const serverVersion = latest.get(key.substring(0, separatorIndex));
3683
+ // serverVersion == null: grup /versions listesinde yok → sunucudan silinmiş /
3684
+ // yeniden adlandırılmış demektir (endpoint Fixed grupların tam listesini döner)
3685
+ // → o da evict edilir; kalırsa kaldırılmış enum değerleri süresiz render edilir.
3686
+ if (serverVersion == null || serverVersion !== group.version) {
3687
+ delete next[key];
3688
+ this.loaded.delete(key);
3689
+ this.failures.delete(key);
3690
+ // Uçuştaki fetch'in flag'i korunur: silinirse ikinci bir consumer guard'ı
3691
+ // geçip duplicate /load atar ve export-overlay'in isLoading poll'ü grubu
3692
+ // beklemeden erken geçer. Yanıt geldiğinde flag'i fetchEnum kendisi yönetir.
3693
+ if (!this.loading.get(key)) {
3694
+ this.loading.delete(key);
3695
+ }
3696
+ changed = true;
3697
+ }
3698
+ });
3699
+ if (changed) {
3700
+ this.selectionGroups.set(next);
3701
+ this.persistToLocalStorage();
3702
+ }
3703
+ },
3704
+ error: () => {
3705
+ // versions endpoint yok/erişilemez: sessizce mevcut cache ile devam.
3706
+ }
3707
+ });
3708
+ }
3589
3709
  fetchEnum(groupName, parentSelectionKey = 'ALL') {
3590
3710
  const key = groupName + '~' + parentSelectionKey;
3591
3711
  if (this.loaded.get(key) || this.loading.get(key))
3592
3712
  return;
3713
+ if ((this.failures.get(key) ?? 0) >= MAX_FETCH_FAILURES)
3714
+ return;
3593
3715
  const payload = {
3594
3716
  groupName,
3595
3717
  parentSelectionKey
@@ -3600,17 +3722,32 @@ class SelectionGroupService {
3600
3722
  this.http
3601
3723
  .post(hostApi + '/selection-group/load', payload)
3602
3724
  .pipe(take(1))
3603
- .subscribe((selectionGroup) => {
3604
- this.loading.set(key, false);
3605
- this.loaded.set(key, true);
3606
- this.selectionGroups.update((groups) => ({
3607
- ...groups,
3608
- [key]: selectionGroup
3609
- }));
3725
+ .subscribe({
3726
+ next: (selectionGroup) => {
3727
+ this.loading.set(key, false);
3728
+ this.loaded.set(key, true);
3729
+ this.failures.delete(key);
3730
+ this.selectionGroups.update((groups) => ({
3731
+ ...groups,
3732
+ [key]: selectionGroup
3733
+ }));
3734
+ this.persistToLocalStorage();
3735
+ },
3736
+ // HTTP hatasında loading'i sıfırla: aksi halde guard (loaded/loading) bu enum'u
3737
+ // kilitler. loading/loaded bridge'de cross-MFE SHARED olduğundan, tek bir anlık hata
3738
+ // bu enum'u TÜM remote'larda hard-reload'a kadar bir daha çekilemez yapardı. loading=false
3739
+ // → sonraki fetchEnum (component init/retry) yeniden deneyebilir. loaded zaten false kalır.
3740
+ // Deneme sayısı MAX_FETCH_FAILURES ile sınırlı: kalıcı hatada sonsuz retry önlenir.
3741
+ error: () => {
3742
+ this.loading.set(key, false);
3743
+ this.failures.set(key, (this.failures.get(key) ?? 0) + 1);
3744
+ }
3610
3745
  });
3611
3746
  }
3612
- isLoading(groupName) {
3613
- return this.loading.get(groupName) ?? false;
3747
+ // key formatı: `groupName~parentSelectionKey` (fetchEnum'daki composite key ile aynı).
3748
+ // Direkt olarak groupName geçilirse Map'te eşleşme olmaz ve her zaman false döner.
3749
+ isLoading(key) {
3750
+ return this.loading.get(key) ?? false;
3614
3751
  }
3615
3752
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.1.2", ngImport: i0, type: SelectionGroupService, deps: [{ token: i6.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
3616
3753
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.1.2", ngImport: i0, type: SelectionGroupService, providedIn: 'root' }); }