@form-engine-ts/translator-cache 2.7.0 → 2.8.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
@@ -25,3 +25,5 @@ const translator = withTranslationCache(baseTranslator, cache, {
25
25
  Keys isolate adapter name, source locale, target locale, and a deterministic UTF-8 hash of the source text. Batch misses
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
+ Use `variant` to isolate glossary/model/configuration revisions, or `buildKey` for complete key control.
29
+ `onStatsReport` receives cumulative real cache hits, misses, evictions, and current size after successful translations.
package/dist/index.cjs CHANGED
@@ -107,14 +107,44 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
107
107
  if (prefix.trim().length === 0) throw new TypeError("keyPrefix must not be empty.");
108
108
  const adapterName = options.adapterName ?? "anonymous";
109
109
  if (adapterName.trim().length === 0) throw new TypeError("adapterName must not be empty.");
110
+ if (options.variant !== void 0 && options.variant.trim().length === 0) {
111
+ throw new TypeError("variant must not be empty.");
112
+ }
113
+ if (options.buildKey !== void 0 && typeof options.buildKey !== "function") {
114
+ throw new TypeError("buildKey must be a function.");
115
+ }
116
+ const initialEvictionCount = cache.evictionCount ?? 0;
117
+ let hits = 0;
118
+ let misses = 0;
119
+ const reportStats = () => {
120
+ const size = cache.size ?? 0;
121
+ options.onStatsReport?.({
122
+ hits,
123
+ misses,
124
+ evictions: Math.max(0, (cache.evictionCount ?? initialEvictionCount) - initialEvictionCount),
125
+ size
126
+ });
127
+ };
110
128
  const translateBatch = async (texts, targetLocale, sourceLocale) => {
111
129
  if (!Array.isArray(texts) || texts.some((text) => typeof text !== "string")) {
112
130
  throw new TypeError("texts must be an array of strings.");
113
131
  }
114
132
  const target = requireLocale(targetLocale, "targetLocale");
115
133
  const source = sourceLocale === void 0 ? "auto" : requireLocale(sourceLocale, "sourceLocale");
116
- const keys = texts.map((text) => `${prefix}:${adapterName}:${source}:${target}:${hashTranslationText(text)}`);
134
+ const keys = texts.map((text) => {
135
+ const context = {
136
+ sourceText: text,
137
+ sourceLocale: source,
138
+ targetLocale: target,
139
+ ...options.variant === void 0 ? {} : { variant: options.variant }
140
+ };
141
+ const key = options.buildKey?.(context) ?? `${prefix}:${adapterName}:${options.variant === void 0 ? "" : `${options.variant}:`}${source}:${target}:${hashTranslationText(text)}`;
142
+ if (typeof key !== "string" || key.length === 0) throw new TypeError("Translation cache key must not be empty.");
143
+ return key;
144
+ });
117
145
  const cached = await Promise.all(keys.map((key) => cache.get(key)));
146
+ hits += cached.filter((value) => value !== void 0).length;
147
+ misses += cached.filter((value) => value === void 0).length;
118
148
  const missingByKey = /* @__PURE__ */ new Map();
119
149
  for (let index = 0; index < texts.length; index += 1) {
120
150
  if (cached[index] !== void 0) continue;
@@ -144,10 +174,12 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
144
174
  })
145
175
  );
146
176
  }
147
- return cached.map((value) => {
177
+ const result = cached.map((value) => {
148
178
  if (value === void 0) throw new Error("Translation cache result is unavailable.");
149
179
  return value;
150
180
  });
181
+ reportStats();
182
+ return result;
151
183
  };
152
184
  return {
153
185
  async translateText(text, targetLocale, sourceLocale) {
package/dist/index.d.cts CHANGED
@@ -1,13 +1,30 @@
1
1
  import { AsyncTranslationAdapter } from '@form-engine-ts/core';
2
2
 
3
3
  interface TranslationCacheStorage {
4
+ readonly size?: number;
5
+ readonly evictionCount?: number;
4
6
  get(key: string): Promise<string | undefined> | string | undefined;
5
7
  set(key: string, value: string, ttlMs?: number): Promise<void> | void;
6
8
  }
9
+ interface TranslationCacheKeyContext {
10
+ readonly sourceText: string;
11
+ readonly sourceLocale: string;
12
+ readonly targetLocale: string;
13
+ readonly variant?: string;
14
+ }
15
+ interface TranslationCacheStats {
16
+ readonly hits: number;
17
+ readonly misses: number;
18
+ readonly evictions: number;
19
+ readonly size: number;
20
+ }
7
21
  interface TranslationCacheOptions {
8
22
  readonly ttlMs?: number;
9
23
  readonly keyPrefix?: string;
10
24
  readonly adapterName?: string;
25
+ readonly variant?: string;
26
+ readonly buildKey?: (context: TranslationCacheKeyContext) => string;
27
+ readonly onStatsReport?: (stats: TranslationCacheStats) => void;
11
28
  }
12
29
  interface MemoryTranslationCacheOptions {
13
30
  readonly maxEntries?: number;
@@ -23,4 +40,4 @@ declare function hashTranslationText(text: string): string;
23
40
  declare function createMemoryTranslationCache(options?: MemoryTranslationCacheOptions): MemoryTranslationCache;
24
41
  declare function withTranslationCache(baseAdapter: AsyncTranslationAdapter, cache: TranslationCacheStorage, options?: TranslationCacheOptions): AsyncTranslationAdapter;
25
42
 
26
- export { type MemoryTranslationCache, type MemoryTranslationCacheOptions, type TranslationCacheOptions, type TranslationCacheStorage, createMemoryTranslationCache, hashTranslationText, withTranslationCache };
43
+ export { type MemoryTranslationCache, type MemoryTranslationCacheOptions, type TranslationCacheKeyContext, type TranslationCacheOptions, type TranslationCacheStats, type TranslationCacheStorage, createMemoryTranslationCache, hashTranslationText, withTranslationCache };
package/dist/index.d.ts CHANGED
@@ -1,13 +1,30 @@
1
1
  import { AsyncTranslationAdapter } from '@form-engine-ts/core';
2
2
 
3
3
  interface TranslationCacheStorage {
4
+ readonly size?: number;
5
+ readonly evictionCount?: number;
4
6
  get(key: string): Promise<string | undefined> | string | undefined;
5
7
  set(key: string, value: string, ttlMs?: number): Promise<void> | void;
6
8
  }
9
+ interface TranslationCacheKeyContext {
10
+ readonly sourceText: string;
11
+ readonly sourceLocale: string;
12
+ readonly targetLocale: string;
13
+ readonly variant?: string;
14
+ }
15
+ interface TranslationCacheStats {
16
+ readonly hits: number;
17
+ readonly misses: number;
18
+ readonly evictions: number;
19
+ readonly size: number;
20
+ }
7
21
  interface TranslationCacheOptions {
8
22
  readonly ttlMs?: number;
9
23
  readonly keyPrefix?: string;
10
24
  readonly adapterName?: string;
25
+ readonly variant?: string;
26
+ readonly buildKey?: (context: TranslationCacheKeyContext) => string;
27
+ readonly onStatsReport?: (stats: TranslationCacheStats) => void;
11
28
  }
12
29
  interface MemoryTranslationCacheOptions {
13
30
  readonly maxEntries?: number;
@@ -23,4 +40,4 @@ declare function hashTranslationText(text: string): string;
23
40
  declare function createMemoryTranslationCache(options?: MemoryTranslationCacheOptions): MemoryTranslationCache;
24
41
  declare function withTranslationCache(baseAdapter: AsyncTranslationAdapter, cache: TranslationCacheStorage, options?: TranslationCacheOptions): AsyncTranslationAdapter;
25
42
 
26
- export { type MemoryTranslationCache, type MemoryTranslationCacheOptions, type TranslationCacheOptions, type TranslationCacheStorage, createMemoryTranslationCache, hashTranslationText, withTranslationCache };
43
+ export { type MemoryTranslationCache, type MemoryTranslationCacheOptions, type TranslationCacheKeyContext, type TranslationCacheOptions, type TranslationCacheStats, type TranslationCacheStorage, createMemoryTranslationCache, hashTranslationText, withTranslationCache };
package/dist/index.js CHANGED
@@ -81,14 +81,44 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
81
81
  if (prefix.trim().length === 0) throw new TypeError("keyPrefix must not be empty.");
82
82
  const adapterName = options.adapterName ?? "anonymous";
83
83
  if (adapterName.trim().length === 0) throw new TypeError("adapterName must not be empty.");
84
+ if (options.variant !== void 0 && options.variant.trim().length === 0) {
85
+ throw new TypeError("variant must not be empty.");
86
+ }
87
+ if (options.buildKey !== void 0 && typeof options.buildKey !== "function") {
88
+ throw new TypeError("buildKey must be a function.");
89
+ }
90
+ const initialEvictionCount = cache.evictionCount ?? 0;
91
+ let hits = 0;
92
+ let misses = 0;
93
+ const reportStats = () => {
94
+ const size = cache.size ?? 0;
95
+ options.onStatsReport?.({
96
+ hits,
97
+ misses,
98
+ evictions: Math.max(0, (cache.evictionCount ?? initialEvictionCount) - initialEvictionCount),
99
+ size
100
+ });
101
+ };
84
102
  const translateBatch = async (texts, targetLocale, sourceLocale) => {
85
103
  if (!Array.isArray(texts) || texts.some((text) => typeof text !== "string")) {
86
104
  throw new TypeError("texts must be an array of strings.");
87
105
  }
88
106
  const target = requireLocale(targetLocale, "targetLocale");
89
107
  const source = sourceLocale === void 0 ? "auto" : requireLocale(sourceLocale, "sourceLocale");
90
- const keys = texts.map((text) => `${prefix}:${adapterName}:${source}:${target}:${hashTranslationText(text)}`);
108
+ const keys = texts.map((text) => {
109
+ const context = {
110
+ sourceText: text,
111
+ sourceLocale: source,
112
+ targetLocale: target,
113
+ ...options.variant === void 0 ? {} : { variant: options.variant }
114
+ };
115
+ const key = options.buildKey?.(context) ?? `${prefix}:${adapterName}:${options.variant === void 0 ? "" : `${options.variant}:`}${source}:${target}:${hashTranslationText(text)}`;
116
+ if (typeof key !== "string" || key.length === 0) throw new TypeError("Translation cache key must not be empty.");
117
+ return key;
118
+ });
91
119
  const cached = await Promise.all(keys.map((key) => cache.get(key)));
120
+ hits += cached.filter((value) => value !== void 0).length;
121
+ misses += cached.filter((value) => value === void 0).length;
92
122
  const missingByKey = /* @__PURE__ */ new Map();
93
123
  for (let index = 0; index < texts.length; index += 1) {
94
124
  if (cached[index] !== void 0) continue;
@@ -118,10 +148,12 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
118
148
  })
119
149
  );
120
150
  }
121
- return cached.map((value) => {
151
+ const result = cached.map((value) => {
122
152
  if (value === void 0) throw new Error("Translation cache result is unavailable.");
123
153
  return value;
124
154
  });
155
+ reportStats();
156
+ return result;
125
157
  };
126
158
  return {
127
159
  async translateText(text, targetLocale, sourceLocale) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/translator-cache",
3
- "version": "2.7.0",
3
+ "version": "2.8.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.7.0"
41
+ "@form-engine-ts/core": "2.8.0"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsup src/index.ts --format esm,cjs --dts --clean --external @form-engine-ts/core",