@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
|
@@ -2819,6 +2819,7 @@ var LionRapidCore = class {
|
|
|
2819
2819
|
// 🔥 EMBEDDED: Core integration maps
|
|
2820
2820
|
__publicField(this, "formatters", /* @__PURE__ */ new Map());
|
|
2821
2821
|
__publicField(this, "processors", /* @__PURE__ */ new Map());
|
|
2822
|
+
__publicField(this, "warnedNoInterpolate", false);
|
|
2822
2823
|
// 📡 EVENT-DRIVEN: Just track for cleanup
|
|
2823
2824
|
__publicField(this, "eventPlugins", /* @__PURE__ */ new Set());
|
|
2824
2825
|
// 📦 CONTENT: Parser and serializer for ContentUnit processing
|
|
@@ -3111,6 +3112,11 @@ var LionRapidCore = class {
|
|
|
3111
3112
|
resolvedLocale,
|
|
3112
3113
|
resolvedKey
|
|
3113
3114
|
);
|
|
3115
|
+
} else if (!this.warnedNoInterpolate && params && Object.keys(params).length > 0 && processedValue.includes("{")) {
|
|
3116
|
+
this.warnedNoInterpolate = true;
|
|
3117
|
+
console.warn(
|
|
3118
|
+
"[LionRapid] Translation params were passed but no interpolate formatter is registered \u2014 {placeholders} render literally. Install @lionrapid/formatters and add .use(coreFormattersPlugin) to the builder."
|
|
3119
|
+
);
|
|
3114
3120
|
}
|
|
3115
3121
|
if (this.processors.size > 0) {
|
|
3116
3122
|
for (const [name, processor] of this.processors.entries()) {
|
|
@@ -3891,6 +3897,31 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3891
3897
|
* and payload can never desync because they live and die together.
|
|
3892
3898
|
*/
|
|
3893
3899
|
__publicField(this, "namespaceSnapshots", /* @__PURE__ */ new Map());
|
|
3900
|
+
/**
|
|
3901
|
+
* Miss-path flood protection (READINESS before-scale #1: one cold locale
|
|
3902
|
+
* switch measured ~380 requests in 10 s — every t() miss refired a key GET
|
|
3903
|
+
* and a namespace GET on every re-render, forever on animated pages).
|
|
3904
|
+
*/
|
|
3905
|
+
/** GETs currently on the wire, keyed by `url|if-none-match` — identical
|
|
3906
|
+
* concurrent requests share one fetch; each awaiter gets a clone. The
|
|
3907
|
+
* If-None-Match value is part of the key so the 304-without-snapshot
|
|
3908
|
+
* unconditional refetch never coalesces onto a conditional flight. */
|
|
3909
|
+
__publicField(this, "inflightGets", /* @__PURE__ */ new Map());
|
|
3910
|
+
/** URL → 404/423 seen recently: repeats are answered locally (same typed
|
|
3911
|
+
* error, no network) until `until`. 423 honors the server's Retry-After.
|
|
3912
|
+
* Cleared entirely by any successful write — discovery sync keeps its
|
|
3913
|
+
* miss → batch → served-back round-trip. */
|
|
3914
|
+
__publicField(this, "negativeCache", /* @__PURE__ */ new Map());
|
|
3915
|
+
/** One-way latch: a 401 anywhere (or a 403 on a READ) means the key is
|
|
3916
|
+
* wrong or unauthorized for this project, and retrying cannot heal it —
|
|
3917
|
+
* after the first one nothing touches the network again for this
|
|
3918
|
+
* instance (a page reload starts fresh). Holds the status that tripped
|
|
3919
|
+
* it so short-circuits answer honestly. */
|
|
3920
|
+
__publicField(this, "authFailedStatus", null);
|
|
3921
|
+
/** Write-only latch (auth-byok Phase 18): a 403 (scope) or 429 (quota) on
|
|
3922
|
+
* a WRITE degrades this instance to read-only — writes stop, silently
|
|
3923
|
+
* and without retries, while reads keep serving the page. */
|
|
3924
|
+
__publicField(this, "writesFailedStatus", null);
|
|
3894
3925
|
/**
|
|
3895
3926
|
* Default endpoint templates with placeholder support
|
|
3896
3927
|
* Placeholders: :key, :locale, :namespace
|
|
@@ -3898,6 +3929,7 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3898
3929
|
__publicField(this, "DEFAULT_ENDPOINTS", {
|
|
3899
3930
|
getTranslation: "/api/integrations/translations/:key",
|
|
3900
3931
|
getNamespace: "/api/integrations/translations/:locale/:namespace",
|
|
3932
|
+
getProjectConfig: "/api/integrations/project-config",
|
|
3901
3933
|
setTranslation: "/api/integrations/sync/:key",
|
|
3902
3934
|
setNamespace: "/api/integrations/sync/:locale/:namespace",
|
|
3903
3935
|
setBatch: "/api/integrations/sync/batch",
|
|
@@ -4009,6 +4041,40 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
4009
4041
|
this.handleCaughtError(error);
|
|
4010
4042
|
}
|
|
4011
4043
|
}
|
|
4044
|
+
/**
|
|
4045
|
+
* Project-level config for this key — the dashboard's language list,
|
|
4046
|
+
* served under the same trust model as translation reads (docs/20,
|
|
4047
|
+
* companion track). Returns null when the server doesn't expose the
|
|
4048
|
+
* endpoint (404 — older server, feature not deployed) or the payload is
|
|
4049
|
+
* unusable: absence means "keep your local fallback", never an error.
|
|
4050
|
+
*/
|
|
4051
|
+
async getProjectConfig() {
|
|
4052
|
+
if (!this.config?.options?.baseUrl) {
|
|
4053
|
+
throw new NetworkError("API base URL is not set");
|
|
4054
|
+
}
|
|
4055
|
+
const url = this.buildUrl("getProjectConfig", {});
|
|
4056
|
+
logger.debug(`API GET ProjectConfig: ${url}`);
|
|
4057
|
+
try {
|
|
4058
|
+
const response = await this.makeRequest(url, "GET");
|
|
4059
|
+
if (response.status === 404) {
|
|
4060
|
+
return null;
|
|
4061
|
+
}
|
|
4062
|
+
if (!response.ok) {
|
|
4063
|
+
this.handleHttpError(response, "project-config");
|
|
4064
|
+
}
|
|
4065
|
+
const data = await response.json();
|
|
4066
|
+
if (!data || typeof data.defaultLocale !== "string" || !Array.isArray(data.languages)) {
|
|
4067
|
+
logger.warn("project-config payload unusable \u2014 keeping local fallback");
|
|
4068
|
+
return null;
|
|
4069
|
+
}
|
|
4070
|
+
return {
|
|
4071
|
+
defaultLocale: data.defaultLocale,
|
|
4072
|
+
languages: data.languages.map(String)
|
|
4073
|
+
};
|
|
4074
|
+
} catch (error) {
|
|
4075
|
+
this.handleCaughtError(error);
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4012
4078
|
/**
|
|
4013
4079
|
* Shared namespace fetch with the canonical 304 contract:
|
|
4014
4080
|
* - If-None-Match is sent ONLY when a snapshot (version + payload) is in hand.
|
|
@@ -4333,7 +4399,87 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
4333
4399
|
/**
|
|
4334
4400
|
* Make HTTP request with timeout and headers
|
|
4335
4401
|
*/
|
|
4336
|
-
|
|
4402
|
+
makeRequest(url, method, body, extraHeaders) {
|
|
4403
|
+
if (this.authFailedStatus !== null) {
|
|
4404
|
+
return Promise.resolve(
|
|
4405
|
+
new Response(null, {
|
|
4406
|
+
status: this.authFailedStatus,
|
|
4407
|
+
statusText: `auth latched off after ${this.authFailedStatus}`
|
|
4408
|
+
})
|
|
4409
|
+
);
|
|
4410
|
+
}
|
|
4411
|
+
if (method !== "GET") {
|
|
4412
|
+
if (this.writesFailedStatus !== null) {
|
|
4413
|
+
return Promise.resolve(
|
|
4414
|
+
new Response(null, {
|
|
4415
|
+
status: this.writesFailedStatus,
|
|
4416
|
+
statusText: `writes latched off after ${this.writesFailedStatus}`
|
|
4417
|
+
})
|
|
4418
|
+
);
|
|
4419
|
+
}
|
|
4420
|
+
return this.doFetch(url, method, body, extraHeaders).then(
|
|
4421
|
+
(resp) => this.recordOutcome(url, method, resp)
|
|
4422
|
+
);
|
|
4423
|
+
}
|
|
4424
|
+
const negative = this.negativeCache.get(url);
|
|
4425
|
+
if (negative) {
|
|
4426
|
+
if (Date.now() < negative.until) {
|
|
4427
|
+
logger.debug(`Negative-cache hit (${negative.status}): ${url}`);
|
|
4428
|
+
const headers = negative.status === 423 ? { "Retry-After": String(Math.ceil((negative.until - Date.now()) / 1e3)) } : void 0;
|
|
4429
|
+
return Promise.resolve(
|
|
4430
|
+
new Response(null, { status: negative.status, statusText: "negative cache", headers })
|
|
4431
|
+
);
|
|
4432
|
+
}
|
|
4433
|
+
this.negativeCache.delete(url);
|
|
4434
|
+
}
|
|
4435
|
+
const flightKey = `${url}|${extraHeaders?.["If-None-Match"] ?? ""}`;
|
|
4436
|
+
const existing = this.inflightGets.get(flightKey);
|
|
4437
|
+
if (existing) {
|
|
4438
|
+
return existing.then((resp) => this.cloneResponse(resp));
|
|
4439
|
+
}
|
|
4440
|
+
const flight = this.doFetch(url, method, body, extraHeaders).then((resp) => this.recordOutcome(url, method, resp)).finally(() => this.inflightGets.delete(flightKey));
|
|
4441
|
+
this.inflightGets.set(flightKey, flight);
|
|
4442
|
+
return flight.then((resp) => this.cloneResponse(resp));
|
|
4443
|
+
}
|
|
4444
|
+
/** Bookkeeping on every real (non-synthesized) response — runs once per
|
|
4445
|
+
* physical fetch, never once per coalesced awaiter. */
|
|
4446
|
+
recordOutcome(url, method, response) {
|
|
4447
|
+
const status = response.status;
|
|
4448
|
+
if (status === 401 || status === 403 && method === "GET") {
|
|
4449
|
+
if (this.authFailedStatus === null) {
|
|
4450
|
+
this.authFailedStatus = status;
|
|
4451
|
+
console.warn(
|
|
4452
|
+
`[LionRapid] API key rejected (${status}) \u2014 network translations are disabled for this session. Fix the key and reload; retrying cannot heal an auth failure.`
|
|
4453
|
+
);
|
|
4454
|
+
}
|
|
4455
|
+
return response;
|
|
4456
|
+
}
|
|
4457
|
+
if ((status === 403 || status === 429) && method !== "GET") {
|
|
4458
|
+
if (this.writesFailedStatus === null) {
|
|
4459
|
+
this.writesFailedStatus = status;
|
|
4460
|
+
logger.warn(
|
|
4461
|
+
`Write path disabled after ${status} (scope/quota) \u2014 running read-only`
|
|
4462
|
+
);
|
|
4463
|
+
}
|
|
4464
|
+
return response;
|
|
4465
|
+
}
|
|
4466
|
+
if (method === "GET" && (status === 404 || status === 423)) {
|
|
4467
|
+
const retryAfterSec = status === 423 ? Number(response.headers?.get?.("Retry-After")) : NaN;
|
|
4468
|
+
const ttl = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? retryAfterSec * 1e3 : this.config.options?.negativeCacheTtlMs ?? _NetworkRepository.DEFAULT_NEGATIVE_TTL_MS;
|
|
4469
|
+
this.negativeCache.set(url, { status, until: Date.now() + ttl });
|
|
4470
|
+
} else if (method !== "GET" && response.ok) {
|
|
4471
|
+
this.negativeCache.clear();
|
|
4472
|
+
} else if (method === "GET" && response.ok) {
|
|
4473
|
+
this.negativeCache.delete(url);
|
|
4474
|
+
}
|
|
4475
|
+
return response;
|
|
4476
|
+
}
|
|
4477
|
+
/** Test doubles (and exotic runtimes) hand us plain objects — only real
|
|
4478
|
+
* Responses need cloning so each coalesced awaiter can read the body. */
|
|
4479
|
+
cloneResponse(response) {
|
|
4480
|
+
return typeof response.clone === "function" ? response.clone() : response;
|
|
4481
|
+
}
|
|
4482
|
+
async doFetch(url, method, body, extraHeaders) {
|
|
4337
4483
|
const controller = new AbortController();
|
|
4338
4484
|
const timeout = this.config.options?.timeout ?? 1e4;
|
|
4339
4485
|
const timeoutId = setTimeout(
|
|
@@ -4366,6 +4512,7 @@ __publicField(_NetworkRepository, "SNAPSHOT_PREFIX", "lionrapid:ns:");
|
|
|
4366
4512
|
/** Legacy bare-version prefix — a version WITHOUT its payload is exactly the
|
|
4367
4513
|
* desync the snapshot store exists to prevent; entries are deleted on sight. */
|
|
4368
4514
|
__publicField(_NetworkRepository, "LEGACY_VERSION_PREFIX", "lionrapid:v:");
|
|
4515
|
+
__publicField(_NetworkRepository, "DEFAULT_NEGATIVE_TTL_MS", 6e4);
|
|
4369
4516
|
var NetworkRepository = _NetworkRepository;
|
|
4370
4517
|
|
|
4371
4518
|
// src/content/validator/ContentValidator.ts
|
|
@@ -4964,9 +5111,11 @@ var LocalStorageRepository = class {
|
|
|
4964
5111
|
this.config = {
|
|
4965
5112
|
enabled: config.enabled ?? true,
|
|
4966
5113
|
priority: config.priority ?? 0,
|
|
5114
|
+
// Use build sequence by default
|
|
4967
5115
|
options: {
|
|
4968
5116
|
prefix: config.options?.prefix ?? STORAGE_PREFIX,
|
|
4969
5117
|
ttl: config.options?.ttl ?? 24 * 60 * 60 * 1e3,
|
|
5118
|
+
// 24 hours default
|
|
4970
5119
|
maxSize: config.options?.maxSize ?? 500,
|
|
4971
5120
|
...config.options
|
|
4972
5121
|
}
|
|
@@ -4979,6 +5128,10 @@ var LocalStorageRepository = class {
|
|
|
4979
5128
|
this.migrateIfNeeded();
|
|
4980
5129
|
}
|
|
4981
5130
|
}
|
|
5131
|
+
/**
|
|
5132
|
+
* Check if localStorage is available
|
|
5133
|
+
* Returns false in SSR, private browsing, or when quota is 0
|
|
5134
|
+
*/
|
|
4982
5135
|
checkAvailability() {
|
|
4983
5136
|
if (typeof window === "undefined") {
|
|
4984
5137
|
return false;
|
|
@@ -4992,6 +5145,9 @@ var LocalStorageRepository = class {
|
|
|
4992
5145
|
return false;
|
|
4993
5146
|
}
|
|
4994
5147
|
}
|
|
5148
|
+
/**
|
|
5149
|
+
* Handle version migrations - clear cache when version changes
|
|
5150
|
+
*/
|
|
4995
5151
|
migrateIfNeeded() {
|
|
4996
5152
|
try {
|
|
4997
5153
|
const storedVersion = window.localStorage.getItem(VERSION_KEY);
|
|
@@ -5002,6 +5158,10 @@ var LocalStorageRepository = class {
|
|
|
5002
5158
|
} catch {
|
|
5003
5159
|
}
|
|
5004
5160
|
}
|
|
5161
|
+
/**
|
|
5162
|
+
* Get a translation value by key
|
|
5163
|
+
* Returns null if not found or expired
|
|
5164
|
+
*/
|
|
5005
5165
|
get(key) {
|
|
5006
5166
|
if (!this.available) return null;
|
|
5007
5167
|
try {
|
|
@@ -5018,6 +5178,10 @@ var LocalStorageRepository = class {
|
|
|
5018
5178
|
return null;
|
|
5019
5179
|
}
|
|
5020
5180
|
}
|
|
5181
|
+
/**
|
|
5182
|
+
* Set a translation value
|
|
5183
|
+
* Automatically handles quota exceeded by evicting oldest entries
|
|
5184
|
+
*/
|
|
5021
5185
|
set(key, value) {
|
|
5022
5186
|
if (!this.available) return;
|
|
5023
5187
|
const fullKey = this.prefix + key;
|
|
@@ -5042,6 +5206,9 @@ var LocalStorageRepository = class {
|
|
|
5042
5206
|
}
|
|
5043
5207
|
}
|
|
5044
5208
|
}
|
|
5209
|
+
/**
|
|
5210
|
+
* Remove a translation by key
|
|
5211
|
+
*/
|
|
5045
5212
|
remove(key) {
|
|
5046
5213
|
if (!this.available) return;
|
|
5047
5214
|
try {
|
|
@@ -5049,6 +5216,10 @@ var LocalStorageRepository = class {
|
|
|
5049
5216
|
} catch {
|
|
5050
5217
|
}
|
|
5051
5218
|
}
|
|
5219
|
+
/**
|
|
5220
|
+
* Clear all LionRapid entries from localStorage
|
|
5221
|
+
* Preserves entries from other applications
|
|
5222
|
+
*/
|
|
5052
5223
|
clear() {
|
|
5053
5224
|
if (!this.available) return;
|
|
5054
5225
|
try {
|
|
@@ -5063,6 +5234,9 @@ var LocalStorageRepository = class {
|
|
|
5063
5234
|
} catch {
|
|
5064
5235
|
}
|
|
5065
5236
|
}
|
|
5237
|
+
/**
|
|
5238
|
+
* Clear all entries for a specific namespace
|
|
5239
|
+
*/
|
|
5066
5240
|
clearNamespace(locale, namespace) {
|
|
5067
5241
|
if (!this.available) return;
|
|
5068
5242
|
const namespaceCacheKey = `@${locale}:${namespace}`;
|
|
@@ -5081,6 +5255,9 @@ var LocalStorageRepository = class {
|
|
|
5081
5255
|
} catch {
|
|
5082
5256
|
}
|
|
5083
5257
|
}
|
|
5258
|
+
/**
|
|
5259
|
+
* Get namespace translations
|
|
5260
|
+
*/
|
|
5084
5261
|
getNamespace(locale, namespace) {
|
|
5085
5262
|
const namespaceCacheKey = `@${locale}:${namespace}`;
|
|
5086
5263
|
const value = this.get(namespaceCacheKey);
|
|
@@ -5092,10 +5269,16 @@ var LocalStorageRepository = class {
|
|
|
5092
5269
|
return null;
|
|
5093
5270
|
}
|
|
5094
5271
|
}
|
|
5272
|
+
/**
|
|
5273
|
+
* Set namespace translations
|
|
5274
|
+
*/
|
|
5095
5275
|
setNamespace(locale, namespace, translations) {
|
|
5096
5276
|
const namespaceCacheKey = `@${locale}:${namespace}`;
|
|
5097
5277
|
this.set(namespaceCacheKey, JSON.stringify(translations));
|
|
5098
5278
|
}
|
|
5279
|
+
/**
|
|
5280
|
+
* Get all cache entries for debugging
|
|
5281
|
+
*/
|
|
5099
5282
|
getEntries() {
|
|
5100
5283
|
const entries = {};
|
|
5101
5284
|
if (!this.available) return entries;
|
|
@@ -5115,12 +5298,21 @@ var LocalStorageRepository = class {
|
|
|
5115
5298
|
}
|
|
5116
5299
|
return entries;
|
|
5117
5300
|
}
|
|
5301
|
+
/**
|
|
5302
|
+
* Get repository configuration
|
|
5303
|
+
*/
|
|
5118
5304
|
getConfig() {
|
|
5119
5305
|
return this.config;
|
|
5120
5306
|
}
|
|
5307
|
+
/**
|
|
5308
|
+
* Check if localStorage is available
|
|
5309
|
+
*/
|
|
5121
5310
|
isAvailable() {
|
|
5122
5311
|
return this.available;
|
|
5123
5312
|
}
|
|
5313
|
+
/**
|
|
5314
|
+
* Get the number of entries in the cache
|
|
5315
|
+
*/
|
|
5124
5316
|
getEntryCount() {
|
|
5125
5317
|
if (!this.available) return 0;
|
|
5126
5318
|
let count = 0;
|
|
@@ -5135,12 +5327,18 @@ var LocalStorageRepository = class {
|
|
|
5135
5327
|
}
|
|
5136
5328
|
return count;
|
|
5137
5329
|
}
|
|
5330
|
+
/**
|
|
5331
|
+
* Check if error is a quota exceeded error
|
|
5332
|
+
*/
|
|
5138
5333
|
isQuotaError(error) {
|
|
5139
5334
|
if (error instanceof DOMException) {
|
|
5140
5335
|
return error.name === "QuotaExceededError" || error.name === "NS_ERROR_DOM_QUOTA_REACHED";
|
|
5141
5336
|
}
|
|
5142
5337
|
return false;
|
|
5143
5338
|
}
|
|
5339
|
+
/**
|
|
5340
|
+
* Evict oldest 25% of entries when quota is exceeded
|
|
5341
|
+
*/
|
|
5144
5342
|
evictOldest() {
|
|
5145
5343
|
if (!this.available) return;
|
|
5146
5344
|
try {
|
|
@@ -5168,6 +5366,9 @@ var LocalStorageRepository = class {
|
|
|
5168
5366
|
} catch {
|
|
5169
5367
|
}
|
|
5170
5368
|
}
|
|
5369
|
+
/**
|
|
5370
|
+
* Get cache statistics
|
|
5371
|
+
*/
|
|
5171
5372
|
getCacheStats() {
|
|
5172
5373
|
const size = this.getEntryCount();
|
|
5173
5374
|
const usage = this.maxSize > 0 ? `${Math.round(size / this.maxSize * 100)}%` : "0%";
|
|
@@ -5181,7 +5382,7 @@ var LocalStorageRepository = class {
|
|
|
5181
5382
|
|
|
5182
5383
|
// src/browser/storage/cache-storage-repository.ts
|
|
5183
5384
|
var CACHE_NAME = "lionrapid-v1";
|
|
5184
|
-
var CACHE_PREFIX = "lionrapid
|
|
5385
|
+
var CACHE_PREFIX = "https://cache.lionrapid.internal/";
|
|
5185
5386
|
var CacheStorageRepository = class {
|
|
5186
5387
|
constructor(config = {}) {
|
|
5187
5388
|
__publicField(this, "name", "CacheStorageRepository");
|
|
@@ -5196,6 +5397,7 @@ var CacheStorageRepository = class {
|
|
|
5196
5397
|
options: {
|
|
5197
5398
|
cacheName: config.options?.cacheName ?? CACHE_NAME,
|
|
5198
5399
|
ttl: config.options?.ttl ?? 7 * 24 * 60 * 60 * 1e3,
|
|
5400
|
+
// 7 days default
|
|
5199
5401
|
...config.options
|
|
5200
5402
|
}
|
|
5201
5403
|
};
|
|
@@ -5203,9 +5405,17 @@ var CacheStorageRepository = class {
|
|
|
5203
5405
|
this.defaultTTL = this.config.options?.ttl ?? 7 * 24 * 60 * 60 * 1e3;
|
|
5204
5406
|
this.available = this.checkAvailability();
|
|
5205
5407
|
}
|
|
5408
|
+
/**
|
|
5409
|
+
* Check if Cache API is available
|
|
5410
|
+
* Returns false in SSR, old browsers, or insecure contexts
|
|
5411
|
+
*/
|
|
5206
5412
|
checkAvailability() {
|
|
5207
5413
|
return typeof caches !== "undefined";
|
|
5208
5414
|
}
|
|
5415
|
+
/**
|
|
5416
|
+
* Get a translation value by key
|
|
5417
|
+
* @throws {TranslationNotFoundError} When translation doesn't exist or is expired
|
|
5418
|
+
*/
|
|
5209
5419
|
async get(key) {
|
|
5210
5420
|
if (!this.available) {
|
|
5211
5421
|
throw new TranslationNotFoundError(key);
|
|
@@ -5229,6 +5439,9 @@ var CacheStorageRepository = class {
|
|
|
5229
5439
|
throw new TranslationNotFoundError(key);
|
|
5230
5440
|
}
|
|
5231
5441
|
}
|
|
5442
|
+
/**
|
|
5443
|
+
* Set a translation value
|
|
5444
|
+
*/
|
|
5232
5445
|
async set(key, value) {
|
|
5233
5446
|
if (!this.available) return;
|
|
5234
5447
|
try {
|
|
@@ -5247,6 +5460,9 @@ var CacheStorageRepository = class {
|
|
|
5247
5460
|
} catch {
|
|
5248
5461
|
}
|
|
5249
5462
|
}
|
|
5463
|
+
/**
|
|
5464
|
+
* Remove a translation by key
|
|
5465
|
+
*/
|
|
5250
5466
|
async remove(key) {
|
|
5251
5467
|
if (!this.available) return;
|
|
5252
5468
|
try {
|
|
@@ -5255,6 +5471,9 @@ var CacheStorageRepository = class {
|
|
|
5255
5471
|
} catch {
|
|
5256
5472
|
}
|
|
5257
5473
|
}
|
|
5474
|
+
/**
|
|
5475
|
+
* Clear all LionRapid entries from the cache
|
|
5476
|
+
*/
|
|
5258
5477
|
async clear() {
|
|
5259
5478
|
if (!this.available) return;
|
|
5260
5479
|
try {
|
|
@@ -5266,6 +5485,9 @@ var CacheStorageRepository = class {
|
|
|
5266
5485
|
} catch {
|
|
5267
5486
|
}
|
|
5268
5487
|
}
|
|
5488
|
+
/**
|
|
5489
|
+
* Clear all entries for a specific namespace
|
|
5490
|
+
*/
|
|
5269
5491
|
async clearNamespace(locale, namespace) {
|
|
5270
5492
|
if (!this.available) return;
|
|
5271
5493
|
const namespaceCacheKey = `@${locale}:${namespace}`;
|
|
@@ -5283,6 +5505,10 @@ var CacheStorageRepository = class {
|
|
|
5283
5505
|
} catch {
|
|
5284
5506
|
}
|
|
5285
5507
|
}
|
|
5508
|
+
/**
|
|
5509
|
+
* Get namespace translations
|
|
5510
|
+
* @throws {TranslationNotFoundError} When namespace doesn't exist
|
|
5511
|
+
*/
|
|
5286
5512
|
async getNamespace(locale, namespace) {
|
|
5287
5513
|
const namespaceCacheKey = `@${locale}:${namespace}`;
|
|
5288
5514
|
const value = await this.get(namespaceCacheKey);
|
|
@@ -5293,16 +5519,29 @@ var CacheStorageRepository = class {
|
|
|
5293
5519
|
throw new TranslationNotFoundError(namespaceCacheKey);
|
|
5294
5520
|
}
|
|
5295
5521
|
}
|
|
5522
|
+
/**
|
|
5523
|
+
* Set namespace translations
|
|
5524
|
+
*/
|
|
5296
5525
|
async setNamespace(locale, namespace, translations) {
|
|
5297
5526
|
const namespaceCacheKey = `@${locale}:${namespace}`;
|
|
5298
5527
|
await this.set(namespaceCacheKey, JSON.stringify(translations));
|
|
5299
5528
|
}
|
|
5529
|
+
/**
|
|
5530
|
+
* Get repository configuration
|
|
5531
|
+
*/
|
|
5300
5532
|
getConfig() {
|
|
5301
5533
|
return this.config;
|
|
5302
5534
|
}
|
|
5535
|
+
/**
|
|
5536
|
+
* Check if Cache API is available
|
|
5537
|
+
*/
|
|
5303
5538
|
isAvailable() {
|
|
5304
5539
|
return this.available;
|
|
5305
5540
|
}
|
|
5541
|
+
/**
|
|
5542
|
+
* Get all cached entries (for debugging)
|
|
5543
|
+
* Returns an object mapping keys to values
|
|
5544
|
+
*/
|
|
5306
5545
|
async getEntries() {
|
|
5307
5546
|
const entries = {};
|
|
5308
5547
|
if (!this.available) return entries;
|
|
@@ -5325,6 +5564,9 @@ var CacheStorageRepository = class {
|
|
|
5325
5564
|
}
|
|
5326
5565
|
return entries;
|
|
5327
5566
|
}
|
|
5567
|
+
/**
|
|
5568
|
+
* Get cache statistics
|
|
5569
|
+
*/
|
|
5328
5570
|
async getCacheStats() {
|
|
5329
5571
|
if (!this.available) {
|
|
5330
5572
|
return { size: 0, cacheName: this.cacheName };
|