@lionrapid/core 0.6.0 → 0.6.2
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/dist/browser/auto-init.d.ts +17 -3
- package/dist/browser/auto-init.d.ts.map +1 -1
- package/dist/browser/auto-init.js +57 -30
- package/dist/browser/auto-init.js.map +1 -1
- package/dist/browser/lionrapid.auto.dev.js +285 -27
- package/dist/browser/lionrapid.auto.dev.js.map +2 -2
- package/dist/browser/lionrapid.auto.js +285 -27
- package/dist/browser/lionrapid.auto.js.map +2 -2
- package/dist/browser/lionrapid.auto.min.js +4 -4
- package/dist/browser/lionrapid.auto.min.js.map +3 -3
- package/dist/browser/lionrapid.esm.js +244 -2
- package/dist/browser/lionrapid.esm.js.map +2 -2
- package/dist/browser/lionrapid.esm.min.js +3 -3
- package/dist/browser/lionrapid.esm.min.js.map +3 -3
- package/dist/browser/lionrapid.iife.dev.js +244 -2
- package/dist/browser/lionrapid.iife.dev.js.map +2 -2
- package/dist/browser/lionrapid.iife.js +244 -2
- package/dist/browser/lionrapid.iife.js.map +2 -2
- package/dist/browser/lionrapid.iife.min.js +3 -3
- package/dist/browser/lionrapid.iife.min.js.map +3 -3
- package/dist/browser/lionrapid.umd.js +244 -2
- package/dist/browser/lionrapid.umd.js.map +2 -2
- package/dist/browser/lionrapid.umd.min.js +3 -3
- package/dist/browser/lionrapid.umd.min.js.map +3 -3
- package/dist/browser/storage/cache-storage-repository.d.ts +65 -0
- package/dist/browser/storage/cache-storage-repository.d.ts.map +1 -1
- package/dist/browser/storage/cache-storage-repository.js +77 -7
- package/dist/browser/storage/cache-storage-repository.js.map +1 -1
- package/dist/browser/storage/localstorage-repository.d.ts +62 -0
- package/dist/browser/storage/localstorage-repository.d.ts.map +1 -1
- package/dist/browser/storage/localstorage-repository.js +71 -4
- package/dist/browser/storage/localstorage-repository.js.map +1 -1
- package/dist/config/repository.config.d.ts +17 -0
- package/dist/config/repository.config.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lionRapid.d.ts +1 -0
- package/dist/lionRapid.d.ts.map +1 -1
- package/dist/lionRapid.js +14 -0
- package/dist/lionRapid.js.map +1 -1
- package/dist/repositories/network-repository.d.ts +42 -1
- package/dist/repositories/network-repository.d.ts.map +1 -1
- package/dist/repositories/network-repository.js +149 -1
- package/dist/repositories/network-repository.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CacheStorageRepository - Cache API-based persistent storage (Browser Bundle Version)
|
|
3
3
|
* Implements IAsyncRepository for asynchronous access to Cache API
|
|
4
|
+
*
|
|
5
|
+
* This provides Layer 3 caching in the handler chain:
|
|
6
|
+
* 1. Memory (sync) → 2. LocalStorage (sync) → 3. CacheStorage (async) → 4. Network (async)
|
|
4
7
|
*/
|
|
5
8
|
import type { IAsyncRepository } from '../../interfaces/repository.interface.js';
|
|
6
9
|
import type { StorageRepositoryConfig } from '../../config/repository.config.js';
|
|
@@ -10,12 +13,34 @@ import type { TranslationData } from '../../types/translation-data.types.js';
|
|
|
10
13
|
*/
|
|
11
14
|
export interface CacheStorageRepositoryConfig extends StorageRepositoryConfig {
|
|
12
15
|
options?: StorageRepositoryConfig['options'] & {
|
|
16
|
+
/** Cache name for the Cache API (default: 'lionrapid-v1') */
|
|
13
17
|
cacheName?: string;
|
|
18
|
+
/** TTL in milliseconds (default: 7 days) */
|
|
14
19
|
ttl?: number;
|
|
15
20
|
};
|
|
16
21
|
}
|
|
17
22
|
/**
|
|
18
23
|
* Cache API-based repository for persistent translation caching
|
|
24
|
+
*
|
|
25
|
+
* Features:
|
|
26
|
+
* - Asynchronous access (Cache API is async)
|
|
27
|
+
* - TTL-based expiration
|
|
28
|
+
* - Larger storage capacity than localStorage
|
|
29
|
+
* - Graceful fallback when Cache API unavailable (SSR, old browsers)
|
|
30
|
+
* - Works alongside Service Worker for offline support
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* import { CacheStorageRepository } from '@lionrapid/storage';
|
|
35
|
+
*
|
|
36
|
+
* const cacheRepo = new CacheStorageRepository({
|
|
37
|
+
* enabled: true,
|
|
38
|
+
* options: {
|
|
39
|
+
* cacheName: 'my-app-translations',
|
|
40
|
+
* ttl: 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
41
|
+
* },
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
19
44
|
*/
|
|
20
45
|
export declare class CacheStorageRepository implements IAsyncRepository<CacheStorageRepositoryConfig> {
|
|
21
46
|
readonly name = "CacheStorageRepository";
|
|
@@ -25,17 +50,57 @@ export declare class CacheStorageRepository implements IAsyncRepository<CacheSto
|
|
|
25
50
|
private defaultTTL;
|
|
26
51
|
private available;
|
|
27
52
|
constructor(config?: CacheStorageRepositoryConfig);
|
|
53
|
+
/**
|
|
54
|
+
* Check if Cache API is available
|
|
55
|
+
* Returns false in SSR, old browsers, or insecure contexts
|
|
56
|
+
*/
|
|
28
57
|
private checkAvailability;
|
|
58
|
+
/**
|
|
59
|
+
* Get a translation value by key
|
|
60
|
+
* @throws {TranslationNotFoundError} When translation doesn't exist or is expired
|
|
61
|
+
*/
|
|
29
62
|
get(key: string): Promise<string>;
|
|
63
|
+
/**
|
|
64
|
+
* Set a translation value
|
|
65
|
+
*/
|
|
30
66
|
set(key: string, value: string): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Remove a translation by key
|
|
69
|
+
*/
|
|
31
70
|
remove(key: string): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Clear all LionRapid entries from the cache
|
|
73
|
+
*/
|
|
32
74
|
clear(): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Clear all entries for a specific namespace
|
|
77
|
+
*/
|
|
33
78
|
clearNamespace(locale: string, namespace: string): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* Get namespace translations
|
|
81
|
+
* @throws {TranslationNotFoundError} When namespace doesn't exist
|
|
82
|
+
*/
|
|
34
83
|
getNamespace(locale: string, namespace: string): Promise<TranslationData>;
|
|
84
|
+
/**
|
|
85
|
+
* Set namespace translations
|
|
86
|
+
*/
|
|
35
87
|
setNamespace(locale: string, namespace: string, translations: TranslationData): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Get repository configuration
|
|
90
|
+
*/
|
|
36
91
|
getConfig(): CacheStorageRepositoryConfig;
|
|
92
|
+
/**
|
|
93
|
+
* Check if Cache API is available
|
|
94
|
+
*/
|
|
37
95
|
isAvailable(): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Get all cached entries (for debugging)
|
|
98
|
+
* Returns an object mapping keys to values
|
|
99
|
+
*/
|
|
38
100
|
getEntries(): Promise<Record<string, string>>;
|
|
101
|
+
/**
|
|
102
|
+
* Get cache statistics
|
|
103
|
+
*/
|
|
39
104
|
getCacheStats(): Promise<{
|
|
40
105
|
size: number;
|
|
41
106
|
cacheName: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-storage-repository.d.ts","sourceRoot":"","sources":["../../../src/browser/storage/cache-storage-repository.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cache-storage-repository.d.ts","sourceRoot":"","sources":["../../../src/browser/storage/cache-storage-repository.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAoB7E;;GAEG;AACH,MAAM,WAAW,4BAA6B,SAAQ,uBAAuB;IAC3E,OAAO,CAAC,EAAE,uBAAuB,CAAC,SAAS,CAAC,GAAG;QAC7C,6DAA6D;QAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,4CAA4C;QAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,sBACX,YAAW,gBAAgB,CAAC,4BAA4B,CAAC;IAEzD,QAAQ,CAAC,IAAI,4BAA4B;IACzC,QAAQ,CAAC,IAAI,YAAsB;IACnC,QAAQ,CAAC,MAAM,EAAE,4BAA4B,CAAC;IAE9C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAU;gBAEf,MAAM,GAAE,4BAAiC;IAgBrD;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;;OAGG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA+BvC;;OAEG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBpD;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWxC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB5B;;OAEG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BtE;;;OAGG;IACG,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC;IAY3B;;OAEG;IACG,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,eAAe,GAC5B,OAAO,CAAC,IAAI,CAAC;IAKhB;;OAEG;IACH,SAAS,IAAI,4BAA4B;IAIzC;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IA6BnD;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CAepE"}
|
|
@@ -1,12 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CacheStorageRepository - Cache API-based persistent storage (Browser Bundle Version)
|
|
3
3
|
* Implements IAsyncRepository for asynchronous access to Cache API
|
|
4
|
+
*
|
|
5
|
+
* This provides Layer 3 caching in the handler chain:
|
|
6
|
+
* 1. Memory (sync) → 2. LocalStorage (sync) → 3. CacheStorage (async) → 4. Network (async)
|
|
4
7
|
*/
|
|
5
8
|
import { TranslationNotFoundError } from '../../errors/translation-errors.js';
|
|
6
9
|
const CACHE_NAME = 'lionrapid-v1';
|
|
7
|
-
|
|
10
|
+
// The Cache API only accepts http/https request URLs as keys — `cache.put`
|
|
11
|
+
// throws `TypeError: Request scheme '…' is unsupported` for a bare string like
|
|
12
|
+
// `lionrapid:…`, which silently killed every write (caught by set()'s guard).
|
|
13
|
+
// Use a synthetic https origin so keys are valid Requests. The host never
|
|
14
|
+
// resolves; it only namespaces entries inside the named cache.
|
|
15
|
+
const CACHE_PREFIX = 'https://cache.lionrapid.internal/';
|
|
8
16
|
/**
|
|
9
17
|
* Cache API-based repository for persistent translation caching
|
|
18
|
+
*
|
|
19
|
+
* Features:
|
|
20
|
+
* - Asynchronous access (Cache API is async)
|
|
21
|
+
* - TTL-based expiration
|
|
22
|
+
* - Larger storage capacity than localStorage
|
|
23
|
+
* - Graceful fallback when Cache API unavailable (SSR, old browsers)
|
|
24
|
+
* - Works alongside Service Worker for offline support
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { CacheStorageRepository } from '@lionrapid/storage';
|
|
29
|
+
*
|
|
30
|
+
* const cacheRepo = new CacheStorageRepository({
|
|
31
|
+
* enabled: true,
|
|
32
|
+
* options: {
|
|
33
|
+
* cacheName: 'my-app-translations',
|
|
34
|
+
* ttl: 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
35
|
+
* },
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
10
38
|
*/
|
|
11
39
|
export class CacheStorageRepository {
|
|
12
40
|
name = 'CacheStorageRepository';
|
|
@@ -21,7 +49,7 @@ export class CacheStorageRepository {
|
|
|
21
49
|
priority: config.priority ?? 0,
|
|
22
50
|
options: {
|
|
23
51
|
cacheName: config.options?.cacheName ?? CACHE_NAME,
|
|
24
|
-
ttl: config.options?.ttl ?? 7 * 24 * 60 * 60 * 1000,
|
|
52
|
+
ttl: config.options?.ttl ?? 7 * 24 * 60 * 60 * 1000, // 7 days default
|
|
25
53
|
...config.options,
|
|
26
54
|
},
|
|
27
55
|
};
|
|
@@ -29,9 +57,17 @@ export class CacheStorageRepository {
|
|
|
29
57
|
this.defaultTTL = this.config.options?.ttl ?? 7 * 24 * 60 * 60 * 1000;
|
|
30
58
|
this.available = this.checkAvailability();
|
|
31
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Check if Cache API is available
|
|
62
|
+
* Returns false in SSR, old browsers, or insecure contexts
|
|
63
|
+
*/
|
|
32
64
|
checkAvailability() {
|
|
33
65
|
return typeof caches !== 'undefined';
|
|
34
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Get a translation value by key
|
|
69
|
+
* @throws {TranslationNotFoundError} When translation doesn't exist or is expired
|
|
70
|
+
*/
|
|
35
71
|
async get(key) {
|
|
36
72
|
if (!this.available) {
|
|
37
73
|
throw new TranslationNotFoundError(key);
|
|
@@ -43,6 +79,7 @@ export class CacheStorageRepository {
|
|
|
43
79
|
throw new TranslationNotFoundError(key);
|
|
44
80
|
}
|
|
45
81
|
const data = await response.json();
|
|
82
|
+
// Check TTL expiry
|
|
46
83
|
if (data.expiry !== null && Date.now() > data.expiry) {
|
|
47
84
|
await cache.delete(CACHE_PREFIX + key);
|
|
48
85
|
throw new TranslationNotFoundError(key);
|
|
@@ -53,9 +90,13 @@ export class CacheStorageRepository {
|
|
|
53
90
|
if (error instanceof TranslationNotFoundError) {
|
|
54
91
|
throw error;
|
|
55
92
|
}
|
|
93
|
+
// Wrap any other error as not found
|
|
56
94
|
throw new TranslationNotFoundError(key);
|
|
57
95
|
}
|
|
58
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Set a translation value
|
|
99
|
+
*/
|
|
59
100
|
async set(key, value) {
|
|
60
101
|
if (!this.available)
|
|
61
102
|
return;
|
|
@@ -74,6 +115,9 @@ export class CacheStorageRepository {
|
|
|
74
115
|
// Silently fail - cache is non-critical
|
|
75
116
|
}
|
|
76
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Remove a translation by key
|
|
120
|
+
*/
|
|
77
121
|
async remove(key) {
|
|
78
122
|
if (!this.available)
|
|
79
123
|
return;
|
|
@@ -85,6 +129,9 @@ export class CacheStorageRepository {
|
|
|
85
129
|
// Silently fail
|
|
86
130
|
}
|
|
87
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Clear all LionRapid entries from the cache
|
|
134
|
+
*/
|
|
88
135
|
async clear() {
|
|
89
136
|
if (!this.available)
|
|
90
137
|
return;
|
|
@@ -92,13 +139,16 @@ export class CacheStorageRepository {
|
|
|
92
139
|
const cache = await caches.open(this.cacheName);
|
|
93
140
|
const keys = await cache.keys();
|
|
94
141
|
await Promise.all(keys
|
|
95
|
-
.filter(
|
|
96
|
-
.map(
|
|
142
|
+
.filter(request => request.url.includes(CACHE_PREFIX))
|
|
143
|
+
.map(request => cache.delete(request)));
|
|
97
144
|
}
|
|
98
145
|
catch {
|
|
99
146
|
// Silently fail
|
|
100
147
|
}
|
|
101
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Clear all entries for a specific namespace
|
|
151
|
+
*/
|
|
102
152
|
async clearNamespace(locale, namespace) {
|
|
103
153
|
if (!this.available)
|
|
104
154
|
return;
|
|
@@ -108,19 +158,23 @@ export class CacheStorageRepository {
|
|
|
108
158
|
const cache = await caches.open(this.cacheName);
|
|
109
159
|
const keys = await cache.keys();
|
|
110
160
|
await Promise.all(keys
|
|
111
|
-
.filter(
|
|
161
|
+
.filter(request => {
|
|
112
162
|
const url = request.url;
|
|
113
163
|
const key = url.includes(CACHE_PREFIX)
|
|
114
164
|
? url.substring(url.indexOf(CACHE_PREFIX) + CACHE_PREFIX.length)
|
|
115
165
|
: '';
|
|
116
166
|
return key === namespaceCacheKey || key.startsWith(keyPrefix);
|
|
117
167
|
})
|
|
118
|
-
.map(
|
|
168
|
+
.map(request => cache.delete(request)));
|
|
119
169
|
}
|
|
120
170
|
catch {
|
|
121
171
|
// Silently fail
|
|
122
172
|
}
|
|
123
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Get namespace translations
|
|
176
|
+
* @throws {TranslationNotFoundError} When namespace doesn't exist
|
|
177
|
+
*/
|
|
124
178
|
async getNamespace(locale, namespace) {
|
|
125
179
|
const namespaceCacheKey = `@${locale}:${namespace}`;
|
|
126
180
|
const value = await this.get(namespaceCacheKey);
|
|
@@ -132,16 +186,29 @@ export class CacheStorageRepository {
|
|
|
132
186
|
throw new TranslationNotFoundError(namespaceCacheKey);
|
|
133
187
|
}
|
|
134
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Set namespace translations
|
|
191
|
+
*/
|
|
135
192
|
async setNamespace(locale, namespace, translations) {
|
|
136
193
|
const namespaceCacheKey = `@${locale}:${namespace}`;
|
|
137
194
|
await this.set(namespaceCacheKey, JSON.stringify(translations));
|
|
138
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Get repository configuration
|
|
198
|
+
*/
|
|
139
199
|
getConfig() {
|
|
140
200
|
return this.config;
|
|
141
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Check if Cache API is available
|
|
204
|
+
*/
|
|
142
205
|
isAvailable() {
|
|
143
206
|
return this.available;
|
|
144
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Get all cached entries (for debugging)
|
|
210
|
+
* Returns an object mapping keys to values
|
|
211
|
+
*/
|
|
145
212
|
async getEntries() {
|
|
146
213
|
const entries = {};
|
|
147
214
|
if (!this.available)
|
|
@@ -168,6 +235,9 @@ export class CacheStorageRepository {
|
|
|
168
235
|
}
|
|
169
236
|
return entries;
|
|
170
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Get cache statistics
|
|
240
|
+
*/
|
|
171
241
|
async getCacheStats() {
|
|
172
242
|
if (!this.available) {
|
|
173
243
|
return { size: 0, cacheName: this.cacheName };
|
|
@@ -175,7 +245,7 @@ export class CacheStorageRepository {
|
|
|
175
245
|
try {
|
|
176
246
|
const cache = await caches.open(this.cacheName);
|
|
177
247
|
const keys = await cache.keys();
|
|
178
|
-
const size = keys.filter(
|
|
248
|
+
const size = keys.filter(r => r.url.includes(CACHE_PREFIX)).length;
|
|
179
249
|
return { size, cacheName: this.cacheName };
|
|
180
250
|
}
|
|
181
251
|
catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-storage-repository.js","sourceRoot":"","sources":["../../../src/browser/storage/cache-storage-repository.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cache-storage-repository.js","sourceRoot":"","sources":["../../../src/browser/storage/cache-storage-repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,13 +10,23 @@ import type { TranslationData } from '../../types/translation-data.types.js';
|
|
|
10
10
|
*/
|
|
11
11
|
export interface LocalStorageRepositoryConfig extends MemoryRepositoryConfig {
|
|
12
12
|
options?: MemoryRepositoryConfig['options'] & {
|
|
13
|
+
/** Storage key prefix (default: 'lionrapid:') */
|
|
13
14
|
prefix?: string;
|
|
15
|
+
/** TTL in milliseconds (default: 24 hours) */
|
|
14
16
|
ttl?: number;
|
|
17
|
+
/** Maximum number of entries before eviction (default: 500) */
|
|
15
18
|
maxSize?: number;
|
|
16
19
|
};
|
|
17
20
|
}
|
|
18
21
|
/**
|
|
19
22
|
* LocalStorage-based repository for persistent translation caching
|
|
23
|
+
*
|
|
24
|
+
* Features:
|
|
25
|
+
* - Synchronous access (localStorage is sync)
|
|
26
|
+
* - TTL-based expiration
|
|
27
|
+
* - Graceful fallback when localStorage unavailable (SSR, private mode)
|
|
28
|
+
* - Automatic eviction when quota exceeded
|
|
29
|
+
* - Version migration support
|
|
20
30
|
*/
|
|
21
31
|
export declare class LocalStorageRepository implements ISyncRepository<LocalStorageRepositoryConfig>, IStatsProvider {
|
|
22
32
|
readonly name = "LocalStorageRepository";
|
|
@@ -27,21 +37,73 @@ export declare class LocalStorageRepository implements ISyncRepository<LocalStor
|
|
|
27
37
|
private defaultTTL;
|
|
28
38
|
private maxSize;
|
|
29
39
|
constructor(config?: LocalStorageRepositoryConfig);
|
|
40
|
+
/**
|
|
41
|
+
* Check if localStorage is available
|
|
42
|
+
* Returns false in SSR, private browsing, or when quota is 0
|
|
43
|
+
*/
|
|
30
44
|
private checkAvailability;
|
|
45
|
+
/**
|
|
46
|
+
* Handle version migrations - clear cache when version changes
|
|
47
|
+
*/
|
|
31
48
|
private migrateIfNeeded;
|
|
49
|
+
/**
|
|
50
|
+
* Get a translation value by key
|
|
51
|
+
* Returns null if not found or expired
|
|
52
|
+
*/
|
|
32
53
|
get(key: string): string | null;
|
|
54
|
+
/**
|
|
55
|
+
* Set a translation value
|
|
56
|
+
* Automatically handles quota exceeded by evicting oldest entries
|
|
57
|
+
*/
|
|
33
58
|
set(key: string, value: string): void;
|
|
59
|
+
/**
|
|
60
|
+
* Remove a translation by key
|
|
61
|
+
*/
|
|
34
62
|
remove(key: string): void;
|
|
63
|
+
/**
|
|
64
|
+
* Clear all LionRapid entries from localStorage
|
|
65
|
+
* Preserves entries from other applications
|
|
66
|
+
*/
|
|
35
67
|
clear(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Clear all entries for a specific namespace
|
|
70
|
+
*/
|
|
36
71
|
clearNamespace(locale: string, namespace: string): void;
|
|
72
|
+
/**
|
|
73
|
+
* Get namespace translations
|
|
74
|
+
*/
|
|
37
75
|
getNamespace(locale: string, namespace: string): TranslationData | null;
|
|
76
|
+
/**
|
|
77
|
+
* Set namespace translations
|
|
78
|
+
*/
|
|
38
79
|
setNamespace(locale: string, namespace: string, translations: TranslationData): void;
|
|
80
|
+
/**
|
|
81
|
+
* Get all cache entries for debugging
|
|
82
|
+
*/
|
|
39
83
|
getEntries(): Record<string, string>;
|
|
84
|
+
/**
|
|
85
|
+
* Get repository configuration
|
|
86
|
+
*/
|
|
40
87
|
getConfig(): LocalStorageRepositoryConfig;
|
|
88
|
+
/**
|
|
89
|
+
* Check if localStorage is available
|
|
90
|
+
*/
|
|
41
91
|
isAvailable(): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Get the number of entries in the cache
|
|
94
|
+
*/
|
|
42
95
|
private getEntryCount;
|
|
96
|
+
/**
|
|
97
|
+
* Check if error is a quota exceeded error
|
|
98
|
+
*/
|
|
43
99
|
private isQuotaError;
|
|
100
|
+
/**
|
|
101
|
+
* Evict oldest 25% of entries when quota is exceeded
|
|
102
|
+
*/
|
|
44
103
|
private evictOldest;
|
|
104
|
+
/**
|
|
105
|
+
* Get cache statistics
|
|
106
|
+
*/
|
|
45
107
|
getCacheStats(): {
|
|
46
108
|
size: number;
|
|
47
109
|
maxSize: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localstorage-repository.d.ts","sourceRoot":"","sources":["../../../src/browser/storage/localstorage-repository.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACf,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAChF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAe7E;;GAEG;AACH,MAAM,WAAW,4BAA6B,SAAQ,sBAAsB;IAC1E,OAAO,CAAC,EAAE,sBAAsB,CAAC,SAAS,CAAC,GAAG;QAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED
|
|
1
|
+
{"version":3,"file":"localstorage-repository.d.ts","sourceRoot":"","sources":["../../../src/browser/storage/localstorage-repository.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACf,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAChF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAe7E;;GAEG;AACH,MAAM,WAAW,4BAA6B,SAAQ,sBAAsB;IAC1E,OAAO,CAAC,EAAE,sBAAsB,CAAC,SAAS,CAAC,GAAG;QAC5C,iDAAiD;QACjD,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,8CAA8C;QAC9C,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,+DAA+D;QAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED;;;;;;;;;GASG;AACH,qBAAa,sBACX,YAAW,eAAe,CAAC,4BAA4B,CAAC,EAAE,cAAc;IAExE,QAAQ,CAAC,IAAI,4BAA4B;IACzC,QAAQ,CAAC,IAAI,WAAqB;IAClC,QAAQ,CAAC,MAAM,EAAE,4BAA4B,CAAC;IAE9C,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAS;gBAEZ,MAAM,GAAE,4BAAiC;IAsBrD;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAezB;;OAEG;IACH,OAAO,CAAC,eAAe;IAYvB;;;OAGG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAsB/B;;;OAGG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IA+BrC;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAUzB;;;OAGG;IACH,KAAK,IAAI,IAAI;IAiBb;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAuBvD;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAavE;;OAEG;IACH,YAAY,CACV,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,eAAe,GAC5B,IAAI;IAKP;;OAEG;IACH,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAwBpC;;OAEG;IACH,SAAS,IAAI,4BAA4B;IAIzC;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,OAAO,CAAC,aAAa;IAkBrB;;OAEG;IACH,OAAO,CAAC,YAAY;IAWpB;;OAEG;IACH,OAAO,CAAC,WAAW;IAsCnB;;OAEG;IACH,aAAa,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;CAWlE"}
|
|
@@ -7,6 +7,13 @@ const VERSION_KEY = `${STORAGE_PREFIX}version`;
|
|
|
7
7
|
const CURRENT_VERSION = '1';
|
|
8
8
|
/**
|
|
9
9
|
* LocalStorage-based repository for persistent translation caching
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - Synchronous access (localStorage is sync)
|
|
13
|
+
* - TTL-based expiration
|
|
14
|
+
* - Graceful fallback when localStorage unavailable (SSR, private mode)
|
|
15
|
+
* - Automatic eviction when quota exceeded
|
|
16
|
+
* - Version migration support
|
|
10
17
|
*/
|
|
11
18
|
export class LocalStorageRepository {
|
|
12
19
|
name = 'LocalStorageRepository';
|
|
@@ -19,10 +26,10 @@ export class LocalStorageRepository {
|
|
|
19
26
|
constructor(config = {}) {
|
|
20
27
|
this.config = {
|
|
21
28
|
enabled: config.enabled ?? true,
|
|
22
|
-
priority: config.priority ?? 0,
|
|
29
|
+
priority: config.priority ?? 0, // Use build sequence by default
|
|
23
30
|
options: {
|
|
24
31
|
prefix: config.options?.prefix ?? STORAGE_PREFIX,
|
|
25
|
-
ttl: config.options?.ttl ?? 24 * 60 * 60 * 1000,
|
|
32
|
+
ttl: config.options?.ttl ?? 24 * 60 * 60 * 1000, // 24 hours default
|
|
26
33
|
maxSize: config.options?.maxSize ?? 500,
|
|
27
34
|
...config.options,
|
|
28
35
|
},
|
|
@@ -35,6 +42,10 @@ export class LocalStorageRepository {
|
|
|
35
42
|
this.migrateIfNeeded();
|
|
36
43
|
}
|
|
37
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Check if localStorage is available
|
|
47
|
+
* Returns false in SSR, private browsing, or when quota is 0
|
|
48
|
+
*/
|
|
38
49
|
checkAvailability() {
|
|
39
50
|
if (typeof window === 'undefined') {
|
|
40
51
|
return false;
|
|
@@ -49,6 +60,9 @@ export class LocalStorageRepository {
|
|
|
49
60
|
return false;
|
|
50
61
|
}
|
|
51
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Handle version migrations - clear cache when version changes
|
|
65
|
+
*/
|
|
52
66
|
migrateIfNeeded() {
|
|
53
67
|
try {
|
|
54
68
|
const storedVersion = window.localStorage.getItem(VERSION_KEY);
|
|
@@ -61,6 +75,10 @@ export class LocalStorageRepository {
|
|
|
61
75
|
// Ignore migration errors
|
|
62
76
|
}
|
|
63
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Get a translation value by key
|
|
80
|
+
* Returns null if not found or expired
|
|
81
|
+
*/
|
|
64
82
|
get(key) {
|
|
65
83
|
if (!this.available)
|
|
66
84
|
return null;
|
|
@@ -70,6 +88,7 @@ export class LocalStorageRepository {
|
|
|
70
88
|
if (!itemStr)
|
|
71
89
|
return null;
|
|
72
90
|
const item = JSON.parse(itemStr);
|
|
91
|
+
// Check TTL expiry
|
|
73
92
|
if (item.expiry !== null && Date.now() > item.expiry) {
|
|
74
93
|
window.localStorage.removeItem(fullKey);
|
|
75
94
|
return null;
|
|
@@ -80,6 +99,10 @@ export class LocalStorageRepository {
|
|
|
80
99
|
return null;
|
|
81
100
|
}
|
|
82
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Set a translation value
|
|
104
|
+
* Automatically handles quota exceeded by evicting oldest entries
|
|
105
|
+
*/
|
|
83
106
|
set(key, value) {
|
|
84
107
|
if (!this.available)
|
|
85
108
|
return;
|
|
@@ -90,6 +113,7 @@ export class LocalStorageRepository {
|
|
|
90
113
|
timestamp: Date.now(),
|
|
91
114
|
};
|
|
92
115
|
try {
|
|
116
|
+
// Check entry count and evict if needed
|
|
93
117
|
const entryCount = this.getEntryCount();
|
|
94
118
|
if (entryCount >= this.maxSize) {
|
|
95
119
|
this.evictOldest();
|
|
@@ -99,6 +123,7 @@ export class LocalStorageRepository {
|
|
|
99
123
|
catch (error) {
|
|
100
124
|
if (this.isQuotaError(error)) {
|
|
101
125
|
this.evictOldest();
|
|
126
|
+
// Retry once after eviction
|
|
102
127
|
try {
|
|
103
128
|
window.localStorage.setItem(fullKey, JSON.stringify(item));
|
|
104
129
|
}
|
|
@@ -108,6 +133,9 @@ export class LocalStorageRepository {
|
|
|
108
133
|
}
|
|
109
134
|
}
|
|
110
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Remove a translation by key
|
|
138
|
+
*/
|
|
111
139
|
remove(key) {
|
|
112
140
|
if (!this.available)
|
|
113
141
|
return;
|
|
@@ -118,6 +146,10 @@ export class LocalStorageRepository {
|
|
|
118
146
|
// Ignore removal errors
|
|
119
147
|
}
|
|
120
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Clear all LionRapid entries from localStorage
|
|
151
|
+
* Preserves entries from other applications
|
|
152
|
+
*/
|
|
121
153
|
clear() {
|
|
122
154
|
if (!this.available)
|
|
123
155
|
return;
|
|
@@ -129,12 +161,15 @@ export class LocalStorageRepository {
|
|
|
129
161
|
keysToRemove.push(key);
|
|
130
162
|
}
|
|
131
163
|
}
|
|
132
|
-
keysToRemove.forEach(
|
|
164
|
+
keysToRemove.forEach(k => window.localStorage.removeItem(k));
|
|
133
165
|
}
|
|
134
166
|
catch {
|
|
135
167
|
// Ignore clear errors
|
|
136
168
|
}
|
|
137
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Clear all entries for a specific namespace
|
|
172
|
+
*/
|
|
138
173
|
clearNamespace(locale, namespace) {
|
|
139
174
|
if (!this.available)
|
|
140
175
|
return;
|
|
@@ -151,12 +186,15 @@ export class LocalStorageRepository {
|
|
|
151
186
|
keysToRemove.push(key);
|
|
152
187
|
}
|
|
153
188
|
}
|
|
154
|
-
keysToRemove.forEach(
|
|
189
|
+
keysToRemove.forEach(k => window.localStorage.removeItem(k));
|
|
155
190
|
}
|
|
156
191
|
catch {
|
|
157
192
|
// Ignore errors
|
|
158
193
|
}
|
|
159
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Get namespace translations
|
|
197
|
+
*/
|
|
160
198
|
getNamespace(locale, namespace) {
|
|
161
199
|
const namespaceCacheKey = `@${locale}:${namespace}`;
|
|
162
200
|
const value = this.get(namespaceCacheKey);
|
|
@@ -170,10 +208,16 @@ export class LocalStorageRepository {
|
|
|
170
208
|
return null;
|
|
171
209
|
}
|
|
172
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Set namespace translations
|
|
213
|
+
*/
|
|
173
214
|
setNamespace(locale, namespace, translations) {
|
|
174
215
|
const namespaceCacheKey = `@${locale}:${namespace}`;
|
|
175
216
|
this.set(namespaceCacheKey, JSON.stringify(translations));
|
|
176
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Get all cache entries for debugging
|
|
220
|
+
*/
|
|
177
221
|
getEntries() {
|
|
178
222
|
const entries = {};
|
|
179
223
|
if (!this.available)
|
|
@@ -196,12 +240,21 @@ export class LocalStorageRepository {
|
|
|
196
240
|
}
|
|
197
241
|
return entries;
|
|
198
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Get repository configuration
|
|
245
|
+
*/
|
|
199
246
|
getConfig() {
|
|
200
247
|
return this.config;
|
|
201
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* Check if localStorage is available
|
|
251
|
+
*/
|
|
202
252
|
isAvailable() {
|
|
203
253
|
return this.available;
|
|
204
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Get the number of entries in the cache
|
|
257
|
+
*/
|
|
205
258
|
getEntryCount() {
|
|
206
259
|
if (!this.available)
|
|
207
260
|
return 0;
|
|
@@ -219,17 +272,25 @@ export class LocalStorageRepository {
|
|
|
219
272
|
}
|
|
220
273
|
return count;
|
|
221
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Check if error is a quota exceeded error
|
|
277
|
+
*/
|
|
222
278
|
isQuotaError(error) {
|
|
223
279
|
if (error instanceof DOMException) {
|
|
280
|
+
// Most browsers
|
|
224
281
|
return (error.name === 'QuotaExceededError' ||
|
|
225
282
|
error.name === 'NS_ERROR_DOM_QUOTA_REACHED');
|
|
226
283
|
}
|
|
227
284
|
return false;
|
|
228
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* Evict oldest 25% of entries when quota is exceeded
|
|
288
|
+
*/
|
|
229
289
|
evictOldest() {
|
|
230
290
|
if (!this.available)
|
|
231
291
|
return;
|
|
232
292
|
try {
|
|
293
|
+
// Collect all entries with timestamps
|
|
233
294
|
const entries = [];
|
|
234
295
|
for (let i = 0; i < window.localStorage.length; i++) {
|
|
235
296
|
const fullKey = window.localStorage.key(i);
|
|
@@ -244,10 +305,13 @@ export class LocalStorageRepository {
|
|
|
244
305
|
}
|
|
245
306
|
}
|
|
246
307
|
catch {
|
|
308
|
+
// Remove corrupted entry
|
|
247
309
|
window.localStorage.removeItem(fullKey);
|
|
248
310
|
}
|
|
249
311
|
}
|
|
312
|
+
// Sort by timestamp (oldest first)
|
|
250
313
|
entries.sort((a, b) => a.timestamp - b.timestamp);
|
|
314
|
+
// Remove oldest 25%
|
|
251
315
|
const removeCount = Math.max(1, Math.floor(entries.length * 0.25));
|
|
252
316
|
for (let i = 0; i < removeCount && i < entries.length; i++) {
|
|
253
317
|
window.localStorage.removeItem(entries[i].key);
|
|
@@ -257,6 +321,9 @@ export class LocalStorageRepository {
|
|
|
257
321
|
// Ignore eviction errors
|
|
258
322
|
}
|
|
259
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* Get cache statistics
|
|
326
|
+
*/
|
|
260
327
|
getCacheStats() {
|
|
261
328
|
const size = this.getEntryCount();
|
|
262
329
|
const usage = this.maxSize > 0 ? `${Math.round((size / this.maxSize) * 100)}%` : '0%';
|