@form-engine-ts/translator-cache 2.8.0 → 2.9.0

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
@@ -27,3 +27,5 @@ are deduplicated, translated once in source order, cached, and restored to their
27
27
  cache applies both TTL expiration and bounded LRU eviction and exposes `size`, `evictionCount`, and `clear()` diagnostics.
28
28
  Use `variant` to isolate glossary/model/configuration revisions, or `buildKey` for complete key control.
29
29
  `onStatsReport` receives cumulative real cache hits, misses, evictions, and current size after successful translations.
30
+ Cache backend failures default to `cacheErrorPolicy: "bypass"`: `onCacheError` is notified and translation continues
31
+ through the wrapped adapter. Use `cacheErrorPolicy: "throw"` when cache availability is mandatory.
package/dist/index.cjs CHANGED
@@ -113,9 +113,38 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
113
113
  if (options.buildKey !== void 0 && typeof options.buildKey !== "function") {
114
114
  throw new TypeError("buildKey must be a function.");
115
115
  }
116
+ if (options.cacheErrorPolicy !== void 0 && options.cacheErrorPolicy !== "bypass" && options.cacheErrorPolicy !== "throw") {
117
+ throw new TypeError('cacheErrorPolicy must be "bypass" or "throw".');
118
+ }
119
+ const cacheErrorPolicy = options.cacheErrorPolicy ?? "bypass";
116
120
  const initialEvictionCount = cache.evictionCount ?? 0;
117
121
  let hits = 0;
118
122
  let misses = 0;
123
+ const handleCacheError = (cause, operation) => {
124
+ const error = cause instanceof Error ? cause : new Error(String(cause));
125
+ try {
126
+ options.onCacheError?.(error, operation);
127
+ } catch {
128
+ }
129
+ return error;
130
+ };
131
+ const getCached = async (key) => {
132
+ try {
133
+ return await cache.get(key);
134
+ } catch (cause) {
135
+ const error = handleCacheError(cause, "get");
136
+ if (cacheErrorPolicy === "throw") throw error;
137
+ return void 0;
138
+ }
139
+ };
140
+ const setCached = async (key, value) => {
141
+ try {
142
+ await cache.set(key, value, options.ttlMs);
143
+ } catch (cause) {
144
+ const error = handleCacheError(cause, "set");
145
+ if (cacheErrorPolicy === "throw") throw error;
146
+ }
147
+ };
119
148
  const reportStats = () => {
120
149
  const size = cache.size ?? 0;
121
150
  options.onStatsReport?.({
@@ -142,7 +171,7 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
142
171
  if (typeof key !== "string" || key.length === 0) throw new TypeError("Translation cache key must not be empty.");
143
172
  return key;
144
173
  });
145
- const cached = await Promise.all(keys.map((key) => cache.get(key)));
174
+ const cached = await Promise.all(keys.map(getCached));
146
175
  hits += cached.filter((value) => value !== void 0).length;
147
176
  misses += cached.filter((value) => value === void 0).length;
148
177
  const missingByKey = /* @__PURE__ */ new Map();
@@ -169,7 +198,7 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
169
198
  missing.map(async ([key, value], translatedIndex) => {
170
199
  const translation = translated[translatedIndex];
171
200
  if (translation === void 0) throw new Error("Translation adapter result is unavailable.");
172
- await cache.set(key, translation, options.ttlMs);
201
+ await setCached(key, translation);
173
202
  for (const index of value.indices) cached[index] = translation;
174
203
  })
175
204
  );
package/dist/index.d.cts CHANGED
@@ -25,6 +25,8 @@ interface TranslationCacheOptions {
25
25
  readonly variant?: string;
26
26
  readonly buildKey?: (context: TranslationCacheKeyContext) => string;
27
27
  readonly onStatsReport?: (stats: TranslationCacheStats) => void;
28
+ readonly cacheErrorPolicy?: "bypass" | "throw";
29
+ readonly onCacheError?: (error: Error, operation: "get" | "set") => void;
28
30
  }
29
31
  interface MemoryTranslationCacheOptions {
30
32
  readonly maxEntries?: number;
package/dist/index.d.ts CHANGED
@@ -25,6 +25,8 @@ interface TranslationCacheOptions {
25
25
  readonly variant?: string;
26
26
  readonly buildKey?: (context: TranslationCacheKeyContext) => string;
27
27
  readonly onStatsReport?: (stats: TranslationCacheStats) => void;
28
+ readonly cacheErrorPolicy?: "bypass" | "throw";
29
+ readonly onCacheError?: (error: Error, operation: "get" | "set") => void;
28
30
  }
29
31
  interface MemoryTranslationCacheOptions {
30
32
  readonly maxEntries?: number;
package/dist/index.js CHANGED
@@ -87,9 +87,38 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
87
87
  if (options.buildKey !== void 0 && typeof options.buildKey !== "function") {
88
88
  throw new TypeError("buildKey must be a function.");
89
89
  }
90
+ if (options.cacheErrorPolicy !== void 0 && options.cacheErrorPolicy !== "bypass" && options.cacheErrorPolicy !== "throw") {
91
+ throw new TypeError('cacheErrorPolicy must be "bypass" or "throw".');
92
+ }
93
+ const cacheErrorPolicy = options.cacheErrorPolicy ?? "bypass";
90
94
  const initialEvictionCount = cache.evictionCount ?? 0;
91
95
  let hits = 0;
92
96
  let misses = 0;
97
+ const handleCacheError = (cause, operation) => {
98
+ const error = cause instanceof Error ? cause : new Error(String(cause));
99
+ try {
100
+ options.onCacheError?.(error, operation);
101
+ } catch {
102
+ }
103
+ return error;
104
+ };
105
+ const getCached = async (key) => {
106
+ try {
107
+ return await cache.get(key);
108
+ } catch (cause) {
109
+ const error = handleCacheError(cause, "get");
110
+ if (cacheErrorPolicy === "throw") throw error;
111
+ return void 0;
112
+ }
113
+ };
114
+ const setCached = async (key, value) => {
115
+ try {
116
+ await cache.set(key, value, options.ttlMs);
117
+ } catch (cause) {
118
+ const error = handleCacheError(cause, "set");
119
+ if (cacheErrorPolicy === "throw") throw error;
120
+ }
121
+ };
93
122
  const reportStats = () => {
94
123
  const size = cache.size ?? 0;
95
124
  options.onStatsReport?.({
@@ -116,7 +145,7 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
116
145
  if (typeof key !== "string" || key.length === 0) throw new TypeError("Translation cache key must not be empty.");
117
146
  return key;
118
147
  });
119
- const cached = await Promise.all(keys.map((key) => cache.get(key)));
148
+ const cached = await Promise.all(keys.map(getCached));
120
149
  hits += cached.filter((value) => value !== void 0).length;
121
150
  misses += cached.filter((value) => value === void 0).length;
122
151
  const missingByKey = /* @__PURE__ */ new Map();
@@ -143,7 +172,7 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
143
172
  missing.map(async ([key, value], translatedIndex) => {
144
173
  const translation = translated[translatedIndex];
145
174
  if (translation === void 0) throw new Error("Translation adapter result is unavailable.");
146
- await cache.set(key, translation, options.ttlMs);
175
+ await setCached(key, translation);
147
176
  for (const index of value.indices) cached[index] = translation;
148
177
  })
149
178
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/translator-cache",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -38,7 +38,7 @@
38
38
  "typescript"
39
39
  ],
40
40
  "dependencies": {
41
- "@form-engine-ts/core": "2.8.0"
41
+ "@form-engine-ts/core": "2.9.0"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsup src/index.ts --format esm,cjs --dts --clean --external @form-engine-ts/core",