@form-engine-ts/translator-cache 2.6.0 → 2.7.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
@@ -12,13 +12,16 @@ pnpm add @form-engine-ts/core @form-engine-ts/translator-cache
12
12
  ## Usage
13
13
 
14
14
  ```ts
15
- import { withTranslationCache } from "@form-engine-ts/translator-cache";
15
+ import { createMemoryTranslationCache, withTranslationCache } from "@form-engine-ts/translator-cache";
16
16
 
17
- const translator = withTranslationCache(baseTranslator, cacheStorage, {
17
+ const cache = createMemoryTranslationCache({ maxEntries: 500, ttlMs: 5 * 60 * 1000 });
18
+ const translator = withTranslationCache(baseTranslator, cache, {
18
19
  ttlMs: 60 * 60 * 1000,
19
- keyPrefix: "survey-translations"
20
+ keyPrefix: "survey-translations",
21
+ adapterName: "google-v3"
20
22
  });
21
23
  ```
22
24
 
23
- Keys isolate source locale, target locale, and a deterministic UTF-8 hash of the source text. Batch misses are deduplicated,
24
- translated once in source order, cached, and restored to their original positions.
25
+ Keys isolate adapter name, source locale, target locale, and a deterministic UTF-8 hash of the source text. Batch misses
26
+ are deduplicated, translated once in source order, cached, and restored to their original positions. The built-in memory
27
+ cache applies both TTL expiration and bounded LRU eviction and exposes `size`, `evictionCount`, and `clear()` diagnostics.
package/dist/index.cjs CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ createMemoryTranslationCache: () => createMemoryTranslationCache,
23
24
  hashTranslationText: () => hashTranslationText,
24
25
  withTranslationCache: () => withTranslationCache
25
26
  });
@@ -40,6 +41,60 @@ function requireLocale(value, name) {
40
41
  if (typeof value !== "string" || value.trim().length === 0) throw new TypeError(`${name} must not be empty.`);
41
42
  return value.trim();
42
43
  }
44
+ function nonNegativeDuration(value, fallback, name) {
45
+ const resolved = value ?? fallback;
46
+ if (!Number.isSafeInteger(resolved) || resolved < 0) {
47
+ throw new TypeError(`${name} must be a non-negative safe integer.`);
48
+ }
49
+ return resolved;
50
+ }
51
+ function createMemoryTranslationCache(options = {}) {
52
+ const maxEntries = options.maxEntries ?? 500;
53
+ if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
54
+ throw new TypeError("maxEntries must be a positive safe integer.");
55
+ }
56
+ const defaultTtlMs = nonNegativeDuration(options.ttlMs, 5 * 60 * 1e3, "ttlMs");
57
+ const now = options.now ?? Date.now;
58
+ const entries = /* @__PURE__ */ new Map();
59
+ let evictionCount = 0;
60
+ const evictExpired = (key, entry) => {
61
+ if (entry.expiresAt > now()) return false;
62
+ entries.delete(key);
63
+ evictionCount += 1;
64
+ return true;
65
+ };
66
+ return {
67
+ get size() {
68
+ for (const [key, entry] of entries) evictExpired(key, entry);
69
+ return entries.size;
70
+ },
71
+ get evictionCount() {
72
+ return evictionCount;
73
+ },
74
+ get(key) {
75
+ const entry = entries.get(key);
76
+ if (entry === void 0 || evictExpired(key, entry)) return void 0;
77
+ entries.delete(key);
78
+ entries.set(key, entry);
79
+ return entry.value;
80
+ },
81
+ set(key, value, ttlMs) {
82
+ const resolvedTtlMs = nonNegativeDuration(ttlMs, defaultTtlMs, "ttlMs");
83
+ if (!entries.has(key) && entries.size >= maxEntries) {
84
+ const leastRecentlyUsed = entries.keys().next().value;
85
+ if (leastRecentlyUsed !== void 0) {
86
+ entries.delete(leastRecentlyUsed);
87
+ evictionCount += 1;
88
+ }
89
+ }
90
+ entries.delete(key);
91
+ entries.set(key, { value, expiresAt: now() + resolvedTtlMs });
92
+ },
93
+ clear() {
94
+ entries.clear();
95
+ }
96
+ };
97
+ }
43
98
  function withTranslationCache(baseAdapter, cache, options = {}) {
44
99
  if (typeof baseAdapter?.translateBatch !== "function") throw new TypeError("baseAdapter.translateBatch is required.");
45
100
  if (typeof cache?.get !== "function" || typeof cache.set !== "function") {
@@ -50,13 +105,15 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
50
105
  }
51
106
  const prefix = options.keyPrefix ?? "form-engine-ts";
52
107
  if (prefix.trim().length === 0) throw new TypeError("keyPrefix must not be empty.");
108
+ const adapterName = options.adapterName ?? "anonymous";
109
+ if (adapterName.trim().length === 0) throw new TypeError("adapterName must not be empty.");
53
110
  const translateBatch = async (texts, targetLocale, sourceLocale) => {
54
111
  if (!Array.isArray(texts) || texts.some((text) => typeof text !== "string")) {
55
112
  throw new TypeError("texts must be an array of strings.");
56
113
  }
57
114
  const target = requireLocale(targetLocale, "targetLocale");
58
115
  const source = sourceLocale === void 0 ? "auto" : requireLocale(sourceLocale, "sourceLocale");
59
- const keys = texts.map((text) => `${prefix}:${source}:${target}:${hashTranslationText(text)}`);
116
+ const keys = texts.map((text) => `${prefix}:${adapterName}:${source}:${target}:${hashTranslationText(text)}`);
60
117
  const cached = await Promise.all(keys.map((key) => cache.get(key)));
61
118
  const missingByKey = /* @__PURE__ */ new Map();
62
119
  for (let index = 0; index < texts.length; index += 1) {
@@ -104,6 +161,7 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
104
161
  }
105
162
  // Annotate the CommonJS export names for ESM import in node:
106
163
  0 && (module.exports = {
164
+ createMemoryTranslationCache,
107
165
  hashTranslationText,
108
166
  withTranslationCache
109
167
  });
package/dist/index.d.cts CHANGED
@@ -7,8 +7,20 @@ interface TranslationCacheStorage {
7
7
  interface TranslationCacheOptions {
8
8
  readonly ttlMs?: number;
9
9
  readonly keyPrefix?: string;
10
+ readonly adapterName?: string;
11
+ }
12
+ interface MemoryTranslationCacheOptions {
13
+ readonly maxEntries?: number;
14
+ readonly ttlMs?: number;
15
+ readonly now?: () => number;
16
+ }
17
+ interface MemoryTranslationCache extends TranslationCacheStorage {
18
+ readonly size: number;
19
+ readonly evictionCount: number;
20
+ clear(): void;
10
21
  }
11
22
  declare function hashTranslationText(text: string): string;
23
+ declare function createMemoryTranslationCache(options?: MemoryTranslationCacheOptions): MemoryTranslationCache;
12
24
  declare function withTranslationCache(baseAdapter: AsyncTranslationAdapter, cache: TranslationCacheStorage, options?: TranslationCacheOptions): AsyncTranslationAdapter;
13
25
 
14
- export { type TranslationCacheOptions, type TranslationCacheStorage, hashTranslationText, withTranslationCache };
26
+ export { type MemoryTranslationCache, type MemoryTranslationCacheOptions, type TranslationCacheOptions, type TranslationCacheStorage, createMemoryTranslationCache, hashTranslationText, withTranslationCache };
package/dist/index.d.ts CHANGED
@@ -7,8 +7,20 @@ interface TranslationCacheStorage {
7
7
  interface TranslationCacheOptions {
8
8
  readonly ttlMs?: number;
9
9
  readonly keyPrefix?: string;
10
+ readonly adapterName?: string;
11
+ }
12
+ interface MemoryTranslationCacheOptions {
13
+ readonly maxEntries?: number;
14
+ readonly ttlMs?: number;
15
+ readonly now?: () => number;
16
+ }
17
+ interface MemoryTranslationCache extends TranslationCacheStorage {
18
+ readonly size: number;
19
+ readonly evictionCount: number;
20
+ clear(): void;
10
21
  }
11
22
  declare function hashTranslationText(text: string): string;
23
+ declare function createMemoryTranslationCache(options?: MemoryTranslationCacheOptions): MemoryTranslationCache;
12
24
  declare function withTranslationCache(baseAdapter: AsyncTranslationAdapter, cache: TranslationCacheStorage, options?: TranslationCacheOptions): AsyncTranslationAdapter;
13
25
 
14
- export { type TranslationCacheOptions, type TranslationCacheStorage, hashTranslationText, withTranslationCache };
26
+ export { type MemoryTranslationCache, type MemoryTranslationCacheOptions, type TranslationCacheOptions, type TranslationCacheStorage, createMemoryTranslationCache, hashTranslationText, withTranslationCache };
package/dist/index.js CHANGED
@@ -15,6 +15,60 @@ function requireLocale(value, name) {
15
15
  if (typeof value !== "string" || value.trim().length === 0) throw new TypeError(`${name} must not be empty.`);
16
16
  return value.trim();
17
17
  }
18
+ function nonNegativeDuration(value, fallback, name) {
19
+ const resolved = value ?? fallback;
20
+ if (!Number.isSafeInteger(resolved) || resolved < 0) {
21
+ throw new TypeError(`${name} must be a non-negative safe integer.`);
22
+ }
23
+ return resolved;
24
+ }
25
+ function createMemoryTranslationCache(options = {}) {
26
+ const maxEntries = options.maxEntries ?? 500;
27
+ if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
28
+ throw new TypeError("maxEntries must be a positive safe integer.");
29
+ }
30
+ const defaultTtlMs = nonNegativeDuration(options.ttlMs, 5 * 60 * 1e3, "ttlMs");
31
+ const now = options.now ?? Date.now;
32
+ const entries = /* @__PURE__ */ new Map();
33
+ let evictionCount = 0;
34
+ const evictExpired = (key, entry) => {
35
+ if (entry.expiresAt > now()) return false;
36
+ entries.delete(key);
37
+ evictionCount += 1;
38
+ return true;
39
+ };
40
+ return {
41
+ get size() {
42
+ for (const [key, entry] of entries) evictExpired(key, entry);
43
+ return entries.size;
44
+ },
45
+ get evictionCount() {
46
+ return evictionCount;
47
+ },
48
+ get(key) {
49
+ const entry = entries.get(key);
50
+ if (entry === void 0 || evictExpired(key, entry)) return void 0;
51
+ entries.delete(key);
52
+ entries.set(key, entry);
53
+ return entry.value;
54
+ },
55
+ set(key, value, ttlMs) {
56
+ const resolvedTtlMs = nonNegativeDuration(ttlMs, defaultTtlMs, "ttlMs");
57
+ if (!entries.has(key) && entries.size >= maxEntries) {
58
+ const leastRecentlyUsed = entries.keys().next().value;
59
+ if (leastRecentlyUsed !== void 0) {
60
+ entries.delete(leastRecentlyUsed);
61
+ evictionCount += 1;
62
+ }
63
+ }
64
+ entries.delete(key);
65
+ entries.set(key, { value, expiresAt: now() + resolvedTtlMs });
66
+ },
67
+ clear() {
68
+ entries.clear();
69
+ }
70
+ };
71
+ }
18
72
  function withTranslationCache(baseAdapter, cache, options = {}) {
19
73
  if (typeof baseAdapter?.translateBatch !== "function") throw new TypeError("baseAdapter.translateBatch is required.");
20
74
  if (typeof cache?.get !== "function" || typeof cache.set !== "function") {
@@ -25,13 +79,15 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
25
79
  }
26
80
  const prefix = options.keyPrefix ?? "form-engine-ts";
27
81
  if (prefix.trim().length === 0) throw new TypeError("keyPrefix must not be empty.");
82
+ const adapterName = options.adapterName ?? "anonymous";
83
+ if (adapterName.trim().length === 0) throw new TypeError("adapterName must not be empty.");
28
84
  const translateBatch = async (texts, targetLocale, sourceLocale) => {
29
85
  if (!Array.isArray(texts) || texts.some((text) => typeof text !== "string")) {
30
86
  throw new TypeError("texts must be an array of strings.");
31
87
  }
32
88
  const target = requireLocale(targetLocale, "targetLocale");
33
89
  const source = sourceLocale === void 0 ? "auto" : requireLocale(sourceLocale, "sourceLocale");
34
- const keys = texts.map((text) => `${prefix}:${source}:${target}:${hashTranslationText(text)}`);
90
+ const keys = texts.map((text) => `${prefix}:${adapterName}:${source}:${target}:${hashTranslationText(text)}`);
35
91
  const cached = await Promise.all(keys.map((key) => cache.get(key)));
36
92
  const missingByKey = /* @__PURE__ */ new Map();
37
93
  for (let index = 0; index < texts.length; index += 1) {
@@ -78,6 +134,7 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
78
134
  };
79
135
  }
80
136
  export {
137
+ createMemoryTranslationCache,
81
138
  hashTranslationText,
82
139
  withTranslationCache
83
140
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/translator-cache",
3
- "version": "2.6.0",
3
+ "version": "2.7.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.6.0"
41
+ "@form-engine-ts/core": "2.7.0"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsup src/index.ts --format esm,cjs --dts --clean --external @form-engine-ts/core",