@mulmoclaude/core 0.2.14 → 0.2.15

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.
@@ -0,0 +1,45 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/translation/client.ts
3
+ function cacheKey(req) {
4
+ return [
5
+ req.namespace,
6
+ req.targetLanguage,
7
+ ...req.sentences
8
+ ].join("\0");
9
+ }
10
+ function createTranslationCache(transport) {
11
+ const memo = /* @__PURE__ */ new Map();
12
+ const inflight = /* @__PURE__ */ new Map();
13
+ async function run(key, req) {
14
+ const translations = (await transport(req))?.translations;
15
+ if (!Array.isArray(translations) || translations.length !== req.sentences.length) return null;
16
+ const frozen = Object.freeze([...translations]);
17
+ memo.set(key, frozen);
18
+ return frozen;
19
+ }
20
+ return {
21
+ peek(req) {
22
+ return memo.get(cacheKey(req)) ?? null;
23
+ },
24
+ fetch(req) {
25
+ const key = cacheKey(req);
26
+ const hit = memo.get(key);
27
+ if (hit) return Promise.resolve(hit);
28
+ const pending = inflight.get(key);
29
+ if (pending) return pending;
30
+ const started = run(key, req).finally(() => {
31
+ inflight.delete(key);
32
+ });
33
+ inflight.set(key, started);
34
+ return started;
35
+ },
36
+ clear() {
37
+ memo.clear();
38
+ inflight.clear();
39
+ }
40
+ };
41
+ }
42
+ //#endregion
43
+ exports.createTranslationCache = createTranslationCache;
44
+
45
+ //# sourceMappingURL=client.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.cjs","names":[],"sources":["../../src/translation/client.ts"],"sourcesContent":["// @mulmoclaude/core/translation/client — framework-neutral runtime-translation\n// cache. Both hosts (MulmoClaude, MulmoTerminal) translate UI strings (role\n// query chips, collection starters, …) into the user's locale at runtime by\n// POSTing batches to their own `/api/translation` route; the LLM step and the\n// transport/auth differ per host, so BOTH are injected. This module owns only\n// the host-agnostic parts: the request/response contract, in-flight de-dup,\n// length validation, and an in-memory memo keyed by (namespace, locale,\n// sentence-set). No framework dependency — the host wraps this in its own\n// reactivity (Vue refs, …), mirroring `@mulmoclaude/core/whisper/client`.\n//\n// `en` and empty inputs are NOT special-cased here — that's a UI fallback\n// decision the caller makes (it already holds the English source).\n\nexport interface TranslateRequest {\n /** Cache + server-cache partition, e.g. \"role-queries\", \"collection-starters\". */\n namespace: string;\n /** BCP-47 target language, e.g. \"ja\", \"pt-BR\". */\n targetLanguage: string;\n /** English source strings to translate, in order. */\n sentences: readonly string[];\n}\n\nexport interface TranslateResponse {\n /** Translations in the same order and length as `sentences`. */\n translations: string[];\n}\n\n/** Host-injected transport. Performs the POST however the host likes (auth,\n * URL, error shape are the host's concern) and resolves to the response, or\n * `null` on any network / HTTP failure — the cache treats `null` as a miss and\n * the caller falls back to the English source. */\nexport type TranslateTransport = (req: TranslateRequest) => Promise<TranslateResponse | null>;\n\nexport interface TranslationCache {\n /** Memoized translations for this exact request, or `null` if not resolved\n * yet (never fetched, in flight, or the last attempt failed). Synchronous —\n * a reactive caller reads this after being nudged by `fetch`'s resolution. */\n peek: (req: TranslateRequest) => readonly string[] | null;\n /** Ensure a translation is fetched (de-duped across concurrent callers and\n * memoized). Resolves to the translations, or `null` on transport failure or\n * a length mismatch. Repeat calls with the same key return the memoized\n * value without re-hitting the transport. */\n fetch: (req: TranslateRequest) => Promise<readonly string[] | null>;\n /** Drop all memoized + in-flight state. Primarily for tests that share a\n * worker; production callers never need it. */\n clear: () => void;\n}\n\nfunction cacheKey(req: TranslateRequest): string {\n // Join the parts with a NUL separator (written as an escaped NUL so the\n // SOURCE stays plain text — a raw NUL byte here made Git classify the file as\n // binary and blocked diff review). NUL can't occur in any part, so two\n // different (namespace, locale, sentence-set) tuples can never collide. The\n // sentence set is part of the key (not just namespace+locale) so callers\n // needn't supply a stable id — identical inputs share a slot, which is correct.\n return [req.namespace, req.targetLanguage, ...req.sentences].join(\"\\u0000\");\n}\n\nexport function createTranslationCache(transport: TranslateTransport): TranslationCache {\n const memo = new Map<string, readonly string[]>();\n const inflight = new Map<string, Promise<readonly string[] | null>>();\n\n async function run(key: string, req: TranslateRequest): Promise<readonly string[] | null> {\n const response = await transport(req);\n const translations = response?.translations;\n if (!Array.isArray(translations) || translations.length !== req.sentences.length) {\n return null;\n }\n const frozen = Object.freeze([...translations]);\n memo.set(key, frozen);\n return frozen;\n }\n\n return {\n peek(req) {\n return memo.get(cacheKey(req)) ?? null;\n },\n fetch(req) {\n const key = cacheKey(req);\n const hit = memo.get(key);\n if (hit) return Promise.resolve(hit);\n const pending = inflight.get(key);\n if (pending) return pending;\n const started = run(key, req).finally(() => {\n inflight.delete(key);\n });\n inflight.set(key, started);\n return started;\n },\n clear() {\n memo.clear();\n inflight.clear();\n },\n };\n}\n"],"mappings":";;AAgDA,SAAS,SAAS,KAA+B;CAO/C,OAAO;EAAC,IAAI;EAAW,IAAI;EAAgB,GAAG,IAAI;CAAS,CAAC,CAAC,KAAK,IAAQ;AAC5E;AAEA,SAAgB,uBAAuB,WAAiD;CACtF,MAAM,uBAAO,IAAI,IAA+B;CAChD,MAAM,2BAAW,IAAI,IAA+C;CAEpE,eAAe,IAAI,KAAa,KAA0D;EAExF,MAAM,gBAAe,MADE,UAAU,GAAG,EAAA,EACL;EAC/B,IAAI,CAAC,MAAM,QAAQ,YAAY,KAAK,aAAa,WAAW,IAAI,UAAU,QACxE,OAAO;EAET,MAAM,SAAS,OAAO,OAAO,CAAC,GAAG,YAAY,CAAC;EAC9C,KAAK,IAAI,KAAK,MAAM;EACpB,OAAO;CACT;CAEA,OAAO;EACL,KAAK,KAAK;GACR,OAAO,KAAK,IAAI,SAAS,GAAG,CAAC,KAAK;EACpC;EACA,MAAM,KAAK;GACT,MAAM,MAAM,SAAS,GAAG;GACxB,MAAM,MAAM,KAAK,IAAI,GAAG;GACxB,IAAI,KAAK,OAAO,QAAQ,QAAQ,GAAG;GACnC,MAAM,UAAU,SAAS,IAAI,GAAG;GAChC,IAAI,SAAS,OAAO;GACpB,MAAM,UAAU,IAAI,KAAK,GAAG,CAAC,CAAC,cAAc;IAC1C,SAAS,OAAO,GAAG;GACrB,CAAC;GACD,SAAS,IAAI,KAAK,OAAO;GACzB,OAAO;EACT;EACA,QAAQ;GACN,KAAK,MAAM;GACX,SAAS,MAAM;EACjB;CACF;AACF"}
@@ -0,0 +1,32 @@
1
+ export interface TranslateRequest {
2
+ /** Cache + server-cache partition, e.g. "role-queries", "collection-starters". */
3
+ namespace: string;
4
+ /** BCP-47 target language, e.g. "ja", "pt-BR". */
5
+ targetLanguage: string;
6
+ /** English source strings to translate, in order. */
7
+ sentences: readonly string[];
8
+ }
9
+ export interface TranslateResponse {
10
+ /** Translations in the same order and length as `sentences`. */
11
+ translations: string[];
12
+ }
13
+ /** Host-injected transport. Performs the POST however the host likes (auth,
14
+ * URL, error shape are the host's concern) and resolves to the response, or
15
+ * `null` on any network / HTTP failure — the cache treats `null` as a miss and
16
+ * the caller falls back to the English source. */
17
+ export type TranslateTransport = (req: TranslateRequest) => Promise<TranslateResponse | null>;
18
+ export interface TranslationCache {
19
+ /** Memoized translations for this exact request, or `null` if not resolved
20
+ * yet (never fetched, in flight, or the last attempt failed). Synchronous —
21
+ * a reactive caller reads this after being nudged by `fetch`'s resolution. */
22
+ peek: (req: TranslateRequest) => readonly string[] | null;
23
+ /** Ensure a translation is fetched (de-duped across concurrent callers and
24
+ * memoized). Resolves to the translations, or `null` on transport failure or
25
+ * a length mismatch. Repeat calls with the same key return the memoized
26
+ * value without re-hitting the transport. */
27
+ fetch: (req: TranslateRequest) => Promise<readonly string[] | null>;
28
+ /** Drop all memoized + in-flight state. Primarily for tests that share a
29
+ * worker; production callers never need it. */
30
+ clear: () => void;
31
+ }
32
+ export declare function createTranslationCache(transport: TranslateTransport): TranslationCache;
@@ -0,0 +1,44 @@
1
+ //#region src/translation/client.ts
2
+ function cacheKey(req) {
3
+ return [
4
+ req.namespace,
5
+ req.targetLanguage,
6
+ ...req.sentences
7
+ ].join("\0");
8
+ }
9
+ function createTranslationCache(transport) {
10
+ const memo = /* @__PURE__ */ new Map();
11
+ const inflight = /* @__PURE__ */ new Map();
12
+ async function run(key, req) {
13
+ const translations = (await transport(req))?.translations;
14
+ if (!Array.isArray(translations) || translations.length !== req.sentences.length) return null;
15
+ const frozen = Object.freeze([...translations]);
16
+ memo.set(key, frozen);
17
+ return frozen;
18
+ }
19
+ return {
20
+ peek(req) {
21
+ return memo.get(cacheKey(req)) ?? null;
22
+ },
23
+ fetch(req) {
24
+ const key = cacheKey(req);
25
+ const hit = memo.get(key);
26
+ if (hit) return Promise.resolve(hit);
27
+ const pending = inflight.get(key);
28
+ if (pending) return pending;
29
+ const started = run(key, req).finally(() => {
30
+ inflight.delete(key);
31
+ });
32
+ inflight.set(key, started);
33
+ return started;
34
+ },
35
+ clear() {
36
+ memo.clear();
37
+ inflight.clear();
38
+ }
39
+ };
40
+ }
41
+ //#endregion
42
+ export { createTranslationCache };
43
+
44
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../src/translation/client.ts"],"sourcesContent":["// @mulmoclaude/core/translation/client — framework-neutral runtime-translation\n// cache. Both hosts (MulmoClaude, MulmoTerminal) translate UI strings (role\n// query chips, collection starters, …) into the user's locale at runtime by\n// POSTing batches to their own `/api/translation` route; the LLM step and the\n// transport/auth differ per host, so BOTH are injected. This module owns only\n// the host-agnostic parts: the request/response contract, in-flight de-dup,\n// length validation, and an in-memory memo keyed by (namespace, locale,\n// sentence-set). No framework dependency — the host wraps this in its own\n// reactivity (Vue refs, …), mirroring `@mulmoclaude/core/whisper/client`.\n//\n// `en` and empty inputs are NOT special-cased here — that's a UI fallback\n// decision the caller makes (it already holds the English source).\n\nexport interface TranslateRequest {\n /** Cache + server-cache partition, e.g. \"role-queries\", \"collection-starters\". */\n namespace: string;\n /** BCP-47 target language, e.g. \"ja\", \"pt-BR\". */\n targetLanguage: string;\n /** English source strings to translate, in order. */\n sentences: readonly string[];\n}\n\nexport interface TranslateResponse {\n /** Translations in the same order and length as `sentences`. */\n translations: string[];\n}\n\n/** Host-injected transport. Performs the POST however the host likes (auth,\n * URL, error shape are the host's concern) and resolves to the response, or\n * `null` on any network / HTTP failure — the cache treats `null` as a miss and\n * the caller falls back to the English source. */\nexport type TranslateTransport = (req: TranslateRequest) => Promise<TranslateResponse | null>;\n\nexport interface TranslationCache {\n /** Memoized translations for this exact request, or `null` if not resolved\n * yet (never fetched, in flight, or the last attempt failed). Synchronous —\n * a reactive caller reads this after being nudged by `fetch`'s resolution. */\n peek: (req: TranslateRequest) => readonly string[] | null;\n /** Ensure a translation is fetched (de-duped across concurrent callers and\n * memoized). Resolves to the translations, or `null` on transport failure or\n * a length mismatch. Repeat calls with the same key return the memoized\n * value without re-hitting the transport. */\n fetch: (req: TranslateRequest) => Promise<readonly string[] | null>;\n /** Drop all memoized + in-flight state. Primarily for tests that share a\n * worker; production callers never need it. */\n clear: () => void;\n}\n\nfunction cacheKey(req: TranslateRequest): string {\n // Join the parts with a NUL separator (written as an escaped NUL so the\n // SOURCE stays plain text — a raw NUL byte here made Git classify the file as\n // binary and blocked diff review). NUL can't occur in any part, so two\n // different (namespace, locale, sentence-set) tuples can never collide. The\n // sentence set is part of the key (not just namespace+locale) so callers\n // needn't supply a stable id — identical inputs share a slot, which is correct.\n return [req.namespace, req.targetLanguage, ...req.sentences].join(\"\\u0000\");\n}\n\nexport function createTranslationCache(transport: TranslateTransport): TranslationCache {\n const memo = new Map<string, readonly string[]>();\n const inflight = new Map<string, Promise<readonly string[] | null>>();\n\n async function run(key: string, req: TranslateRequest): Promise<readonly string[] | null> {\n const response = await transport(req);\n const translations = response?.translations;\n if (!Array.isArray(translations) || translations.length !== req.sentences.length) {\n return null;\n }\n const frozen = Object.freeze([...translations]);\n memo.set(key, frozen);\n return frozen;\n }\n\n return {\n peek(req) {\n return memo.get(cacheKey(req)) ?? null;\n },\n fetch(req) {\n const key = cacheKey(req);\n const hit = memo.get(key);\n if (hit) return Promise.resolve(hit);\n const pending = inflight.get(key);\n if (pending) return pending;\n const started = run(key, req).finally(() => {\n inflight.delete(key);\n });\n inflight.set(key, started);\n return started;\n },\n clear() {\n memo.clear();\n inflight.clear();\n },\n };\n}\n"],"mappings":";AAgDA,SAAS,SAAS,KAA+B;CAO/C,OAAO;EAAC,IAAI;EAAW,IAAI;EAAgB,GAAG,IAAI;CAAS,CAAC,CAAC,KAAK,IAAQ;AAC5E;AAEA,SAAgB,uBAAuB,WAAiD;CACtF,MAAM,uBAAO,IAAI,IAA+B;CAChD,MAAM,2BAAW,IAAI,IAA+C;CAEpE,eAAe,IAAI,KAAa,KAA0D;EAExF,MAAM,gBAAe,MADE,UAAU,GAAG,EAAA,EACL;EAC/B,IAAI,CAAC,MAAM,QAAQ,YAAY,KAAK,aAAa,WAAW,IAAI,UAAU,QACxE,OAAO;EAET,MAAM,SAAS,OAAO,OAAO,CAAC,GAAG,YAAY,CAAC;EAC9C,KAAK,IAAI,KAAK,MAAM;EACpB,OAAO;CACT;CAEA,OAAO;EACL,KAAK,KAAK;GACR,OAAO,KAAK,IAAI,SAAS,GAAG,CAAC,KAAK;EACpC;EACA,MAAM,KAAK;GACT,MAAM,MAAM,SAAS,GAAG;GACxB,MAAM,MAAM,KAAK,IAAI,GAAG;GACxB,IAAI,KAAK,OAAO,QAAQ,QAAQ,GAAG;GACnC,MAAM,UAAU,SAAS,IAAI,GAAG;GAChC,IAAI,SAAS,OAAO;GACpB,MAAM,UAAU,IAAI,KAAK,GAAG,CAAC,CAAC,cAAc;IAC1C,SAAS,OAAO,GAAG;GACrB,CAAC;GACD,SAAS,IAAI,KAAK,OAAO;GACzB,OAAO;EACT;EACA,QAAQ;GACN,KAAK,MAAM;GACX,SAAS,MAAM;EACjB;CACF;AACF"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mulmoclaude/core",
3
- "version": "0.2.14",
4
- "description": "Shared server-side core for MulmoClaude and MulmoTerminal — the always-shipped-together subsystems consolidated behind subpath exports so the two hosts can't drift. Server-only except the browser-safe ./whisper/client and ./workspace-setup/slug entries. All host specifics are injected.",
3
+ "version": "0.2.15",
4
+ "description": "Shared server-side core for MulmoClaude and MulmoTerminal — the always-shipped-together subsystems consolidated behind subpath exports so the two hosts can't drift. Server-only except the browser-safe ./whisper/client, ./workspace-setup/slug and ./translation/client entries. All host specifics are injected.",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  "./collection": {
@@ -82,6 +82,12 @@
82
82
  "require": "./dist/whisper/client.cjs",
83
83
  "default": "./dist/whisper/client.js"
84
84
  },
85
+ "./translation/client": {
86
+ "types": "./dist/translation/client.d.ts",
87
+ "import": "./dist/translation/client.js",
88
+ "require": "./dist/translation/client.cjs",
89
+ "default": "./dist/translation/client.js"
90
+ },
85
91
  "./workspace-setup": {
86
92
  "types": "./dist/workspace-setup/index.d.ts",
87
93
  "import": "./dist/workspace-setup/index.js",