@smartcorehub/localization-sdk 1.0.3514 → 1.0.3542

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,6 +49,42 @@ const list = await localizationClient.listResources();
47
49
  // list retorna objetos { key, value, language }
48
50
  ~~~
49
51
 
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:
57
+
58
+ - `options.locale` por chamada
59
+ - `locale` configurado no `createLocalizationClient`
60
+ - `navigator.language` no browser (fallback automatico)
61
+
62
+ Exemplo com locale global:
63
+
64
+ ~~~ts
65
+ const client = createLocalizationClient({
66
+ baseUrl: 'https://api.smartcorehub.io/',
67
+ token: 'SEU_TOKEN',
68
+ locale: 'pt-BR'
69
+ });
70
+ ~~~
71
+
72
+ Exemplo com override por chamada:
73
+
74
+ ~~~ts
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
+
50
88
  ## Integracao com Angular
51
89
 
52
90
  Forma validada e recomendada (mesma usada no exampleangular): facade com `createLocalizationClient`.
@@ -81,9 +119,7 @@ export class LocalizationFacadeService {
81
119
 
82
120
  ### Forma com provider Angular (opcional)
83
121
 
84
- Modo alternativo em teste alpha: integracao via provider Angular (`provideLocalization` + `LocalizationService`).
85
-
86
- Status atual: nao validado como fluxo principal no exemplo oficial. Use apenas para experimentacao, sem garantia de estabilidade nesta versao.
122
+ Modo alternativo: integracao via provider Angular (`provideLocalization` + `LocalizationService`).
87
123
 
88
124
  ## Integracao com React
89
125
 
@@ -114,6 +150,8 @@ export function HomeTitle() {
114
150
  - `getCurrentLanguage(resourceKey)`: retorna `string`
115
151
  - `getByLanguage(resourceKey, languageKey)`: retorna `string`
116
152
  - `listResources()`: retorna `LocalizationListResource[]`
153
+ - `invalidateCache(key?)`: invalida uma chave especifica ou todas as entradas
154
+ - `clearCache()`: limpa todo o cache configurado
117
155
 
118
156
  ~~~ts
119
157
  export interface LocalizationListResource {
@@ -171,10 +209,15 @@ try {
171
209
 
172
210
  ## Cache configuravel
173
211
 
174
- Por padrao, voce pode usar o MemoryCacheProvider.
212
+ Quando cache.enabled = true, o provider padrao e MemoryCacheProvider.
175
213
 
176
214
  ~~~ts
177
- import { createLocalizationClient, MemoryCacheProvider } from '@smartcorehub/localization-sdk';
215
+ import {
216
+ createLocalizationClient,
217
+ MemoryCacheProvider,
218
+ LocalStorageCacheProvider,
219
+ SessionStorageCacheProvider
220
+ } from '@smartcorehub/localization-sdk';
178
221
 
179
222
  const client = createLocalizationClient({
180
223
  baseUrl: 'https://api.smartcorehub.io/',
@@ -185,6 +228,24 @@ const client = createLocalizationClient({
185
228
  ttlMs: 120000
186
229
  }
187
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
+ });
188
249
  ~~~
189
250
 
190
251
  ## Logging opcional
@@ -203,33 +264,13 @@ const client = createLocalizationClient({
203
264
 
204
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.
205
266
 
206
- ## Comandos para desenvolvimento do pacote
207
-
208
- No diretorio do pacote:
209
-
210
- ~~~bash
211
- npm install
212
- npm run build
213
- npm test
214
- ~~~
215
-
216
- Para detalhes de publicacao em pipeline classico, consulte NPM-PUBLISH-GUIDE.md.
267
+ ## Documentacao tecnica para desenvolvimento
217
268
 
218
- Para automacao completa de CI/CD da publicacao, consulte NPM-AUTOMATION-GUIDE.md.
219
-
220
- Para fluxo interno de contribuicao e troubleshooting de desenvolvimento, consulte o arquivo DEVELOPMENT-GUIDE.md.
221
-
222
- ## Validar pacote publicado (npm)
223
-
224
- No projeto `frontendsdk/exampleangular`, rode:
225
-
226
- ~~~bash
227
- npm run sdk:use:npm
228
- npm run sdk:status
229
- npm run build
230
- ~~~
269
+ Para build, testes automatizados, smoke test e validacao de pacote publicado, consulte:
231
270
 
232
- Atualmente, o comando `sdk:use:npm` fixa o pacote publicado em `@smartcorehub/localization-sdk@1.0.3507`.
271
+ - SDK-DEVELOPMENT-TESTING.md
272
+ - MANUAL-TESTING.md
273
+ - DEVELOPMENT-GUIDE.md
233
274
 
234
275
  ## Licensing / Licenciamento
235
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,r,t)=>{for(var n=e,a=o.length-1,s;a>=0;a--)(s=o[a])&&(n=(s(n))||n);return n},I=(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 i=class extends Error{constructor(r,t){super(r);this.metadata=t;this.name="LocalizationError";}},p=class extends i{constructor(e="Authentication failed",r){super(e,r),this.name="LocalizationAuthError";}},h=class extends i{constructor(e="Resource not found",r){super(e,r),this.name="ResourceNotFoundError";}},g=class extends i{constructor(e="Localization server error",r){super(e,r),this.name="LocalizationServerError";}},m=class extends i{constructor(e="Network error while contacting localization API",r){super(e,r),this.name="LocalizationNetworkError";}},f=class extends i{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";}},L=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";}},y=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),a=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(a),O;try{O=await this.fetchFn(s);}catch(E){throw this.isAbortError(E)?new L(r):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 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 k="localization:listResources",T=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(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,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 i(`${r} is required`)}map(e){return e instanceof i?e:e instanceof L?new f(e.message):e instanceof l?new m(e.message):e instanceof v?this.mapStatusError(e):new i("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 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 i(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,r,t){this.write("error",`${e}:error ${this.formatErrorDetails(r,t)}`);}formatErrorDetails(e,r){let t=this.getOriginalCause(e,r),n=this.getErrorMessage(t),a=[`type=${e.name}`,`message=${e.message}`];return n&&n!==e.message&&a.push(`original=${n}`),e instanceof i&&e.metadata?.statusCode&&a.push(`statusCode=${e.metadata.statusCode}`),a.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=`${q} ${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 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 r=await this.send({method:"GET",path:"api/localization/client/currentlanguage",query:{resourceKey:e}});return this.logger.info(`getCurrentLanguage:success resourceKey=${e} hasValue=${!!r}`),r??""})}async getByLanguage(e,r){return this.errorHandler.run("getByLanguage",async()=>{this.errorHandler.validateRequired(e,"resourceKey"),this.errorHandler.validateRequired(r,"languageKey"),this.logger.debug(`getByLanguage:start resourceKey=${e} languageKey=${r}`);let t=await this.send({method:"GET",path:"api/localization/client/bylanguage",query:{resourceKey:e,languageKey:r}});return this.logger.info(`getByLanguage:success resourceKey=${e} languageKey=${r} hasValue=${!!t}`),t??""})}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 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)}}};function x(o){D(o);let r=o.cache?.enabled??false?o.cache?.provider??new d:void 0,t=new y({baseUrl:o.baseUrl,fetchFn:o.fetchFn,timeoutMs:o.timeoutMs??1e4,defaultHeaders:o.headers,middleware:o.middleware});return new z({httpTransport:t,authProvider:o.authProvider??(o.token?new u(o.token):void 0),cacheProvider:r,cacheTtlMs:o.cache?.ttlMs,enableLogs:o.enableLogs??false})}function D(o){if(!o?.baseUrl?.trim())throw new i("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,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))}};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=i;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 S=(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},k=(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),$;try{$=await this.fetchFn(s);}catch(T){throw this.isAbortError(T)?new y(r):new l(T)}let c=await this.runResponseMiddleware($);if(!c.ok){let T=await c.text();throw new v(c.status,T,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")||r.includes(":api-call")?"\u{1F310}":r.includes("request:success")?"\u{1F4E1}":r.includes(":cache-hit")||r.includes("cache-hit")?"\u{1F5C4}\uFE0F":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.isCacheEnabled()){let n=await this.resolveFromCachedResources(e);return this.logger.info(`getCurrentLanguage:cache-${n?"hit":"miss"} resourceKey=${e} hasValue=${!!n}`),n}this.logger.info(`getCurrentLanguage:api-call path=api/localization/client/currentlanguage resourceKey=${e}`);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.isCacheEnabled()){let i=await this.resolveFromCachedResources(e,r);return this.logger.info(`getByLanguage:cache-${i?"hit":"miss"} resourceKey=${e} languageKey=${r} hasValue=${!!i}`),i}this.logger.info(`getByLanguage:api-call path=api/localization/client/bylanguage resourceKey=${e} languageKey=${r}`);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()=>{if(this.isCacheEnabled()){let t=await this.cacheHelper.getListResources();if(t)return this.logger.info(`listResources:cache-hit-return count=${t.length}`),t}else this.logger.debug("listResources:cache-disabled");this.logger.info("listResources:api-call path=api/localization/client/list");let r=(await this.send({method:"GET",path:"api/localization/client/list"})??[]).map(t=>({key:t.resourceKey,value:t.value,language:t.languageKey}));return this.isCacheEnabled()&&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 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}isCacheEnabled(){if(this.dependencies.cacheEnabled!==void 0)return this.logger.debug(`isCacheEnabled: enabled=${this.dependencies.cacheEnabled} source=cacheEnabled-flag`),this.dependencies.cacheEnabled;let e=!!this.dependencies.cacheProvider;return this.logger.debug(`isCacheEnabled: enabled=${e} source=cacheProvider`),e}};function O(o){_(o);let e=o.cache?.enabled??false,r=e?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,cacheEnabled:e,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=S([core.Injectable({providedIn:"root"}),k(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,r,t)=>{for(var n=e,a=o.length-1,s;a>=0;a--)(s=o[a])&&(n=(s(n))||n);return n},I=(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 i=class extends Error{constructor(r,t){super(r);this.metadata=t;this.name="LocalizationError";}},p=class extends i{constructor(e="Authentication failed",r){super(e,r),this.name="LocalizationAuthError";}},h=class extends i{constructor(e="Resource not found",r){super(e,r),this.name="ResourceNotFoundError";}},g=class extends i{constructor(e="Localization server error",r){super(e,r),this.name="LocalizationServerError";}},m=class extends i{constructor(e="Network error while contacting localization API",r){super(e,r),this.name="LocalizationNetworkError";}},f=class extends i{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";}},L=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";}},y=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),a=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(a),O;try{O=await this.fetchFn(s);}catch(E){throw this.isAbortError(E)?new L(r):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 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 k="localization:listResources",T=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(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,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 i(`${r} is required`)}map(e){return e instanceof i?e:e instanceof L?new f(e.message):e instanceof l?new m(e.message):e instanceof v?this.mapStatusError(e):new i("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 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 i(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,r,t){this.write("error",`${e}:error ${this.formatErrorDetails(r,t)}`);}formatErrorDetails(e,r){let t=this.getOriginalCause(e,r),n=this.getErrorMessage(t),a=[`type=${e.name}`,`message=${e.message}`];return n&&n!==e.message&&a.push(`original=${n}`),e instanceof i&&e.metadata?.statusCode&&a.push(`statusCode=${e.metadata.statusCode}`),a.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=`${q} ${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 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 r=await this.send({method:"GET",path:"api/localization/client/currentlanguage",query:{resourceKey:e}});return this.logger.info(`getCurrentLanguage:success resourceKey=${e} hasValue=${!!r}`),r??""})}async getByLanguage(e,r){return this.errorHandler.run("getByLanguage",async()=>{this.errorHandler.validateRequired(e,"resourceKey"),this.errorHandler.validateRequired(r,"languageKey"),this.logger.debug(`getByLanguage:start resourceKey=${e} languageKey=${r}`);let t=await this.send({method:"GET",path:"api/localization/client/bylanguage",query:{resourceKey:e,languageKey:r}});return this.logger.info(`getByLanguage:success resourceKey=${e} languageKey=${r} hasValue=${!!t}`),t??""})}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 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)}}};function x(o){D(o);let r=o.cache?.enabled??false?o.cache?.provider??new d:void 0,t=new y({baseUrl:o.baseUrl,fetchFn:o.fetchFn,timeoutMs:o.timeoutMs??1e4,defaultHeaders:o.headers,middleware:o.middleware});return new z({httpTransport:t,authProvider:o.authProvider??(o.token?new u(o.token):void 0),cacheProvider:r,cacheTtlMs:o.cache?.ttlMs,enableLogs:o.enableLogs??false})}function D(o){if(!o?.baseUrl?.trim())throw new i("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,r){return from(this.client.getByLanguage(e,r))}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,i 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 S=(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},k=(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),$;try{$=await this.fetchFn(s);}catch(T){throw this.isAbortError(T)?new y(r):new l(T)}let c=await this.runResponseMiddleware($);if(!c.ok){let T=await c.text();throw new v(c.status,T,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")||r.includes(":api-call")?"\u{1F310}":r.includes("request:success")?"\u{1F4E1}":r.includes(":cache-hit")||r.includes("cache-hit")?"\u{1F5C4}\uFE0F":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.isCacheEnabled()){let n=await this.resolveFromCachedResources(e);return this.logger.info(`getCurrentLanguage:cache-${n?"hit":"miss"} resourceKey=${e} hasValue=${!!n}`),n}this.logger.info(`getCurrentLanguage:api-call path=api/localization/client/currentlanguage resourceKey=${e}`);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.isCacheEnabled()){let i=await this.resolveFromCachedResources(e,r);return this.logger.info(`getByLanguage:cache-${i?"hit":"miss"} resourceKey=${e} languageKey=${r} hasValue=${!!i}`),i}this.logger.info(`getByLanguage:api-call path=api/localization/client/bylanguage resourceKey=${e} languageKey=${r}`);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()=>{if(this.isCacheEnabled()){let t=await this.cacheHelper.getListResources();if(t)return this.logger.info(`listResources:cache-hit-return count=${t.length}`),t}else this.logger.debug("listResources:cache-disabled");this.logger.info("listResources:api-call path=api/localization/client/list");let r=(await this.send({method:"GET",path:"api/localization/client/list"})??[]).map(t=>({key:t.resourceKey,value:t.value,language:t.languageKey}));return this.isCacheEnabled()&&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 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}isCacheEnabled(){if(this.dependencies.cacheEnabled!==void 0)return this.logger.debug(`isCacheEnabled: enabled=${this.dependencies.cacheEnabled} source=cacheEnabled-flag`),this.dependencies.cacheEnabled;let e=!!this.dependencies.cacheProvider;return this.logger.debug(`isCacheEnabled: enabled=${e} source=cacheProvider`),e}};function O(o){_(o);let e=o.cache?.enabled??false,r=e?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,cacheEnabled:e,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=S([Injectable({providedIn:"root"}),k(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.3514",
23
+ "version": "1.0.3542",
24
24
  "devDependencies": {
25
25
  "@angular/core": "^20.3.15",
26
26
  "@angular/common": "^20.3.15",