@form-engine-ts/translator-cache 2.8.0 → 2.9.1
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 +4 -0
- package/dist/index.cjs +44 -4
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +44 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -26,4 +26,8 @@ Keys isolate adapter name, source locale, target locale, and a deterministic UTF
|
|
|
26
26
|
are deduplicated, translated once in source order, cached, and restored to their original positions. The built-in memory
|
|
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
|
+
Adapters that expose a locale-aware cache variant (such as `@form-engine-ts/translator-google-v3` with a glossary)
|
|
30
|
+
are isolated automatically; an explicit `variant` is combined with that adapter variant.
|
|
29
31
|
`onStatsReport` receives cumulative real cache hits, misses, evictions, and current size after successful translations.
|
|
32
|
+
Cache backend failures default to `cacheErrorPolicy: "bypass"`: `onCacheError` is notified and translation continues
|
|
33
|
+
through the wrapped adapter. Use `cacheErrorPolicy: "throw"` when cache availability is mandatory.
|
package/dist/index.cjs
CHANGED
|
@@ -48,6 +48,11 @@ function nonNegativeDuration(value, fallback, name) {
|
|
|
48
48
|
}
|
|
49
49
|
return resolved;
|
|
50
50
|
}
|
|
51
|
+
function getVariantProvider(adapter) {
|
|
52
|
+
if (typeof adapter !== "object" || adapter === null) return void 0;
|
|
53
|
+
const candidate = adapter;
|
|
54
|
+
return typeof candidate.getCacheVariant === "function" ? { getCacheVariant: candidate.getCacheVariant.bind(adapter) } : void 0;
|
|
55
|
+
}
|
|
51
56
|
function createMemoryTranslationCache(options = {}) {
|
|
52
57
|
const maxEntries = options.maxEntries ?? 500;
|
|
53
58
|
if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
|
|
@@ -113,9 +118,39 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
|
|
|
113
118
|
if (options.buildKey !== void 0 && typeof options.buildKey !== "function") {
|
|
114
119
|
throw new TypeError("buildKey must be a function.");
|
|
115
120
|
}
|
|
121
|
+
if (options.cacheErrorPolicy !== void 0 && options.cacheErrorPolicy !== "bypass" && options.cacheErrorPolicy !== "throw") {
|
|
122
|
+
throw new TypeError('cacheErrorPolicy must be "bypass" or "throw".');
|
|
123
|
+
}
|
|
124
|
+
const cacheErrorPolicy = options.cacheErrorPolicy ?? "bypass";
|
|
116
125
|
const initialEvictionCount = cache.evictionCount ?? 0;
|
|
117
126
|
let hits = 0;
|
|
118
127
|
let misses = 0;
|
|
128
|
+
const variantProvider = getVariantProvider(baseAdapter);
|
|
129
|
+
const handleCacheError = (cause, operation) => {
|
|
130
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
131
|
+
try {
|
|
132
|
+
options.onCacheError?.(error, operation);
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
return error;
|
|
136
|
+
};
|
|
137
|
+
const getCached = async (key) => {
|
|
138
|
+
try {
|
|
139
|
+
return await cache.get(key);
|
|
140
|
+
} catch (cause) {
|
|
141
|
+
const error = handleCacheError(cause, "get");
|
|
142
|
+
if (cacheErrorPolicy === "throw") throw error;
|
|
143
|
+
return void 0;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const setCached = async (key, value) => {
|
|
147
|
+
try {
|
|
148
|
+
await cache.set(key, value, options.ttlMs);
|
|
149
|
+
} catch (cause) {
|
|
150
|
+
const error = handleCacheError(cause, "set");
|
|
151
|
+
if (cacheErrorPolicy === "throw") throw error;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
119
154
|
const reportStats = () => {
|
|
120
155
|
const size = cache.size ?? 0;
|
|
121
156
|
options.onStatsReport?.({
|
|
@@ -131,18 +166,23 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
|
|
|
131
166
|
}
|
|
132
167
|
const target = requireLocale(targetLocale, "targetLocale");
|
|
133
168
|
const source = sourceLocale === void 0 ? "auto" : requireLocale(sourceLocale, "sourceLocale");
|
|
169
|
+
const automaticVariant = variantProvider?.getCacheVariant(target, sourceLocale);
|
|
170
|
+
if (automaticVariant !== void 0 && automaticVariant.trim().length === 0) {
|
|
171
|
+
throw new TypeError("baseAdapter cache variant must not be empty.");
|
|
172
|
+
}
|
|
173
|
+
const variant = options.variant === void 0 ? automaticVariant : automaticVariant === void 0 ? options.variant : `${options.variant}:${automaticVariant}`;
|
|
134
174
|
const keys = texts.map((text) => {
|
|
135
175
|
const context = {
|
|
136
176
|
sourceText: text,
|
|
137
177
|
sourceLocale: source,
|
|
138
178
|
targetLocale: target,
|
|
139
|
-
...
|
|
179
|
+
...variant === void 0 ? {} : { variant }
|
|
140
180
|
};
|
|
141
|
-
const key = options.buildKey?.(context) ?? `${prefix}:${adapterName}:${
|
|
181
|
+
const key = options.buildKey?.(context) ?? `${prefix}:${adapterName}:${variant === void 0 ? "" : `${variant}:`}${source}:${target}:${hashTranslationText(text)}`;
|
|
142
182
|
if (typeof key !== "string" || key.length === 0) throw new TypeError("Translation cache key must not be empty.");
|
|
143
183
|
return key;
|
|
144
184
|
});
|
|
145
|
-
const cached = await Promise.all(keys.map(
|
|
185
|
+
const cached = await Promise.all(keys.map(getCached));
|
|
146
186
|
hits += cached.filter((value) => value !== void 0).length;
|
|
147
187
|
misses += cached.filter((value) => value === void 0).length;
|
|
148
188
|
const missingByKey = /* @__PURE__ */ new Map();
|
|
@@ -169,7 +209,7 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
|
|
|
169
209
|
missing.map(async ([key, value], translatedIndex) => {
|
|
170
210
|
const translation = translated[translatedIndex];
|
|
171
211
|
if (translation === void 0) throw new Error("Translation adapter result is unavailable.");
|
|
172
|
-
await
|
|
212
|
+
await setCached(key, translation);
|
|
173
213
|
for (const index of value.indices) cached[index] = translation;
|
|
174
214
|
})
|
|
175
215
|
);
|
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
|
@@ -22,6 +22,11 @@ function nonNegativeDuration(value, fallback, name) {
|
|
|
22
22
|
}
|
|
23
23
|
return resolved;
|
|
24
24
|
}
|
|
25
|
+
function getVariantProvider(adapter) {
|
|
26
|
+
if (typeof adapter !== "object" || adapter === null) return void 0;
|
|
27
|
+
const candidate = adapter;
|
|
28
|
+
return typeof candidate.getCacheVariant === "function" ? { getCacheVariant: candidate.getCacheVariant.bind(adapter) } : void 0;
|
|
29
|
+
}
|
|
25
30
|
function createMemoryTranslationCache(options = {}) {
|
|
26
31
|
const maxEntries = options.maxEntries ?? 500;
|
|
27
32
|
if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
|
|
@@ -87,9 +92,39 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
|
|
|
87
92
|
if (options.buildKey !== void 0 && typeof options.buildKey !== "function") {
|
|
88
93
|
throw new TypeError("buildKey must be a function.");
|
|
89
94
|
}
|
|
95
|
+
if (options.cacheErrorPolicy !== void 0 && options.cacheErrorPolicy !== "bypass" && options.cacheErrorPolicy !== "throw") {
|
|
96
|
+
throw new TypeError('cacheErrorPolicy must be "bypass" or "throw".');
|
|
97
|
+
}
|
|
98
|
+
const cacheErrorPolicy = options.cacheErrorPolicy ?? "bypass";
|
|
90
99
|
const initialEvictionCount = cache.evictionCount ?? 0;
|
|
91
100
|
let hits = 0;
|
|
92
101
|
let misses = 0;
|
|
102
|
+
const variantProvider = getVariantProvider(baseAdapter);
|
|
103
|
+
const handleCacheError = (cause, operation) => {
|
|
104
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
105
|
+
try {
|
|
106
|
+
options.onCacheError?.(error, operation);
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
return error;
|
|
110
|
+
};
|
|
111
|
+
const getCached = async (key) => {
|
|
112
|
+
try {
|
|
113
|
+
return await cache.get(key);
|
|
114
|
+
} catch (cause) {
|
|
115
|
+
const error = handleCacheError(cause, "get");
|
|
116
|
+
if (cacheErrorPolicy === "throw") throw error;
|
|
117
|
+
return void 0;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const setCached = async (key, value) => {
|
|
121
|
+
try {
|
|
122
|
+
await cache.set(key, value, options.ttlMs);
|
|
123
|
+
} catch (cause) {
|
|
124
|
+
const error = handleCacheError(cause, "set");
|
|
125
|
+
if (cacheErrorPolicy === "throw") throw error;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
93
128
|
const reportStats = () => {
|
|
94
129
|
const size = cache.size ?? 0;
|
|
95
130
|
options.onStatsReport?.({
|
|
@@ -105,18 +140,23 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
|
|
|
105
140
|
}
|
|
106
141
|
const target = requireLocale(targetLocale, "targetLocale");
|
|
107
142
|
const source = sourceLocale === void 0 ? "auto" : requireLocale(sourceLocale, "sourceLocale");
|
|
143
|
+
const automaticVariant = variantProvider?.getCacheVariant(target, sourceLocale);
|
|
144
|
+
if (automaticVariant !== void 0 && automaticVariant.trim().length === 0) {
|
|
145
|
+
throw new TypeError("baseAdapter cache variant must not be empty.");
|
|
146
|
+
}
|
|
147
|
+
const variant = options.variant === void 0 ? automaticVariant : automaticVariant === void 0 ? options.variant : `${options.variant}:${automaticVariant}`;
|
|
108
148
|
const keys = texts.map((text) => {
|
|
109
149
|
const context = {
|
|
110
150
|
sourceText: text,
|
|
111
151
|
sourceLocale: source,
|
|
112
152
|
targetLocale: target,
|
|
113
|
-
...
|
|
153
|
+
...variant === void 0 ? {} : { variant }
|
|
114
154
|
};
|
|
115
|
-
const key = options.buildKey?.(context) ?? `${prefix}:${adapterName}:${
|
|
155
|
+
const key = options.buildKey?.(context) ?? `${prefix}:${adapterName}:${variant === void 0 ? "" : `${variant}:`}${source}:${target}:${hashTranslationText(text)}`;
|
|
116
156
|
if (typeof key !== "string" || key.length === 0) throw new TypeError("Translation cache key must not be empty.");
|
|
117
157
|
return key;
|
|
118
158
|
});
|
|
119
|
-
const cached = await Promise.all(keys.map(
|
|
159
|
+
const cached = await Promise.all(keys.map(getCached));
|
|
120
160
|
hits += cached.filter((value) => value !== void 0).length;
|
|
121
161
|
misses += cached.filter((value) => value === void 0).length;
|
|
122
162
|
const missingByKey = /* @__PURE__ */ new Map();
|
|
@@ -143,7 +183,7 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
|
|
|
143
183
|
missing.map(async ([key, value], translatedIndex) => {
|
|
144
184
|
const translation = translated[translatedIndex];
|
|
145
185
|
if (translation === void 0) throw new Error("Translation adapter result is unavailable.");
|
|
146
|
-
await
|
|
186
|
+
await setCached(key, translation);
|
|
147
187
|
for (const index of value.indices) cached[index] = translation;
|
|
148
188
|
})
|
|
149
189
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@form-engine-ts/translator-cache",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.1",
|
|
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.
|
|
41
|
+
"@form-engine-ts/core": "2.9.1"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean --external @form-engine-ts/core",
|