UNPKG

@nova-fe/i18next-cache-backend

Version:

强大的 i18next 后端插件,具有 IndexedDB 缓存、批量加载和智能缓存策略

2 lines (1 loc) 8.44 kB
"use strict";var m=Object.defineProperty;var y=(n,e,t)=>e in n?m(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var i=(n,e,t)=>y(n,typeof e!="symbol"?e+"":e,t);Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const C=require("idb"),p=require("ky");class u{constructor(e=100){i(this,"cache",new Map);i(this,"maxSize");this.maxSize=e}async get(e){const t=this.cache.get(e);return t?Date.now()>t.timestamp+t.ttl?(this.cache.delete(e),null):t:null}async set(e,t){if(this.cache.size>=this.maxSize&&!this.cache.has(e)){const s=this.cache.keys().next().value;s&&this.cache.delete(s)}this.cache.set(e,t)}async delete(e){this.cache.delete(e)}async clear(){this.cache.clear()}async keys(){return Array.from(this.cache.keys())}async size(){return this.cache.size}async cleanup(){const e=Date.now();for(const[t,s]of this.cache.entries())e>s.timestamp+s.ttl&&this.cache.delete(t)}}class g{constructor(e="i18next-cache",t=1){i(this,"db",null);i(this,"dbName");i(this,"version");this.dbName=e,this.version=t}async getDB(){return this.db||(this.db=await C.openDB(this.dbName,this.version,{upgrade(e){e.objectStoreNames.contains("translations")||e.createObjectStore("translations")}})),this.db}async get(e){try{const s=await(await this.getDB()).get("translations",e);return s?Date.now()>s.timestamp+s.ttl?(await this.delete(e),null):s:null}catch(t){return console.error("IDBCache get error:",t),null}}async set(e,t){try{await(await this.getDB()).put("translations",t,e)}catch(s){console.error("IDBCache set error:",s)}}async delete(e){try{await(await this.getDB()).delete("translations",e)}catch(t){console.error("IDBCache delete error:",t)}}async clear(){try{await(await this.getDB()).clear("translations")}catch(e){console.error("IDBCache clear error:",e)}}async keys(){try{return await(await this.getDB()).getAllKeys("translations")}catch(e){return console.error("IDBCache keys error:",e),[]}}async size(){try{return await(await this.getDB()).count("translations")}catch(e){return console.error("IDBCache size error:",e),0}}async cleanup(){try{const t=(await this.getDB()).transaction("translations","readwrite"),a=await t.objectStore("translations").openCursor(),c=Date.now();for(;a;){const r=a.value;c>r.timestamp+r.ttl&&await a.delete(),await a.continue()}await t.done}catch(e){console.error("IDBCache cleanup error:",e)}}}class d{constructor(e=50,t="i18next-cache",s=1){i(this,"memoryCache");i(this,"persistentCache");this.memoryCache=new u(e),this.persistentCache=new g(t,s)}async get(e){let t=await this.memoryCache.get(e);return t||(t=await this.persistentCache.get(e),t?(await this.memoryCache.set(e,t),t):null)}async set(e,t){await Promise.all([this.memoryCache.set(e,t),this.persistentCache.set(e,t)])}async delete(e){await Promise.all([this.memoryCache.delete(e),this.persistentCache.delete(e)])}async clear(){await Promise.all([this.memoryCache.clear(),this.persistentCache.clear()])}async keys(){return await this.persistentCache.keys()}async size(){return await this.persistentCache.size()}async cleanup(){await Promise.all([this.memoryCache.cleanup(),this.persistentCache.cleanup()])}async getStats(){const[e,t]=await Promise.all([this.memoryCache.size(),this.persistentCache.size()]);return{memory:{size:e,maxSize:this.memoryCache.maxSize},persistent:{size:t}}}}class l{constructor(e){i(this,"client");i(this,"config");i(this,"requestQueue",new Map);this.config=e,this.client=p.create({prefixUrl:`http://${e.server.host}:${e.server.port}${e.server.apiPrefix}`,timeout:e.performance.timeout,retry:{limit:e.performance.retryAttempts,methods:["get"],statusCodes:[408,413,429,500,502,503,504]}})}getCacheKey(e){return"language"in e?`${e.language}:${e.namespace}`:`bulk:${e.languages.join(",")}:${e.namespaces.join(",")}`}async makeRequest(e,t){if(this.requestQueue.has(t))return this.requestQueue.get(t);const s=this.client.get(e).json();this.requestQueue.set(t,s);try{return await s}finally{this.requestQueue.delete(t)}}async fetchNamespace(e){const{language:t,namespace:s}=e,a=`${t}/${s}`,c=this.getCacheKey(e);return this.config.debug&&console.log(`Fetching namespace: ${t}/${s}`),this.makeRequest(a,c)}async fetchBulk(e){const t="all",s=this.getCacheKey(e);return this.config.debug&&console.log("Fetching bulk data"),this.makeRequest(t,s)}async fetchIncremental(e,t,s){const a=`${e}/${t}/incremental`,c=s?{version:s}:void 0;this.config.debug&&console.log(`Fetching incremental: ${e}/${t} from version ${s}`);const r=c?{searchParams:c}:{};return this.client.get(a,r).json()}async ping(){try{return await this.client.get("health").json(),!0}catch{return!1}}async getServerInfo(){return this.client.get("info").json()}clearRequestQueue(){this.requestQueue.clear()}}class f{constructor(e,t){i(this,"type","backend");i(this,"config");i(this,"cache");i(this,"httpClient");i(this,"_services");i(this,"_isInitialized",!1);i(this,"bulkDataCache",new Map);this._services=e,this.config=this.mergeConfig(t),this.cache=this.createCacheProvider(),this.httpClient=new l(this.config)}mergeConfig(e){return{...{server:{port:3001,host:"localhost",apiPrefix:"/api"},cache:{storage:"indexeddb",maxSize:52428800,ttl:864e5,namespace:"i18next-cache",version:"1.0.0"},fetchStrategy:"namespace",performance:{batchSize:10,retryAttempts:3,timeout:1e4,concurrency:5},debug:!1,offline:!1},...e}}createCacheProvider(){const{storage:e,namespace:t}=this.config.cache;switch(e){case"localstorage":return new u(100);case"indexeddb":default:return new d(50,t)}}init(e,t,s){this._services=e,this.config=this.mergeConfig(t),this.cache=this.createCacheProvider(),this.httpClient=new l(this.config),this._isInitialized=!0,this.config.debug&&console.log("CacheBackend initialized with config:",this.config),this.startCleanupInterval()}startCleanupInterval(){setInterval(()=>{this.cache.cleanup&&this.cache.cleanup()},60*60*1e3)}generateCacheKey(e,t){return`${this.config.cache.namespace}:${e}:${t}`}async read(e,t,s){try{const a=this.generateCacheKey(e,t),c=await this.cache.get(a);if(c&&this.isCacheValid(c)){this.config.debug&&console.log(`Cache hit for ${e}/${t}`),s(null,c.data);return}this.config.debug&&console.log(`Cache miss for ${e}/${t}, fetching from server`);const r=await this.fetchTranslations(e,t),o={data:r,timestamp:Date.now(),version:this.config.cache.version,ttl:this.config.cache.ttl};await this.cache.set(a,o),s(null,r)}catch(a){if(this.config.debug&&console.error(`Error reading ${e}/${t}:`,a),this.config.offline){const c=this.generateCacheKey(e,t),r=await this.cache.get(c);if(r){s(null,r.data);return}}s(a,null)}}isCacheValid(e){const s=Date.now()>e.timestamp+e.ttl,a=!this.config.cache.version||e.version===this.config.cache.version;return!s&&a}async fetchTranslations(e,t){const s={language:e,namespace:t};return this.config.fetchStrategy==="bulk"?await this.fetchFromBulk(e,t):await this.httpClient.fetchNamespace(s)}async fetchFromBulk(e,t){const s="bulk:all";if(this.bulkDataCache.has(s))this.config.debug&&console.log("Using cached bulk request");else{this.config.debug&&console.log("Creating bulk request for all languages and namespaces");const c=this.httpClient.fetchBulk({languages:["en","zh","es","fr"],namespaces:["common","navigation","forms"]}).then(async r=>(await this.cacheAllBulkData(r),r));this.bulkDataCache.set(s,c)}return(await this.bulkDataCache.get(s))[e]?.[t]||{}}async cacheAllBulkData(e){this.config.debug&&console.log("Caching all bulk data to IndexedDB...");const t=[];for(const[s,a]of Object.entries(e))for(const[c,r]of Object.entries(a)){const o=this.generateCacheKey(s,c),h={data:r,timestamp:Date.now(),version:this.config.cache.version,ttl:this.config.cache.ttl};t.push(this.cache.set(o,h))}await Promise.all(t),this.config.debug&&console.log(`Cached ${t.length} language/namespace combinations to IndexedDB`)}async preload(e,t){const s=[];for(const a of e)for(const c of t)s.push(new Promise((r,o)=>{this.read(a,c,h=>{h?o(h):r()})}));await Promise.allSettled(s)}async clearCache(){await this.cache.clear()}async getCacheStats(){const e=await this.cache.size(),t=await this.cache.keys();return{size:e,entries:t.length,keys:t}}async warmup(e,t){this.config.debug&&console.log("Warming up cache for:",{languages:e,namespaces:t}),await this.preload(e,t)}async isServerHealthy(){return await this.httpClient.ping()}}i(f,"type","backend");exports.CacheBackend=f;exports.DualCache=d;exports.HttpClient=l;exports.IDBCache=g;exports.MemoryCache=u;exports.default=f;