@smartcorehub/localization-sdk 1.0.3507 → 1.0.3529

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
@@ -7,8 +7,9 @@ SDK TypeScript para consumir a API de localizacao da SmartCoreHub em projetos An
7
7
  - Cliente tipado para localizacao
8
8
  - Autenticacao por header X-Auth-Token
9
9
  - Tratamento de erros padronizado
10
- - Cache configuravel para listagem
10
+ - Cache opcional e configuravel
11
11
  - Integracao Angular via provider e service
12
+ - Suporte de locale via header HTTP padrao `Accept-Language`
12
13
 
13
14
  ## Requisitos
14
15
 
@@ -31,6 +32,7 @@ import { createLocalizationClient } from '@smartcorehub/localization-sdk';
31
32
  const localizationClient = createLocalizationClient({
32
33
  baseUrl: 'https://api.smartcorehub.io/',
33
34
  token: 'SEU_TOKEN',
35
+ locale: 'pt-BR',
34
36
  timeoutMs: 10000,
35
37
  cache: {
36
38
  enabled: true,
@@ -47,47 +49,78 @@ const list = await localizationClient.listResources();
47
49
  // list retorna objetos { key, value, language }
48
50
  ~~~
49
51
 
50
- ## Integracao com Angular
52
+ ## Locale via Accept-Language
53
+
54
+ O SDK envia locale usando o header HTTP padrao `Accept-Language`.
55
+
56
+ Ordem de prioridade para definir o locale enviado:
51
57
 
52
- Use o provider para registrar o SDK na injecao de dependencia.
58
+ - `options.locale` por chamada
59
+ - `locale` configurado no `createLocalizationClient`
60
+ - `navigator.language` no browser (fallback automatico)
61
+
62
+ Exemplo com locale global:
53
63
 
54
64
  ~~~ts
55
- import { ApplicationConfig } from '@angular/core';
56
- import { provideLocalization } from '@smartcorehub/localization-sdk';
57
-
58
- export const appConfig: ApplicationConfig = {
59
- providers: [
60
- provideLocalization({
61
- baseUrl: 'https://api.smartcorehub.io/',
62
- token: 'SEU_TOKEN',
63
- cache: { enabled: true, ttlMs: 60000 }
64
- })
65
- ]
66
- };
65
+ const client = createLocalizationClient({
66
+ baseUrl: 'https://api.smartcorehub.io/',
67
+ token: 'SEU_TOKEN',
68
+ locale: 'pt-BR'
69
+ });
67
70
  ~~~
68
71
 
69
- Consumindo no service/componente Angular:
72
+ Exemplo com override por chamada:
70
73
 
71
74
  ~~~ts
72
- import { Component, inject } from '@angular/core';
73
- import { LocalizationService } from '@smartcorehub/localization-sdk';
74
-
75
- @Component({
76
- selector: 'app-home-title',
77
- template: '{{ title }}'
78
- })
79
- export class HomeTitleComponent {
80
- private readonly localizationService = inject(LocalizationService);
81
- title = '';
82
-
83
- ngOnInit(): void {
84
- this.localizationService.getCurrentLanguage('home.title').subscribe((result) => {
85
- this.title = result;
86
- });
75
+ await client.getCurrentLanguage('home.title', { locale: 'en-US' });
76
+ ~~~
77
+
78
+ Quando cache.enabled = true:
79
+
80
+ - a primeira chamada de getCurrentLanguage/getByLanguage usa listResources internamente e guarda em cache
81
+ - chamadas seguintes retornam do cache
82
+ - invalidateCache(key?) e clearCache() podem ser usados para invalidar/limpar
83
+
84
+ Quando cache.enabled = false:
85
+
86
+ - o fluxo segue com chamadas diretas aos endpoints
87
+
88
+ ## Integracao com Angular
89
+
90
+ Forma validada e recomendada (mesma usada no exampleangular): facade com `createLocalizationClient`.
91
+
92
+ ~~~ts
93
+ import { Injectable } from '@angular/core';
94
+ import { from, Observable } from 'rxjs';
95
+ import { createLocalizationClient, LocalizationListResource } from '@smartcorehub/localization-sdk';
96
+
97
+ @Injectable({ providedIn: 'root' })
98
+ export class LocalizationFacadeService {
99
+ private readonly client = createLocalizationClient({
100
+ baseUrl: 'https://api.smartcorehub.io/',
101
+ token: 'SEU_TOKEN',
102
+ cache: { enabled: false },
103
+ enableLogs: true
104
+ });
105
+
106
+ getCurrentLanguage(resourceKey: string): Observable<string> {
107
+ return from(this.client.getCurrentLanguage(resourceKey));
108
+ }
109
+
110
+ getByLanguage(resourceKey: string, languageKey: string): Observable<string> {
111
+ return from(this.client.getByLanguage(resourceKey, languageKey));
112
+ }
113
+
114
+ listResources(): Observable<readonly LocalizationListResource[]> {
115
+ return from(this.client.listResources());
87
116
  }
88
117
  }
89
118
  ~~~
90
119
 
120
+ ### Forma com provider Angular (opcional)
121
+
122
+ Modo alternativo: integracao via provider Angular (`provideLocalization` + `LocalizationService`).
123
+
91
124
  ## Integracao com React
92
125
 
93
126
  ~~~tsx
@@ -117,6 +150,8 @@ export function HomeTitle() {
117
150
  - `getCurrentLanguage(resourceKey)`: retorna `string`
118
151
  - `getByLanguage(resourceKey, languageKey)`: retorna `string`
119
152
  - `listResources()`: retorna `LocalizationListResource[]`
153
+ - `invalidateCache(key?)`: invalida uma chave especifica ou todas as entradas
154
+ - `clearCache()`: limpa todo o cache configurado
120
155
 
121
156
  ~~~ts
122
157
  export interface LocalizationListResource {
@@ -174,10 +209,15 @@ try {
174
209
 
175
210
  ## Cache configuravel
176
211
 
177
- Por padrao, voce pode usar o MemoryCacheProvider.
212
+ Quando cache.enabled = true, o provider padrao e MemoryCacheProvider.
178
213
 
179
214
  ~~~ts
180
- import { createLocalizationClient, MemoryCacheProvider } from '@smartcorehub/localization-sdk';
215
+ import {
216
+ createLocalizationClient,
217
+ MemoryCacheProvider,
218
+ LocalStorageCacheProvider,
219
+ SessionStorageCacheProvider
220
+ } from '@smartcorehub/localization-sdk';
181
221
 
182
222
  const client = createLocalizationClient({
183
223
  baseUrl: 'https://api.smartcorehub.io/',
@@ -188,6 +228,24 @@ const client = createLocalizationClient({
188
228
  ttlMs: 120000
189
229
  }
190
230
  });
231
+
232
+ const localStorageClient = createLocalizationClient({
233
+ baseUrl: 'https://api.smartcorehub.io/',
234
+ token: 'SEU_TOKEN',
235
+ cache: {
236
+ enabled: true,
237
+ provider: new LocalStorageCacheProvider()
238
+ }
239
+ });
240
+
241
+ const sessionStorageClient = createLocalizationClient({
242
+ baseUrl: 'https://api.smartcorehub.io/',
243
+ token: 'SEU_TOKEN',
244
+ cache: {
245
+ enabled: true,
246
+ provider: new SessionStorageCacheProvider()
247
+ }
248
+ });
191
249
  ~~~
192
250
 
193
251
  ## Logging opcional
@@ -206,21 +264,13 @@ const client = createLocalizationClient({
206
264
 
207
265
  Quando ativo, o SDK escreve trilhas em `console.debug`, `console.info` e `console.error` para facilitar o rastreio de requests, cache, sucesso e excecoes mapeadas.
208
266
 
209
- ## Comandos para desenvolvimento do pacote
210
-
211
- No diretorio do pacote:
212
-
213
- ~~~bash
214
- npm install
215
- npm run build
216
- npm test
217
- ~~~
218
-
219
- Para detalhes de publicacao em pipeline classico, consulte NPM-PUBLISH-GUIDE.md.
267
+ ## Documentacao tecnica para desenvolvimento
220
268
 
221
- Para automacao completa de CI/CD da publicacao, consulte NPM-AUTOMATION-GUIDE.md.
269
+ Para build, testes automatizados, smoke test e validacao de pacote publicado, consulte:
222
270
 
223
- Para fluxo interno de contribuicao e troubleshooting de desenvolvimento, consulte o arquivo DEVELOPMENT-GUIDE.md.
271
+ - SDK-DEVELOPMENT-TESTING.md
272
+ - MANUAL-TESTING.md
273
+ - DEVELOPMENT-GUIDE.md
224
274
 
225
275
  ## Licensing / Licenciamento
226
276
 
package/dist/index.d.mts CHANGED
@@ -38,6 +38,7 @@ interface HttpTransport {
38
38
  interface LocalizationClientOptions {
39
39
  readonly baseUrl: string;
40
40
  readonly token?: string;
41
+ readonly locale?: string;
41
42
  readonly authProvider?: AuthProvider;
42
43
  readonly timeoutMs?: number;
43
44
  readonly headers?: Readonly<Record<string, string>>;
@@ -58,11 +59,15 @@ interface LocalizationListResource {
58
59
  }
59
60
  type ResourceResponse = LocalizationListResource;
60
61
 
62
+ interface LocaleOverrideOptions {
63
+ readonly locale?: string;
64
+ }
61
65
  interface LocalizationClient {
62
- getCurrentLanguage(resourceKey: string): Promise<string>;
63
- getByLanguage(resourceKey: string, languageKey: string): Promise<string>;
66
+ getCurrentLanguage(resourceKey: string, options?: LocaleOverrideOptions): Promise<string>;
67
+ getByLanguage(resourceKey: string, languageKey: string, options?: LocaleOverrideOptions): Promise<string>;
64
68
  listResources(): Promise<readonly LocalizationListResource[]>;
65
69
  invalidateCache(key?: string): Promise<void>;
70
+ clearCache(): Promise<void>;
66
71
  }
67
72
 
68
73
  declare function createLocalizationClient(options: LocalizationClientOptions): LocalizationClient;
@@ -81,6 +86,22 @@ declare class MemoryCacheProvider implements CacheProvider {
81
86
  clear(): Promise<void>;
82
87
  }
83
88
 
89
+ declare class StorageCacheProvider implements CacheProvider {
90
+ private readonly storage;
91
+ constructor(storage: Storage);
92
+ get<T>(key: string): Promise<T | null>;
93
+ set<T>(key: string, value: T, ttlMs?: number): Promise<void>;
94
+ delete(key: string): Promise<void>;
95
+ clear(): Promise<void>;
96
+ private safeParse;
97
+ }
98
+ declare class LocalStorageCacheProvider extends StorageCacheProvider {
99
+ constructor();
100
+ }
101
+ declare class SessionStorageCacheProvider extends StorageCacheProvider {
102
+ constructor();
103
+ }
104
+
84
105
  interface FetchHttpTransportOptions {
85
106
  readonly baseUrl: string;
86
107
  readonly fetchFn?: typeof fetch;
@@ -137,9 +158,10 @@ declare class LocalizationService {
137
158
  getByLanguage(resourceKey: string, languageKey: string): Observable<string>;
138
159
  listResources(): Observable<readonly LocalizationListResource[]>;
139
160
  invalidateCache(key?: string): Observable<void>;
161
+ clearCache(): Observable<void>;
140
162
  }
141
163
 
142
164
  declare const LOCALIZATION_SDK_OPTIONS: InjectionToken<LocalizationClientOptions>;
143
165
  declare const LOCALIZATION_CLIENT: InjectionToken<LocalizationClient>;
144
166
 
145
- export { type AuthProvider, type CacheProvider, FetchHttpTransport, type HttpRequest, type HttpResponse, type HttpTransport, LOCALIZATION_CLIENT, LOCALIZATION_SDK_OPTIONS, LocalizationAuthError, type LocalizationClient, type LocalizationClientOptions, LocalizationError, type LocalizationListResource, LocalizationNetworkError, LocalizationServerError, LocalizationService, LocalizationTimeoutError, MemoryCacheProvider, ResourceNotFoundError, type ResourceResponse, type SDKMiddleware, XAuthTokenProvider, createLocalizationClient, provideLocalization };
167
+ export { type AuthProvider, type CacheProvider, FetchHttpTransport, type HttpRequest, type HttpResponse, type HttpTransport, LOCALIZATION_CLIENT, LOCALIZATION_SDK_OPTIONS, LocalStorageCacheProvider, LocalizationAuthError, type LocalizationClient, type LocalizationClientOptions, LocalizationError, type LocalizationListResource, LocalizationNetworkError, LocalizationServerError, LocalizationService, LocalizationTimeoutError, MemoryCacheProvider, ResourceNotFoundError, type ResourceResponse, type SDKMiddleware, SessionStorageCacheProvider, StorageCacheProvider, XAuthTokenProvider, createLocalizationClient, provideLocalization };
package/dist/index.d.ts CHANGED
@@ -38,6 +38,7 @@ interface HttpTransport {
38
38
  interface LocalizationClientOptions {
39
39
  readonly baseUrl: string;
40
40
  readonly token?: string;
41
+ readonly locale?: string;
41
42
  readonly authProvider?: AuthProvider;
42
43
  readonly timeoutMs?: number;
43
44
  readonly headers?: Readonly<Record<string, string>>;
@@ -58,11 +59,15 @@ interface LocalizationListResource {
58
59
  }
59
60
  type ResourceResponse = LocalizationListResource;
60
61
 
62
+ interface LocaleOverrideOptions {
63
+ readonly locale?: string;
64
+ }
61
65
  interface LocalizationClient {
62
- getCurrentLanguage(resourceKey: string): Promise<string>;
63
- getByLanguage(resourceKey: string, languageKey: string): Promise<string>;
66
+ getCurrentLanguage(resourceKey: string, options?: LocaleOverrideOptions): Promise<string>;
67
+ getByLanguage(resourceKey: string, languageKey: string, options?: LocaleOverrideOptions): Promise<string>;
64
68
  listResources(): Promise<readonly LocalizationListResource[]>;
65
69
  invalidateCache(key?: string): Promise<void>;
70
+ clearCache(): Promise<void>;
66
71
  }
67
72
 
68
73
  declare function createLocalizationClient(options: LocalizationClientOptions): LocalizationClient;
@@ -81,6 +86,22 @@ declare class MemoryCacheProvider implements CacheProvider {
81
86
  clear(): Promise<void>;
82
87
  }
83
88
 
89
+ declare class StorageCacheProvider implements CacheProvider {
90
+ private readonly storage;
91
+ constructor(storage: Storage);
92
+ get<T>(key: string): Promise<T | null>;
93
+ set<T>(key: string, value: T, ttlMs?: number): Promise<void>;
94
+ delete(key: string): Promise<void>;
95
+ clear(): Promise<void>;
96
+ private safeParse;
97
+ }
98
+ declare class LocalStorageCacheProvider extends StorageCacheProvider {
99
+ constructor();
100
+ }
101
+ declare class SessionStorageCacheProvider extends StorageCacheProvider {
102
+ constructor();
103
+ }
104
+
84
105
  interface FetchHttpTransportOptions {
85
106
  readonly baseUrl: string;
86
107
  readonly fetchFn?: typeof fetch;
@@ -137,9 +158,10 @@ declare class LocalizationService {
137
158
  getByLanguage(resourceKey: string, languageKey: string): Observable<string>;
138
159
  listResources(): Observable<readonly LocalizationListResource[]>;
139
160
  invalidateCache(key?: string): Observable<void>;
161
+ clearCache(): Observable<void>;
140
162
  }
141
163
 
142
164
  declare const LOCALIZATION_SDK_OPTIONS: InjectionToken<LocalizationClientOptions>;
143
165
  declare const LOCALIZATION_CLIENT: InjectionToken<LocalizationClient>;
144
166
 
145
- export { type AuthProvider, type CacheProvider, FetchHttpTransport, type HttpRequest, type HttpResponse, type HttpTransport, LOCALIZATION_CLIENT, LOCALIZATION_SDK_OPTIONS, LocalizationAuthError, type LocalizationClient, type LocalizationClientOptions, LocalizationError, type LocalizationListResource, LocalizationNetworkError, LocalizationServerError, LocalizationService, LocalizationTimeoutError, MemoryCacheProvider, ResourceNotFoundError, type ResourceResponse, type SDKMiddleware, XAuthTokenProvider, createLocalizationClient, provideLocalization };
167
+ export { type AuthProvider, type CacheProvider, FetchHttpTransport, type HttpRequest, type HttpResponse, type HttpTransport, LOCALIZATION_CLIENT, LOCALIZATION_SDK_OPTIONS, LocalStorageCacheProvider, LocalizationAuthError, type LocalizationClient, type LocalizationClientOptions, LocalizationError, type LocalizationListResource, LocalizationNetworkError, LocalizationServerError, LocalizationService, LocalizationTimeoutError, MemoryCacheProvider, ResourceNotFoundError, type ResourceResponse, type SDKMiddleware, SessionStorageCacheProvider, StorageCacheProvider, XAuthTokenProvider, createLocalizationClient, provideLocalization };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- 'use strict';var core=require('@angular/core'),rxjs=require('rxjs');var H=(o,e,t,r)=>{for(var n=e,i=o.length-1,s;i>=0;i--)(s=o[i])&&(n=(s(n))||n);return n},I=(o,e)=>(t,r)=>e(t,r,o);var u=class{constructor(e){this.token=e;}async getToken(){return this.token?.trim()?this.token:null}};var d=class{constructor(){this.store=new Map;}async get(e){let t=this.store.get(e);return t?t.expiresAt!==void 0&&Date.now()>t.expiresAt?(this.store.delete(e),null):t.value:null}async set(e,t,r){let n=r&&r>0?Date.now()+r:void 0;this.store.set(e,{value:t,expiresAt:n});}async delete(e){this.store.delete(e);}async clear(){this.store.clear();}};var a=class extends Error{constructor(t,r){super(t);this.metadata=r;this.name="LocalizationError";}},p=class extends a{constructor(e="Authentication failed",t){super(e,t),this.name="LocalizationAuthError";}},h=class extends a{constructor(e="Resource not found",t){super(e,t),this.name="ResourceNotFoundError";}},g=class extends a{constructor(e="Localization server error",t){super(e,t),this.name="LocalizationServerError";}},m=class extends a{constructor(e="Network error while contacting localization API",t){super(e,t),this.name="LocalizationNetworkError";}},f=class extends a{constructor(e="Localization request timeout",t){super(e,t),this.name="LocalizationTimeoutError";}};var v=class extends Error{constructor(t,r,n){super(`Request failed with status ${t}`);this.statusCode=t;this.responseBody=r;this.responseHeaders=n;this.name="HttpStatusError";}},L=class extends Error{constructor(t){super(`Request timed out after ${t}ms`);this.timeoutMs=t;this.name="HttpTimeoutError";}},l=class extends Error{constructor(t){super("Network error while sending request");this.causeError=t;this.name="HttpNetworkError";}},y=class{constructor(e){this.options=e;this.fetchFn=e.fetchFn??globalThis.fetch.bind(globalThis);}async request(e){let t=e.timeoutMs??this.options.timeoutMs,r=new AbortController,n=setTimeout(()=>r.abort(),t),i=new Request(this.resolveUrl(e),{method:e.method,headers:{"Content-Type":"application/json",...this.options.defaultHeaders,...e.headers},body:e.body===void 0?void 0:JSON.stringify(e.body),signal:r.signal});try{let s=await this.runRequestMiddleware(i),O;try{O=await this.fetchFn(s);}catch(E){throw this.isAbortError(E)?new L(t):new l(E)}let c=await this.runResponseMiddleware(O);if(!c.ok){let E=await c.text();throw new v(c.status,E,c.headers)}let $=await this.parseResponse(c);return {status:c.status,headers:c.headers,data:$}}catch(s){throw await this.runErrorMiddleware(this.toError(s)),s}finally{clearTimeout(n);}}resolveUrl(e){let t=new URL(e.path,this.options.baseUrl);return e.query&&Object.entries(e.query).forEach(([r,n])=>{n!==void 0&&t.searchParams.set(r,String(n));}),t.toString()}async runRequestMiddleware(e){let t=e;for(let r of this.options.middleware??[])r.onRequest&&(t=await r.onRequest(t));return t}async runResponseMiddleware(e){let t=e;for(let r of this.options.middleware??[])r.onResponse&&(t=await r.onResponse(t));return t}async runErrorMiddleware(e){for(let t of this.options.middleware??[])t.onError&&await t.onError(e);}async parseResponse(e){return e.status===204?null:(e.headers.get("content-type")??"").includes("application/json")?await e.json():await e.text()}toError(e){return e instanceof Error?e:new Error("Unknown request error")}isAbortError(e){return e instanceof DOMException&&e.name==="AbortError"}};var k="localization:listResources",T=class{constructor(e,t,r){this.cacheProvider=e;this.cacheTtlMs=t;this.logger=r;}async getListResources(){this.logger.debug(`listResources:start cacheEnabled=${!!this.cacheProvider}`);let e=await this.cacheProvider?.get(k);return e?this.logger.info(`listResources:cache-hit count=${e.length}`):this.logger.debug("listResources:cache-miss"),e}async storeListResources(e){await this.cacheProvider?.set(k,e,this.cacheTtlMs),this.logger.debug(`listResources:cache-store ttlMs=${this.cacheTtlMs??0} count=${e.length}`);}async invalidate(e){if(this.logger.debug(`invalidateCache:start scope=${e??"all"}`),!this.cacheProvider){this.logger.debug("invalidateCache:skipped no-cache-provider");return}if(e){await this.cacheProvider.delete(e),this.logger.info(`invalidateCache:deleted key=${e}`);return}await this.cacheProvider.clear(),this.logger.info("invalidateCache:cleared-all");}};var b=class{constructor(e){this.logger=e;}async run(e,t){try{return await t()}catch(r){let n=this.map(r);throw this.logger.errorForOperation(e,n,r),n}}validateRequired(e,t){if(!e?.trim())throw new a(`${t} is required`)}map(e){return e instanceof a?e:e instanceof L?new f(e.message):e instanceof l?new m(e.message):e instanceof v?this.mapStatusError(e):new a("Unexpected localization SDK error")}preserveCause(e){let t=this.map(e);return t.cause??(t.cause=e),t}mapStatusError(e){return e.statusCode===401||e.statusCode===403?new p("Invalid token for localization endpoint",{statusCode:e.statusCode}):e.statusCode===404?new h("Localization resource was not found",{statusCode:e.statusCode}):e.statusCode>=500?new g("Localization API is unavailable",{statusCode:e.statusCode}):new a(e.message,{statusCode:e.statusCode})}};var q="[LocalizationSdk]",R=class{constructor(e=false){this.enabled=e;}debug(e){this.write("debug",e);}info(e){this.write("info",e);}errorForOperation(e,t,r){this.write("error",`${e}:error ${this.formatErrorDetails(t,r)}`);}formatErrorDetails(e,t){let r=this.getOriginalCause(e,t),n=this.getErrorMessage(r),i=[`type=${e.name}`,`message=${e.message}`];return n&&n!==e.message&&i.push(`original=${n}`),e instanceof a&&e.metadata?.statusCode&&i.push(`statusCode=${e.metadata.statusCode}`),i.join(" ")}getOriginalCause(e,t){let n=e.cause??t;return n instanceof l?n.causeError:n}getErrorMessage(e){return e instanceof Error?e.message:typeof e=="string"?e:"unknown-error"}write(e,t){if(!this.enabled)return;let r=`${q} ${t}`;switch(e){case "debug":console.debug(r);break;case "info":console.info(r);break;case "error":console.error(r);break}}};var z=class{constructor(e){this.dependencies=e;this.logger=new R(e.enableLogs),this.cacheHelper=new T(e.cacheProvider,e.cacheTtlMs,this.logger),this.errorHandler=new b(this.logger);}async getCurrentLanguage(e){return this.errorHandler.run("getCurrentLanguage",async()=>{this.errorHandler.validateRequired(e,"resourceKey"),this.logger.debug(`getCurrentLanguage:start resourceKey=${e}`);let t=await this.send({method:"GET",path:"api/localization/client/currentlanguage",query:{resourceKey:e}});return this.logger.info(`getCurrentLanguage:success resourceKey=${e} hasValue=${!!t}`),t??""})}async getByLanguage(e,t){return this.errorHandler.run("getByLanguage",async()=>{this.errorHandler.validateRequired(e,"resourceKey"),this.errorHandler.validateRequired(t,"languageKey"),this.logger.debug(`getByLanguage:start resourceKey=${e} languageKey=${t}`);let r=await this.send({method:"GET",path:"api/localization/client/bylanguage",query:{resourceKey:e,languageKey:t}});return this.logger.info(`getByLanguage:success resourceKey=${e} languageKey=${t} hasValue=${!!r}`),r??""})}async listResources(){return this.errorHandler.run("listResources",async()=>{let e=await this.cacheHelper.getListResources();if(e)return e;let r=(await this.send({method:"GET",path:"api/localization/client/list"})??[]).map(n=>({key:n.resourceKey,value:n.value,language:n.languageKey}));return await this.cacheHelper.storeListResources(r),this.logger.info(`listResources:success count=${r.length}`),r})}async invalidateCache(e){return this.errorHandler.run("invalidateCache",async()=>this.cacheHelper.invalidate(e))}async send(e){try{let t=await this.dependencies.authProvider?.getToken(),r=t?{...e.headers,"X-Auth-Token":t}:e.headers;this.logger.debug(`request:start method=${e.method} path=${e.path} hasAuth=${!!t}`);let n=await this.dependencies.httpTransport.request({...e,headers:r});return this.logger.debug(`request:success method=${e.method} path=${e.path} status=${n.status}`),n.data}catch(t){throw this.errorHandler.preserveCause(t)}}};function x(o){D(o);let t=o.cache?.enabled??false?o.cache?.provider??new d:void 0,r=new y({baseUrl:o.baseUrl,fetchFn:o.fetchFn,timeoutMs:o.timeoutMs??1e4,defaultHeaders:o.headers,middleware:o.middleware});return new z({httpTransport:r,authProvider:o.authProvider??(o.token?new u(o.token):void 0),cacheProvider:t,cacheTtlMs:o.cache?.ttlMs,enableLogs:o.enableLogs??false})}function D(o){if(!o?.baseUrl?.trim())throw new a("baseUrl is required to create localization client")}var A=new core.InjectionToken("LOCALIZATION_SDK_OPTIONS"),w=new core.InjectionToken("LOCALIZATION_CLIENT");function F(o){return core.makeEnvironmentProviders([{provide:A,useValue:o},{provide:w,useFactory:()=>x(o)}])}exports.LocalizationService=class C{constructor(e){this.client=e;}getCurrentLanguage(e){return rxjs.from(this.client.getCurrentLanguage(e))}getByLanguage(e,t){return rxjs.from(this.client.getByLanguage(e,t))}listResources(){return rxjs.from(this.client.listResources())}invalidateCache(e){return rxjs.from(this.client.invalidateCache(e))}};exports.LocalizationService=H([core.Injectable({providedIn:"root"}),I(0,core.Inject(w))],exports.LocalizationService);exports.FetchHttpTransport=y;exports.LOCALIZATION_CLIENT=w;exports.LOCALIZATION_SDK_OPTIONS=A;exports.LocalizationAuthError=p;exports.LocalizationError=a;exports.LocalizationNetworkError=m;exports.LocalizationServerError=g;exports.LocalizationTimeoutError=f;exports.MemoryCacheProvider=d;exports.ResourceNotFoundError=h;exports.XAuthTokenProvider=u;exports.createLocalizationClient=x;exports.provideLocalization=F;
1
+ 'use strict';var core=require('@angular/core'),rxjs=require('rxjs');var k=(o,e,r,t)=>{for(var n=e,i=o.length-1,s;i>=0;i--)(s=o[i])&&(n=(s(n))||n);return n},$=(o,e)=>(r,t)=>e(r,t,o);var u=class{constructor(e){this.token=e;}async getToken(){return this.token?.trim()?this.token:null}};var d=class{constructor(){this.store=new Map;}async get(e){let r=this.store.get(e);return r?r.expiresAt!==void 0&&Date.now()>r.expiresAt?(this.store.delete(e),null):r.value:null}async set(e,r,t){let n=t&&t>0?Date.now()+t:void 0;this.store.set(e,{value:r,expiresAt:n});}async delete(e){this.store.delete(e);}async clear(){this.store.clear();}};var a=class extends Error{constructor(r,t){super(r);this.metadata=t;this.name="LocalizationError";}},h=class extends a{constructor(e="Authentication failed",r){super(e,r),this.name="LocalizationAuthError";}},p=class extends a{constructor(e="Resource not found",r){super(e,r),this.name="ResourceNotFoundError";}},g=class extends a{constructor(e="Localization server error",r){super(e,r),this.name="LocalizationServerError";}},m=class extends a{constructor(e="Network error while contacting localization API",r){super(e,r),this.name="LocalizationNetworkError";}},f=class extends a{constructor(e="Localization request timeout",r){super(e,r),this.name="LocalizationTimeoutError";}};var v=class extends Error{constructor(r,t,n){super(`Request failed with status ${r}`);this.statusCode=r;this.responseBody=t;this.responseHeaders=n;this.name="HttpStatusError";}},y=class extends Error{constructor(r){super(`Request timed out after ${r}ms`);this.timeoutMs=r;this.name="HttpTimeoutError";}},l=class extends Error{constructor(r){super("Network error while sending request");this.causeError=r;this.name="HttpNetworkError";}},L=class{constructor(e){this.options=e;this.fetchFn=e.fetchFn??globalThis.fetch.bind(globalThis);}async request(e){let r=e.timeoutMs??this.options.timeoutMs,t=new AbortController,n=setTimeout(()=>t.abort(),r),i=new Request(this.resolveUrl(e),{method:e.method,headers:{"Content-Type":"application/json",...this.options.defaultHeaders,...e.headers},body:e.body===void 0?void 0:JSON.stringify(e.body),signal:t.signal});try{let s=await this.runRequestMiddleware(i),S;try{S=await this.fetchFn(s);}catch(b){throw this.isAbortError(b)?new y(r):new l(b)}let c=await this.runResponseMiddleware(S);if(!c.ok){let b=await c.text();throw new v(c.status,b,c.headers)}let D=await this.parseResponse(c);return {status:c.status,headers:c.headers,data:D}}catch(s){throw await this.runErrorMiddleware(this.toError(s)),s}finally{clearTimeout(n);}}resolveUrl(e){let r=new URL(e.path,this.options.baseUrl);return e.query&&Object.entries(e.query).forEach(([t,n])=>{n!==void 0&&r.searchParams.set(t,String(n));}),r.toString()}async runRequestMiddleware(e){let r=e;for(let t of this.options.middleware??[])t.onRequest&&(r=await t.onRequest(r));return r}async runResponseMiddleware(e){let r=e;for(let t of this.options.middleware??[])t.onResponse&&(r=await t.onResponse(r));return r}async runErrorMiddleware(e){for(let r of this.options.middleware??[])r.onError&&await r.onError(e);}async parseResponse(e){return e.status===204?null:(e.headers.get("content-type")??"").includes("application/json")?await e.json():await e.text()}toError(e){return e instanceof Error?e:new Error("Unknown request error")}isAbortError(e){return e instanceof DOMException&&e.name==="AbortError"}};var M="localization:listResources",R=class{constructor(e,r,t){this.cacheProvider=e;this.cacheTtlMs=r;this.logger=t;}async getListResources(){this.logger.debug(`listResources:start cacheEnabled=${!!this.cacheProvider}`);let e=await this.cacheProvider?.get(M);return e?this.logger.info(`listResources:cache-hit count=${e.length}`):this.logger.debug("listResources:cache-miss"),e}async storeListResources(e){await this.cacheProvider?.set(M,e,this.cacheTtlMs),this.logger.debug(`listResources:cache-store ttlMs=${this.cacheTtlMs??0} count=${e.length}`);}async invalidate(e){if(this.logger.debug(`invalidateCache:start scope=${e??"all"}`),!this.cacheProvider){this.logger.debug("invalidateCache:skipped no-cache-provider");return}if(e){await this.cacheProvider.delete(e),this.logger.info(`invalidateCache:deleted key=${e}`);return}await this.cacheProvider.clear(),this.logger.info("invalidateCache:cleared-all");}};var z=class{constructor(e){this.logger=e;}async run(e,r){try{return await r()}catch(t){let n=this.map(t);throw this.logger.errorForOperation(e,n,t),n}}validateRequired(e,r){if(!e?.trim())throw new a(`${r} is required`)}map(e){return e instanceof a?e:e instanceof y?new f(e.message):e instanceof l?new m(e.message):e instanceof v?this.mapStatusError(e):new a("Unexpected localization SDK error")}preserveCause(e){let r=this.map(e);return r.cause??(r.cause=e),r}mapStatusError(e){return e.statusCode===401||e.statusCode===403?new h("Invalid token for localization endpoint",{statusCode:e.statusCode}):e.statusCode===404?new p("Localization resource was not found",{statusCode:e.statusCode}):e.statusCode>=500?new g("Localization API is unavailable",{statusCode:e.statusCode}):new a(e.message,{statusCode:e.statusCode})}};var K="[LocalizationSdk]",P=class{constructor(e=false){this.enabled=e;}debug(e){this.write("debug",e);}info(e){this.write("info",e);}errorForOperation(e,r,t){this.write("error",`${e}:error ${this.formatErrorDetails(r,t)}`);}formatErrorDetails(e,r){let t=this.getOriginalCause(e,r),n=this.getErrorMessage(t),i=[`type=${e.name}`,`message=${e.message}`];return n&&n!==e.message&&i.push(`original=${n}`),e instanceof a&&e.metadata?.statusCode&&i.push(`statusCode=${e.metadata.statusCode}`),i.join(" ")}getOriginalCause(e,r){let n=e.cause??r;return n instanceof l?n.causeError:n}getErrorMessage(e){return e instanceof Error?e.message:typeof e=="string"?e:"unknown-error"}write(e,r){if(!this.enabled)return;let t=this.resolveIcon(e,r),n=`${K} ${t} ${r}`;switch(e){case "debug":console.debug(n);break;case "info":console.info(n);break;case "error":console.error(n);break}}resolveIcon(e,r){return e==="error"||r.includes(":error")?"\u274C":r.includes("request:start")?"\u{1F310}":r.includes("request:success")?"\u{1F4E1}":r.includes("cache-hit")?"\u26A1":r.includes("cache-miss")?"\u{1F4ED}":r.includes("cache-store")?"\u{1F4BE}":r.includes("invalidateCache")?"\u{1F9F9}":r.includes("listResources")?"\u{1F4DA}":r.includes("getByLanguage")?"\u{1F30D}":r.includes("getCurrentLanguage")?"\u{1F22F}":r.includes(":start")?"\u{1F680}":r.includes(":success")?"\u2705":e==="info"?"\u2139\uFE0F":"\u{1F50E}"}};var x=class{constructor(e){this.dependencies=e;this.logger=new P(e.enableLogs),this.cacheHelper=new R(e.cacheProvider,e.cacheTtlMs,this.logger),this.errorHandler=new z(this.logger);}async getCurrentLanguage(e,r){return this.errorHandler.run("getCurrentLanguage",async()=>{if(this.errorHandler.validateRequired(e,"resourceKey"),this.logger.debug(`getCurrentLanguage:start resourceKey=${e}`),this.dependencies.cacheProvider){let n=await this.resolveFromCachedResources(e);return this.logger.info(`getCurrentLanguage:cache-result resourceKey=${e} hasValue=${!!n}`),n}let t=await this.send({method:"GET",path:"api/localization/client/currentlanguage",query:{resourceKey:e},headers:this.resolveLocaleHeader(r)});return this.logger.info(`getCurrentLanguage:success resourceKey=${e} hasValue=${!!t}`),t??""})}async getByLanguage(e,r,t){return this.errorHandler.run("getByLanguage",async()=>{if(this.errorHandler.validateRequired(e,"resourceKey"),this.errorHandler.validateRequired(r,"languageKey"),this.logger.debug(`getByLanguage:start resourceKey=${e} languageKey=${r}`),this.dependencies.cacheProvider){let i=await this.resolveFromCachedResources(e,r);return this.logger.info(`getByLanguage:cache-result resourceKey=${e} languageKey=${r} hasValue=${!!i}`),i}let n=await this.send({method:"GET",path:"api/localization/client/bylanguage",query:{resourceKey:e,languageKey:r},headers:this.resolveLocaleHeader(t)});return this.logger.info(`getByLanguage:success resourceKey=${e} languageKey=${r} hasValue=${!!n}`),n??""})}async listResources(){return this.errorHandler.run("listResources",async()=>{let e=await this.cacheHelper.getListResources();if(e)return e;let t=(await this.send({method:"GET",path:"api/localization/client/list"})??[]).map(n=>({key:n.resourceKey,value:n.value,language:n.languageKey}));return await this.cacheHelper.storeListResources(t),this.logger.info(`listResources:success count=${t.length}`),t})}async invalidateCache(e){return this.errorHandler.run("invalidateCache",async()=>this.cacheHelper.invalidate(e))}async clearCache(){return this.errorHandler.run("clearCache",async()=>this.cacheHelper.invalidate())}async resolveFromCachedResources(e,r){return (await this.listResources()).find(i=>i.key!==e?false:r===void 0?true:i.language===r)?.value??""}async send(e){try{let r=await this.dependencies.authProvider?.getToken(),t=r?{...e.headers,"X-Auth-Token":r}:e.headers;this.logger.debug(`request:start method=${e.method} path=${e.path} hasAuth=${!!r}`);let n=await this.dependencies.httpTransport.request({...e,headers:t});return this.logger.debug(`request:success method=${e.method} path=${e.path} status=${n.status}`),n.data}catch(r){throw this.errorHandler.preserveCause(r)}}resolveLocaleHeader(e){let r=e?.locale?.trim()||this.dependencies.locale?.trim()||this.resolveBrowserLocale();if(r)return {"Accept-Language":r}}resolveBrowserLocale(){return globalThis.navigator?.language?.trim()||void 0}};function O(o){_(o);let r=o.cache?.enabled??false?o.cache?.provider??new d:void 0,t=new L({baseUrl:o.baseUrl,fetchFn:o.fetchFn,timeoutMs:o.timeoutMs??1e4,defaultHeaders:o.headers,middleware:o.middleware});return new x({httpTransport:t,authProvider:o.authProvider??(o.token?new u(o.token):void 0),locale:o.locale,cacheProvider:r,cacheTtlMs:o.cache?.ttlMs,enableLogs:o.enableLogs??false})}function _(o){if(!o?.baseUrl?.trim())throw new a("baseUrl is required to create localization client")}var w=class{constructor(e){this.storage=e;}async get(e){let r=this.storage.getItem(e);if(!r)return null;let t=this.safeParse(r);return t?t.expiresAt!==void 0&&Date.now()>t.expiresAt?(this.storage.removeItem(e),null):t.value:(this.storage.removeItem(e),null)}async set(e,r,t){let n=t&&t>0?Date.now()+t:void 0,i={value:r,expiresAt:n};this.storage.setItem(e,JSON.stringify(i));}async delete(e){this.storage.removeItem(e);}async clear(){this.storage.clear();}safeParse(e){try{return JSON.parse(e)}catch{return null}}},A=class extends w{constructor(){super(N("localStorage"));}},H=class extends w{constructor(){super(N("sessionStorage"));}};function N(o){let e=typeof window>"u"?void 0:window[o];return e||U()}function U(){let o=new Map;return {get length(){return o.size},clear:()=>{o.clear();},getItem:e=>o.get(e)??null,key:e=>Array.from(o.keys())[e]??null,removeItem:e=>{o.delete(e);},setItem:(e,r)=>{o.set(e,r);}}}var I=new core.InjectionToken("LOCALIZATION_SDK_OPTIONS"),C=new core.InjectionToken("LOCALIZATION_CLIENT");function Z(o){return core.makeEnvironmentProviders([{provide:I,useValue:o},{provide:C,useFactory:()=>O(o)}])}exports.LocalizationService=class E{constructor(e){this.client=e;}getCurrentLanguage(e){return rxjs.from(this.client.getCurrentLanguage(e))}getByLanguage(e,r){return rxjs.from(this.client.getByLanguage(e,r))}listResources(){return rxjs.from(this.client.listResources())}invalidateCache(e){return rxjs.from(this.client.invalidateCache(e))}clearCache(){return rxjs.from(this.client.clearCache())}};exports.LocalizationService=k([core.Injectable({providedIn:"root"}),$(0,core.Inject(C))],exports.LocalizationService);exports.FetchHttpTransport=L;exports.LOCALIZATION_CLIENT=C;exports.LOCALIZATION_SDK_OPTIONS=I;exports.LocalStorageCacheProvider=A;exports.LocalizationAuthError=h;exports.LocalizationError=a;exports.LocalizationNetworkError=m;exports.LocalizationServerError=g;exports.LocalizationTimeoutError=f;exports.MemoryCacheProvider=d;exports.ResourceNotFoundError=p;exports.SessionStorageCacheProvider=H;exports.StorageCacheProvider=w;exports.XAuthTokenProvider=u;exports.createLocalizationClient=O;exports.provideLocalization=Z;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import {InjectionToken,Injectable,Inject,makeEnvironmentProviders}from'@angular/core';import {from}from'rxjs';var H=(o,e,t,r)=>{for(var n=e,i=o.length-1,s;i>=0;i--)(s=o[i])&&(n=(s(n))||n);return n},I=(o,e)=>(t,r)=>e(t,r,o);var u=class{constructor(e){this.token=e;}async getToken(){return this.token?.trim()?this.token:null}};var d=class{constructor(){this.store=new Map;}async get(e){let t=this.store.get(e);return t?t.expiresAt!==void 0&&Date.now()>t.expiresAt?(this.store.delete(e),null):t.value:null}async set(e,t,r){let n=r&&r>0?Date.now()+r:void 0;this.store.set(e,{value:t,expiresAt:n});}async delete(e){this.store.delete(e);}async clear(){this.store.clear();}};var a=class extends Error{constructor(t,r){super(t);this.metadata=r;this.name="LocalizationError";}},p=class extends a{constructor(e="Authentication failed",t){super(e,t),this.name="LocalizationAuthError";}},h=class extends a{constructor(e="Resource not found",t){super(e,t),this.name="ResourceNotFoundError";}},g=class extends a{constructor(e="Localization server error",t){super(e,t),this.name="LocalizationServerError";}},m=class extends a{constructor(e="Network error while contacting localization API",t){super(e,t),this.name="LocalizationNetworkError";}},f=class extends a{constructor(e="Localization request timeout",t){super(e,t),this.name="LocalizationTimeoutError";}};var v=class extends Error{constructor(t,r,n){super(`Request failed with status ${t}`);this.statusCode=t;this.responseBody=r;this.responseHeaders=n;this.name="HttpStatusError";}},L=class extends Error{constructor(t){super(`Request timed out after ${t}ms`);this.timeoutMs=t;this.name="HttpTimeoutError";}},l=class extends Error{constructor(t){super("Network error while sending request");this.causeError=t;this.name="HttpNetworkError";}},y=class{constructor(e){this.options=e;this.fetchFn=e.fetchFn??globalThis.fetch.bind(globalThis);}async request(e){let t=e.timeoutMs??this.options.timeoutMs,r=new AbortController,n=setTimeout(()=>r.abort(),t),i=new Request(this.resolveUrl(e),{method:e.method,headers:{"Content-Type":"application/json",...this.options.defaultHeaders,...e.headers},body:e.body===void 0?void 0:JSON.stringify(e.body),signal:r.signal});try{let s=await this.runRequestMiddleware(i),O;try{O=await this.fetchFn(s);}catch(E){throw this.isAbortError(E)?new L(t):new l(E)}let c=await this.runResponseMiddleware(O);if(!c.ok){let E=await c.text();throw new v(c.status,E,c.headers)}let $=await this.parseResponse(c);return {status:c.status,headers:c.headers,data:$}}catch(s){throw await this.runErrorMiddleware(this.toError(s)),s}finally{clearTimeout(n);}}resolveUrl(e){let t=new URL(e.path,this.options.baseUrl);return e.query&&Object.entries(e.query).forEach(([r,n])=>{n!==void 0&&t.searchParams.set(r,String(n));}),t.toString()}async runRequestMiddleware(e){let t=e;for(let r of this.options.middleware??[])r.onRequest&&(t=await r.onRequest(t));return t}async runResponseMiddleware(e){let t=e;for(let r of this.options.middleware??[])r.onResponse&&(t=await r.onResponse(t));return t}async runErrorMiddleware(e){for(let t of this.options.middleware??[])t.onError&&await t.onError(e);}async parseResponse(e){return e.status===204?null:(e.headers.get("content-type")??"").includes("application/json")?await e.json():await e.text()}toError(e){return e instanceof Error?e:new Error("Unknown request error")}isAbortError(e){return e instanceof DOMException&&e.name==="AbortError"}};var k="localization:listResources",T=class{constructor(e,t,r){this.cacheProvider=e;this.cacheTtlMs=t;this.logger=r;}async getListResources(){this.logger.debug(`listResources:start cacheEnabled=${!!this.cacheProvider}`);let e=await this.cacheProvider?.get(k);return e?this.logger.info(`listResources:cache-hit count=${e.length}`):this.logger.debug("listResources:cache-miss"),e}async storeListResources(e){await this.cacheProvider?.set(k,e,this.cacheTtlMs),this.logger.debug(`listResources:cache-store ttlMs=${this.cacheTtlMs??0} count=${e.length}`);}async invalidate(e){if(this.logger.debug(`invalidateCache:start scope=${e??"all"}`),!this.cacheProvider){this.logger.debug("invalidateCache:skipped no-cache-provider");return}if(e){await this.cacheProvider.delete(e),this.logger.info(`invalidateCache:deleted key=${e}`);return}await this.cacheProvider.clear(),this.logger.info("invalidateCache:cleared-all");}};var b=class{constructor(e){this.logger=e;}async run(e,t){try{return await t()}catch(r){let n=this.map(r);throw this.logger.errorForOperation(e,n,r),n}}validateRequired(e,t){if(!e?.trim())throw new a(`${t} is required`)}map(e){return e instanceof a?e:e instanceof L?new f(e.message):e instanceof l?new m(e.message):e instanceof v?this.mapStatusError(e):new a("Unexpected localization SDK error")}preserveCause(e){let t=this.map(e);return t.cause??(t.cause=e),t}mapStatusError(e){return e.statusCode===401||e.statusCode===403?new p("Invalid token for localization endpoint",{statusCode:e.statusCode}):e.statusCode===404?new h("Localization resource was not found",{statusCode:e.statusCode}):e.statusCode>=500?new g("Localization API is unavailable",{statusCode:e.statusCode}):new a(e.message,{statusCode:e.statusCode})}};var q="[LocalizationSdk]",R=class{constructor(e=false){this.enabled=e;}debug(e){this.write("debug",e);}info(e){this.write("info",e);}errorForOperation(e,t,r){this.write("error",`${e}:error ${this.formatErrorDetails(t,r)}`);}formatErrorDetails(e,t){let r=this.getOriginalCause(e,t),n=this.getErrorMessage(r),i=[`type=${e.name}`,`message=${e.message}`];return n&&n!==e.message&&i.push(`original=${n}`),e instanceof a&&e.metadata?.statusCode&&i.push(`statusCode=${e.metadata.statusCode}`),i.join(" ")}getOriginalCause(e,t){let n=e.cause??t;return n instanceof l?n.causeError:n}getErrorMessage(e){return e instanceof Error?e.message:typeof e=="string"?e:"unknown-error"}write(e,t){if(!this.enabled)return;let r=`${q} ${t}`;switch(e){case "debug":console.debug(r);break;case "info":console.info(r);break;case "error":console.error(r);break}}};var z=class{constructor(e){this.dependencies=e;this.logger=new R(e.enableLogs),this.cacheHelper=new T(e.cacheProvider,e.cacheTtlMs,this.logger),this.errorHandler=new b(this.logger);}async getCurrentLanguage(e){return this.errorHandler.run("getCurrentLanguage",async()=>{this.errorHandler.validateRequired(e,"resourceKey"),this.logger.debug(`getCurrentLanguage:start resourceKey=${e}`);let t=await this.send({method:"GET",path:"api/localization/client/currentlanguage",query:{resourceKey:e}});return this.logger.info(`getCurrentLanguage:success resourceKey=${e} hasValue=${!!t}`),t??""})}async getByLanguage(e,t){return this.errorHandler.run("getByLanguage",async()=>{this.errorHandler.validateRequired(e,"resourceKey"),this.errorHandler.validateRequired(t,"languageKey"),this.logger.debug(`getByLanguage:start resourceKey=${e} languageKey=${t}`);let r=await this.send({method:"GET",path:"api/localization/client/bylanguage",query:{resourceKey:e,languageKey:t}});return this.logger.info(`getByLanguage:success resourceKey=${e} languageKey=${t} hasValue=${!!r}`),r??""})}async listResources(){return this.errorHandler.run("listResources",async()=>{let e=await this.cacheHelper.getListResources();if(e)return e;let r=(await this.send({method:"GET",path:"api/localization/client/list"})??[]).map(n=>({key:n.resourceKey,value:n.value,language:n.languageKey}));return await this.cacheHelper.storeListResources(r),this.logger.info(`listResources:success count=${r.length}`),r})}async invalidateCache(e){return this.errorHandler.run("invalidateCache",async()=>this.cacheHelper.invalidate(e))}async send(e){try{let t=await this.dependencies.authProvider?.getToken(),r=t?{...e.headers,"X-Auth-Token":t}:e.headers;this.logger.debug(`request:start method=${e.method} path=${e.path} hasAuth=${!!t}`);let n=await this.dependencies.httpTransport.request({...e,headers:r});return this.logger.debug(`request:success method=${e.method} path=${e.path} status=${n.status}`),n.data}catch(t){throw this.errorHandler.preserveCause(t)}}};function x(o){D(o);let t=o.cache?.enabled??false?o.cache?.provider??new d:void 0,r=new y({baseUrl:o.baseUrl,fetchFn:o.fetchFn,timeoutMs:o.timeoutMs??1e4,defaultHeaders:o.headers,middleware:o.middleware});return new z({httpTransport:r,authProvider:o.authProvider??(o.token?new u(o.token):void 0),cacheProvider:t,cacheTtlMs:o.cache?.ttlMs,enableLogs:o.enableLogs??false})}function D(o){if(!o?.baseUrl?.trim())throw new a("baseUrl is required to create localization client")}var A=new InjectionToken("LOCALIZATION_SDK_OPTIONS"),w=new InjectionToken("LOCALIZATION_CLIENT");function F(o){return makeEnvironmentProviders([{provide:A,useValue:o},{provide:w,useFactory:()=>x(o)}])}var C=class{constructor(e){this.client=e;}getCurrentLanguage(e){return from(this.client.getCurrentLanguage(e))}getByLanguage(e,t){return from(this.client.getByLanguage(e,t))}listResources(){return from(this.client.listResources())}invalidateCache(e){return from(this.client.invalidateCache(e))}};C=H([Injectable({providedIn:"root"}),I(0,Inject(w))],C);export{y as FetchHttpTransport,w as LOCALIZATION_CLIENT,A as LOCALIZATION_SDK_OPTIONS,p as LocalizationAuthError,a as LocalizationError,m as LocalizationNetworkError,g as LocalizationServerError,C as LocalizationService,f as LocalizationTimeoutError,d as MemoryCacheProvider,h as ResourceNotFoundError,u as XAuthTokenProvider,x as createLocalizationClient,F as provideLocalization};
1
+ import {InjectionToken,Injectable,Inject,makeEnvironmentProviders}from'@angular/core';import {from}from'rxjs';var k=(o,e,r,t)=>{for(var n=e,i=o.length-1,s;i>=0;i--)(s=o[i])&&(n=(s(n))||n);return n},$=(o,e)=>(r,t)=>e(r,t,o);var u=class{constructor(e){this.token=e;}async getToken(){return this.token?.trim()?this.token:null}};var d=class{constructor(){this.store=new Map;}async get(e){let r=this.store.get(e);return r?r.expiresAt!==void 0&&Date.now()>r.expiresAt?(this.store.delete(e),null):r.value:null}async set(e,r,t){let n=t&&t>0?Date.now()+t:void 0;this.store.set(e,{value:r,expiresAt:n});}async delete(e){this.store.delete(e);}async clear(){this.store.clear();}};var a=class extends Error{constructor(r,t){super(r);this.metadata=t;this.name="LocalizationError";}},h=class extends a{constructor(e="Authentication failed",r){super(e,r),this.name="LocalizationAuthError";}},p=class extends a{constructor(e="Resource not found",r){super(e,r),this.name="ResourceNotFoundError";}},g=class extends a{constructor(e="Localization server error",r){super(e,r),this.name="LocalizationServerError";}},m=class extends a{constructor(e="Network error while contacting localization API",r){super(e,r),this.name="LocalizationNetworkError";}},f=class extends a{constructor(e="Localization request timeout",r){super(e,r),this.name="LocalizationTimeoutError";}};var v=class extends Error{constructor(r,t,n){super(`Request failed with status ${r}`);this.statusCode=r;this.responseBody=t;this.responseHeaders=n;this.name="HttpStatusError";}},y=class extends Error{constructor(r){super(`Request timed out after ${r}ms`);this.timeoutMs=r;this.name="HttpTimeoutError";}},l=class extends Error{constructor(r){super("Network error while sending request");this.causeError=r;this.name="HttpNetworkError";}},L=class{constructor(e){this.options=e;this.fetchFn=e.fetchFn??globalThis.fetch.bind(globalThis);}async request(e){let r=e.timeoutMs??this.options.timeoutMs,t=new AbortController,n=setTimeout(()=>t.abort(),r),i=new Request(this.resolveUrl(e),{method:e.method,headers:{"Content-Type":"application/json",...this.options.defaultHeaders,...e.headers},body:e.body===void 0?void 0:JSON.stringify(e.body),signal:t.signal});try{let s=await this.runRequestMiddleware(i),S;try{S=await this.fetchFn(s);}catch(b){throw this.isAbortError(b)?new y(r):new l(b)}let c=await this.runResponseMiddleware(S);if(!c.ok){let b=await c.text();throw new v(c.status,b,c.headers)}let D=await this.parseResponse(c);return {status:c.status,headers:c.headers,data:D}}catch(s){throw await this.runErrorMiddleware(this.toError(s)),s}finally{clearTimeout(n);}}resolveUrl(e){let r=new URL(e.path,this.options.baseUrl);return e.query&&Object.entries(e.query).forEach(([t,n])=>{n!==void 0&&r.searchParams.set(t,String(n));}),r.toString()}async runRequestMiddleware(e){let r=e;for(let t of this.options.middleware??[])t.onRequest&&(r=await t.onRequest(r));return r}async runResponseMiddleware(e){let r=e;for(let t of this.options.middleware??[])t.onResponse&&(r=await t.onResponse(r));return r}async runErrorMiddleware(e){for(let r of this.options.middleware??[])r.onError&&await r.onError(e);}async parseResponse(e){return e.status===204?null:(e.headers.get("content-type")??"").includes("application/json")?await e.json():await e.text()}toError(e){return e instanceof Error?e:new Error("Unknown request error")}isAbortError(e){return e instanceof DOMException&&e.name==="AbortError"}};var M="localization:listResources",R=class{constructor(e,r,t){this.cacheProvider=e;this.cacheTtlMs=r;this.logger=t;}async getListResources(){this.logger.debug(`listResources:start cacheEnabled=${!!this.cacheProvider}`);let e=await this.cacheProvider?.get(M);return e?this.logger.info(`listResources:cache-hit count=${e.length}`):this.logger.debug("listResources:cache-miss"),e}async storeListResources(e){await this.cacheProvider?.set(M,e,this.cacheTtlMs),this.logger.debug(`listResources:cache-store ttlMs=${this.cacheTtlMs??0} count=${e.length}`);}async invalidate(e){if(this.logger.debug(`invalidateCache:start scope=${e??"all"}`),!this.cacheProvider){this.logger.debug("invalidateCache:skipped no-cache-provider");return}if(e){await this.cacheProvider.delete(e),this.logger.info(`invalidateCache:deleted key=${e}`);return}await this.cacheProvider.clear(),this.logger.info("invalidateCache:cleared-all");}};var z=class{constructor(e){this.logger=e;}async run(e,r){try{return await r()}catch(t){let n=this.map(t);throw this.logger.errorForOperation(e,n,t),n}}validateRequired(e,r){if(!e?.trim())throw new a(`${r} is required`)}map(e){return e instanceof a?e:e instanceof y?new f(e.message):e instanceof l?new m(e.message):e instanceof v?this.mapStatusError(e):new a("Unexpected localization SDK error")}preserveCause(e){let r=this.map(e);return r.cause??(r.cause=e),r}mapStatusError(e){return e.statusCode===401||e.statusCode===403?new h("Invalid token for localization endpoint",{statusCode:e.statusCode}):e.statusCode===404?new p("Localization resource was not found",{statusCode:e.statusCode}):e.statusCode>=500?new g("Localization API is unavailable",{statusCode:e.statusCode}):new a(e.message,{statusCode:e.statusCode})}};var K="[LocalizationSdk]",P=class{constructor(e=false){this.enabled=e;}debug(e){this.write("debug",e);}info(e){this.write("info",e);}errorForOperation(e,r,t){this.write("error",`${e}:error ${this.formatErrorDetails(r,t)}`);}formatErrorDetails(e,r){let t=this.getOriginalCause(e,r),n=this.getErrorMessage(t),i=[`type=${e.name}`,`message=${e.message}`];return n&&n!==e.message&&i.push(`original=${n}`),e instanceof a&&e.metadata?.statusCode&&i.push(`statusCode=${e.metadata.statusCode}`),i.join(" ")}getOriginalCause(e,r){let n=e.cause??r;return n instanceof l?n.causeError:n}getErrorMessage(e){return e instanceof Error?e.message:typeof e=="string"?e:"unknown-error"}write(e,r){if(!this.enabled)return;let t=this.resolveIcon(e,r),n=`${K} ${t} ${r}`;switch(e){case "debug":console.debug(n);break;case "info":console.info(n);break;case "error":console.error(n);break}}resolveIcon(e,r){return e==="error"||r.includes(":error")?"\u274C":r.includes("request:start")?"\u{1F310}":r.includes("request:success")?"\u{1F4E1}":r.includes("cache-hit")?"\u26A1":r.includes("cache-miss")?"\u{1F4ED}":r.includes("cache-store")?"\u{1F4BE}":r.includes("invalidateCache")?"\u{1F9F9}":r.includes("listResources")?"\u{1F4DA}":r.includes("getByLanguage")?"\u{1F30D}":r.includes("getCurrentLanguage")?"\u{1F22F}":r.includes(":start")?"\u{1F680}":r.includes(":success")?"\u2705":e==="info"?"\u2139\uFE0F":"\u{1F50E}"}};var x=class{constructor(e){this.dependencies=e;this.logger=new P(e.enableLogs),this.cacheHelper=new R(e.cacheProvider,e.cacheTtlMs,this.logger),this.errorHandler=new z(this.logger);}async getCurrentLanguage(e,r){return this.errorHandler.run("getCurrentLanguage",async()=>{if(this.errorHandler.validateRequired(e,"resourceKey"),this.logger.debug(`getCurrentLanguage:start resourceKey=${e}`),this.dependencies.cacheProvider){let n=await this.resolveFromCachedResources(e);return this.logger.info(`getCurrentLanguage:cache-result resourceKey=${e} hasValue=${!!n}`),n}let t=await this.send({method:"GET",path:"api/localization/client/currentlanguage",query:{resourceKey:e},headers:this.resolveLocaleHeader(r)});return this.logger.info(`getCurrentLanguage:success resourceKey=${e} hasValue=${!!t}`),t??""})}async getByLanguage(e,r,t){return this.errorHandler.run("getByLanguage",async()=>{if(this.errorHandler.validateRequired(e,"resourceKey"),this.errorHandler.validateRequired(r,"languageKey"),this.logger.debug(`getByLanguage:start resourceKey=${e} languageKey=${r}`),this.dependencies.cacheProvider){let i=await this.resolveFromCachedResources(e,r);return this.logger.info(`getByLanguage:cache-result resourceKey=${e} languageKey=${r} hasValue=${!!i}`),i}let n=await this.send({method:"GET",path:"api/localization/client/bylanguage",query:{resourceKey:e,languageKey:r},headers:this.resolveLocaleHeader(t)});return this.logger.info(`getByLanguage:success resourceKey=${e} languageKey=${r} hasValue=${!!n}`),n??""})}async listResources(){return this.errorHandler.run("listResources",async()=>{let e=await this.cacheHelper.getListResources();if(e)return e;let t=(await this.send({method:"GET",path:"api/localization/client/list"})??[]).map(n=>({key:n.resourceKey,value:n.value,language:n.languageKey}));return await this.cacheHelper.storeListResources(t),this.logger.info(`listResources:success count=${t.length}`),t})}async invalidateCache(e){return this.errorHandler.run("invalidateCache",async()=>this.cacheHelper.invalidate(e))}async clearCache(){return this.errorHandler.run("clearCache",async()=>this.cacheHelper.invalidate())}async resolveFromCachedResources(e,r){return (await this.listResources()).find(i=>i.key!==e?false:r===void 0?true:i.language===r)?.value??""}async send(e){try{let r=await this.dependencies.authProvider?.getToken(),t=r?{...e.headers,"X-Auth-Token":r}:e.headers;this.logger.debug(`request:start method=${e.method} path=${e.path} hasAuth=${!!r}`);let n=await this.dependencies.httpTransport.request({...e,headers:t});return this.logger.debug(`request:success method=${e.method} path=${e.path} status=${n.status}`),n.data}catch(r){throw this.errorHandler.preserveCause(r)}}resolveLocaleHeader(e){let r=e?.locale?.trim()||this.dependencies.locale?.trim()||this.resolveBrowserLocale();if(r)return {"Accept-Language":r}}resolveBrowserLocale(){return globalThis.navigator?.language?.trim()||void 0}};function O(o){_(o);let r=o.cache?.enabled??false?o.cache?.provider??new d:void 0,t=new L({baseUrl:o.baseUrl,fetchFn:o.fetchFn,timeoutMs:o.timeoutMs??1e4,defaultHeaders:o.headers,middleware:o.middleware});return new x({httpTransport:t,authProvider:o.authProvider??(o.token?new u(o.token):void 0),locale:o.locale,cacheProvider:r,cacheTtlMs:o.cache?.ttlMs,enableLogs:o.enableLogs??false})}function _(o){if(!o?.baseUrl?.trim())throw new a("baseUrl is required to create localization client")}var w=class{constructor(e){this.storage=e;}async get(e){let r=this.storage.getItem(e);if(!r)return null;let t=this.safeParse(r);return t?t.expiresAt!==void 0&&Date.now()>t.expiresAt?(this.storage.removeItem(e),null):t.value:(this.storage.removeItem(e),null)}async set(e,r,t){let n=t&&t>0?Date.now()+t:void 0,i={value:r,expiresAt:n};this.storage.setItem(e,JSON.stringify(i));}async delete(e){this.storage.removeItem(e);}async clear(){this.storage.clear();}safeParse(e){try{return JSON.parse(e)}catch{return null}}},A=class extends w{constructor(){super(N("localStorage"));}},H=class extends w{constructor(){super(N("sessionStorage"));}};function N(o){let e=typeof window>"u"?void 0:window[o];return e||U()}function U(){let o=new Map;return {get length(){return o.size},clear:()=>{o.clear();},getItem:e=>o.get(e)??null,key:e=>Array.from(o.keys())[e]??null,removeItem:e=>{o.delete(e);},setItem:(e,r)=>{o.set(e,r);}}}var I=new InjectionToken("LOCALIZATION_SDK_OPTIONS"),C=new InjectionToken("LOCALIZATION_CLIENT");function Z(o){return makeEnvironmentProviders([{provide:I,useValue:o},{provide:C,useFactory:()=>O(o)}])}var E=class{constructor(e){this.client=e;}getCurrentLanguage(e){return from(this.client.getCurrentLanguage(e))}getByLanguage(e,r){return from(this.client.getByLanguage(e,r))}listResources(){return from(this.client.listResources())}invalidateCache(e){return from(this.client.invalidateCache(e))}clearCache(){return from(this.client.clearCache())}};E=k([Injectable({providedIn:"root"}),$(0,Inject(C))],E);export{L as FetchHttpTransport,C as LOCALIZATION_CLIENT,I as LOCALIZATION_SDK_OPTIONS,A as LocalStorageCacheProvider,h as LocalizationAuthError,a as LocalizationError,m as LocalizationNetworkError,g as LocalizationServerError,E as LocalizationService,f as LocalizationTimeoutError,d as MemoryCacheProvider,p as ResourceNotFoundError,H as SessionStorageCacheProvider,w as StorageCacheProvider,u as XAuthTokenProvider,O as createLocalizationClient,Z as provideLocalization};
package/package.json CHANGED
@@ -20,7 +20,7 @@
20
20
  "publishConfig": {
21
21
  "access": "public"
22
22
  },
23
- "version": "1.0.3507",
23
+ "version": "1.0.3529",
24
24
  "devDependencies": {
25
25
  "@angular/core": "^20.3.15",
26
26
  "@angular/common": "^20.3.15",