@absolutejs/voice 0.0.22-beta.575 → 0.0.22-beta.577

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.
@@ -1,4 +1,21 @@
1
- import type { TTSAdapter, TTSAdapterOpenOptions } from "./types";
1
+ import type { TTSAdapter, TTSAdapterOpenOptions, TTSAudioEvent } from "./types";
2
+ /**
3
+ * Optional persistent backing store for the cache — an L2 behind the in-memory
4
+ * LRU. Lets rendered audio survive process restarts/deploys so a fixed prompt
5
+ * (e.g. a greeting) is synthesized once *ever* per content key, not once per
6
+ * process. The store is content-addressed by the same `keyFor` key, so a
7
+ * changed prompt/voice/model naturally lands on a new key and re-renders.
8
+ *
9
+ * The store is told `TTSAudioEvent[]` and must return the same on read; how it
10
+ * serializes the binary `chunk`s (base64 in JSON, bytea, a file, etc.) is up to
11
+ * the implementation. `get` returns `null`/`undefined` on a miss. Both may be
12
+ * sync or async; errors should be swallowed by the implementation (a store
13
+ * failure must never break playback — the wrapper falls back to live render).
14
+ */
15
+ export type CachedTTSStore = {
16
+ get: (key: string) => Promise<TTSAudioEvent[] | null | undefined> | TTSAudioEvent[] | null | undefined;
17
+ set: (key: string, events: TTSAudioEvent[]) => Promise<void> | void;
18
+ };
2
19
  export type CachedTTSOptions = {
3
20
  /**
4
21
  * Return a stable cache key for an utterance whose synthesized audio should
@@ -13,8 +30,15 @@ export type CachedTTSOptions = {
13
30
  * (and re-caches) while the old entry is simply orphaned.
14
31
  */
15
32
  keyFor: (text: string, openOptions: TTSAdapterOpenOptions) => string | null | undefined;
16
- /** Max distinct utterances to retain (LRU by insertion). Default 32. */
33
+ /** Max distinct utterances to retain in memory (LRU by insertion). Default 32. */
17
34
  maxEntries?: number;
35
+ /**
36
+ * Optional persistent L2 store (see {@link CachedTTSStore}). When set, an
37
+ * in-memory miss consults the store before rendering; a store hit is replayed
38
+ * and promoted into memory, and a fresh render is written through to it. Omit
39
+ * for memory-only behaviour (unchanged).
40
+ */
41
+ store?: CachedTTSStore;
18
42
  };
19
43
  /**
20
44
  * Wrap a TTS adapter so selected utterances are synthesized once and replayed
package/dist/index.d.ts CHANGED
@@ -223,7 +223,7 @@ export type { VoiceSimulationSuiteAssertionInput, VoiceSimulationSuiteAssertionR
223
223
  export type { VoiceWorkflowContract, VoiceWorkflowContractDefinition, VoiceWorkflowContractField, VoiceWorkflowContractFieldMatch, VoiceWorkflowContractPresetName, VoiceWorkflowContractPresetOptions, VoiceWorkflowContractTracePayload, VoiceWorkflowContractValidation, VoiceWorkflowContractValidationIssue, VoiceWorkflowOutcome, } from "./core/workflowContract";
224
224
  export type { VoiceSessionListHTMLHandlerOptions, VoiceSessionListItem, VoiceSessionListOptions, VoiceSessionListRoutesOptions, VoiceSessionListStatus, VoiceProviderFallbackRecoverySummary, VoiceSessionReplay, VoiceSessionReplayHTMLHandlerOptions, VoiceSessionReplayOptions, VoiceSessionReplayRoutesOptions, VoiceSessionReplayTurn, } from "./core/sessionReplay";
225
225
  export type { AnthropicVoiceAssistantModelOptions, GeminiVoiceAssistantModelOptions, OpenAIVoiceAssistantModelOptions, VoiceProviderRouterEvent, VoiceProviderRouterFallbackMode, VoiceProviderRouterHealthOptions, VoiceProviderRouterOptions, VoiceProviderOrchestrationProfile, VoiceProviderOrchestrationProfileOptions, VoiceProviderOrchestrationResolvedSurface, VoiceProviderOrchestrationSurface, VoiceProviderRouterPolicy, VoiceProviderRouterPolicyPreset, VoiceProviderRouterPolicyWeights, VoiceProviderRouterProviderHealth, VoiceProviderRouterProviderProfile, VoiceProviderRouterStrategy, VoiceJSONAssistantModelHandler, VoiceJSONAssistantModelOptions, } from "./core/modelAdapters";
226
- export type { CachedTTSOptions } from "./core/cachedTTS";
226
+ export type { CachedTTSOptions, CachedTTSStore } from "./core/cachedTTS";
227
227
  export type { OpenAIVoiceTTSOptions, OpenAIVoiceTTSVoice, } from "./core/openaiTTS";
228
228
  export type { VoiceProviderHealthStatus, VoiceProviderHealthSummary, VoiceProviderHealthSummaryOptions, } from "./core/providerHealth";
229
229
  export type { VoiceProviderCapabilityDefinition, VoiceProviderCapabilityHandlerOptions, VoiceProviderCapabilityHTMLHandlerOptions, VoiceProviderCapabilityKind, VoiceProviderCapabilityOptions, VoiceProviderCapabilityReport, VoiceProviderCapabilityRoutesOptions, VoiceProviderCapabilitySummary, } from "./core/providerCapabilities";
package/dist/index.js CHANGED
@@ -45566,6 +45566,7 @@ var createGeminiVoiceAssistantModel = (options) => {
45566
45566
  var DEFAULT_MAX_ENTRIES = 32;
45567
45567
  var createCachedTTS = (inner, options) => {
45568
45568
  const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
45569
+ const { store } = options;
45569
45570
  const cache = new Map;
45570
45571
  const remember = (key, events) => {
45571
45572
  cache.delete(key);
@@ -45578,6 +45579,18 @@ var createCachedTTS = (inner, options) => {
45578
45579
  cache.delete(oldest);
45579
45580
  }
45580
45581
  };
45582
+ const loadFromStore = async (key) => {
45583
+ if (!store)
45584
+ return null;
45585
+ try {
45586
+ const events = await store.get(key);
45587
+ if (events && events.length > 0) {
45588
+ remember(key, events);
45589
+ return events;
45590
+ }
45591
+ } catch {}
45592
+ return null;
45593
+ };
45581
45594
  return {
45582
45595
  kind: "tts",
45583
45596
  open: async (openOptions) => {
@@ -45608,9 +45621,8 @@ var createCachedTTS = (inner, options) => {
45608
45621
  await session.send(text);
45609
45622
  return;
45610
45623
  }
45611
- const cached = cache.get(key);
45612
- if (cached) {
45613
- for (const event of cached) {
45624
+ const replayEvents = async (events) => {
45625
+ for (const event of events) {
45614
45626
  const replay = {
45615
45627
  ...event,
45616
45628
  receivedAt: Date.now()
@@ -45619,12 +45631,25 @@ var createCachedTTS = (inner, options) => {
45619
45631
  await Promise.resolve(handler(replay));
45620
45632
  }
45621
45633
  }
45634
+ };
45635
+ const cached = cache.get(key) ?? await loadFromStore(key);
45636
+ if (cached && cached.length > 0) {
45637
+ await replayEvents(cached);
45622
45638
  return;
45623
45639
  }
45624
45640
  capture = [];
45625
45641
  await session.send(text);
45626
- remember(key, capture);
45642
+ const rendered = capture;
45627
45643
  capture = null;
45644
+ if (rendered.length === 0) {
45645
+ return;
45646
+ }
45647
+ remember(key, rendered);
45648
+ if (store) {
45649
+ try {
45650
+ await store.set(key, rendered);
45651
+ } catch {}
45652
+ }
45628
45653
  }
45629
45654
  };
45630
45655
  }
package/package.json CHANGED
@@ -1,157 +1,157 @@
1
1
  {
2
- "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.575",
4
- "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
- "repository": {
6
- "type": "git",
7
- "url": "https://github.com/absolutejs/voice.git"
8
- },
9
- "files": [
10
- "dist",
11
- "README.md"
12
- ],
13
- "main": "./dist/index.js",
14
- "types": "./dist/index.d.ts",
15
- "license": "BSL-1.1",
16
- "author": "Alex Kahn",
17
- "scripts": {
18
- "build": "rm -rf dist && bun build ./src/index.ts ./src/client/index.ts ./src/react/index.ts ./src/vue/index.ts ./src/svelte/index.ts ./src/angular/index.ts ./src/testing/index.ts ./src/drizzle/index.ts --outdir dist --target bun --external elysia --external react --external vue --external @angular/core --external @absolutejs/absolute --external @absolutejs/ai --external @absolutejs/media --external drizzle-orm && bun build ./src/client/htmxBootstrap.ts --outdir dist/client --target browser --format esm && bun build ./src/embed/index.ts --outfile dist/embed/voice-widget.js --target browser --format iife --minify && bun build ./src/embed/index.ts --outdir dist/embed --target browser --format esm && tsc --emitDeclarationOnly --project tsconfig.json",
19
- "config": "absolute config",
20
- "format": "absolute prettier --write",
21
- "knip": "knip",
22
- "lint": "absolute eslint",
23
- "release": "bun run format && bun run build && bun publish",
24
- "test": "bun test ./test/*.test.ts",
25
- "test:adapters": "bun test ./test/live/*.test.ts",
26
- "test:assemblyai": "bun test ./test/live/assemblyai.live.test.ts",
27
- "test:deepgram": "bun test ./test/live/deepgram.live.test.ts",
28
- "test:elevenlabs": "bun test ./test/live/elevenlabs.live.test.ts",
29
- "test:openai": "bun test ./test/live/openai.live.test.ts",
30
- "typecheck": "absolute typecheck"
31
- },
32
- "exports": {
33
- ".": {
34
- "import": "./dist/index.js",
35
- "types": "./dist/index.d.ts"
36
- },
37
- "./client": {
38
- "browser": "./dist/client/index.js",
39
- "import": "./dist/client/index.js",
40
- "types": "./dist/client/index.d.ts"
41
- },
42
- "./react": {
43
- "browser": "./dist/react/index.js",
44
- "import": "./dist/react/index.js",
45
- "types": "./dist/react/index.d.ts"
46
- },
47
- "./vue": {
48
- "browser": "./dist/vue/index.js",
49
- "import": "./dist/vue/index.js",
50
- "types": "./dist/vue/index.d.ts"
51
- },
52
- "./svelte": {
53
- "import": "./dist/svelte/index.js",
54
- "types": "./dist/svelte/index.d.ts"
55
- },
56
- "./angular": {
57
- "import": "./dist/angular/index.js",
58
- "types": "./dist/angular/index.d.ts"
59
- },
60
- "./drizzle": {
61
- "import": "./dist/drizzle/index.js",
62
- "types": "./dist/drizzle/index.d.ts",
63
- "default": "./dist/drizzle/index.js"
64
- },
65
- "./testing": {
66
- "import": "./dist/testing/index.js",
67
- "types": "./dist/testing/index.d.ts"
68
- },
69
- "./embed": {
70
- "browser": "./dist/embed/index.js",
71
- "import": "./dist/embed/index.js",
72
- "types": "./dist/embed/index.d.ts"
73
- },
74
- "./embed/voice-widget.js": "./dist/embed/voice-widget.js"
75
- },
76
- "typesVersions": {
77
- "*": {
78
- "testing": [
79
- "dist/testing/index.d.ts"
80
- ],
81
- "client": [
82
- "dist/client/index.d.ts"
83
- ],
84
- "react": [
85
- "dist/react/index.d.ts"
86
- ],
87
- "svelte": [
88
- "dist/svelte/index.d.ts"
89
- ],
90
- "vue": [
91
- "dist/vue/index.d.ts"
92
- ],
93
- "angular": [
94
- "dist/angular/index.d.ts"
95
- ],
96
- "drizzle": [
97
- "dist/drizzle/index.d.ts"
98
- ],
99
- "embed": [
100
- "dist/embed/index.d.ts"
101
- ]
102
- }
103
- },
104
- "peerDependencies": {
105
- "@absolutejs/absolute": ">=0.19.0-beta.646",
106
- "@absolutejs/ai": ">=0.0.5",
107
- "@angular/core": ">=21.0.0",
108
- "drizzle-orm": ">=1.0.0-rc.1",
109
- "elysia": ">=1.4.18",
110
- "react": ">=19.0.0",
111
- "vue": ">=3.5.0"
112
- },
113
- "peerDependenciesMeta": {
114
- "@absolutejs/ai": {
115
- "optional": true
116
- },
117
- "drizzle-orm": {
118
- "optional": true
119
- },
120
- "@angular/core": {
121
- "optional": true
122
- },
123
- "react": {
124
- "optional": true
125
- },
126
- "vue": {
127
- "optional": true
128
- }
129
- },
130
- "dependencies": {
131
- "@absolutejs/media": "0.0.1-beta.19"
132
- },
133
- "devDependencies": {
134
- "@absolutejs/absolute": "0.19.0-beta.1009",
135
- "@absolutejs/ai": "0.0.5",
136
- "@angular/core": "^21.0.0",
137
- "@electric-sql/pglite": "^0.4.5",
138
- "@eslint/js": "^10.0.1",
139
- "@stylistic/eslint-plugin": "^5.10.0",
140
- "@types/bun": "1.3.9",
141
- "@types/react": "19.2.0",
142
- "@typescript-eslint/parser": "^8.57.2",
143
- "drizzle-orm": "1.0.0-rc.3",
144
- "elysia": "1.4.18",
145
- "eslint": "^10.1.0",
146
- "eslint-plugin-absolute": "0.11.0-beta.3",
147
- "eslint-plugin-promise": "^7.2.1",
148
- "eslint-plugin-security": "^4.0.0",
149
- "globals": "^17.4.0",
150
- "knip": "^6.14.1",
151
- "prettier": "^3.4.0",
152
- "react": "19.2.1",
153
- "typescript": "^5.9.3",
154
- "typescript-eslint": "^8.57.2",
155
- "vue": "3.5.27"
156
- }
2
+ "name": "@absolutejs/voice",
3
+ "version": "0.0.22-beta.577",
4
+ "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/absolutejs/voice.git"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "license": "BSL-1.1",
16
+ "author": "Alex Kahn",
17
+ "scripts": {
18
+ "build": "rm -rf dist && bun build ./src/index.ts ./src/client/index.ts ./src/react/index.ts ./src/vue/index.ts ./src/svelte/index.ts ./src/angular/index.ts ./src/testing/index.ts ./src/drizzle/index.ts --outdir dist --target bun --external elysia --external react --external vue --external @angular/core --external @absolutejs/absolute --external @absolutejs/ai --external @absolutejs/media --external drizzle-orm && bun build ./src/client/htmxBootstrap.ts --outdir dist/client --target browser --format esm && bun build ./src/embed/index.ts --outfile dist/embed/voice-widget.js --target browser --format iife --minify && bun build ./src/embed/index.ts --outdir dist/embed --target browser --format esm && tsc --emitDeclarationOnly --project tsconfig.json",
19
+ "config": "absolute config",
20
+ "format": "absolute prettier --write",
21
+ "knip": "knip",
22
+ "lint": "absolute eslint",
23
+ "release": "bun run format && bun run build && bun publish",
24
+ "test": "bun test ./test/*.test.ts",
25
+ "test:adapters": "bun test ./test/live/*.test.ts",
26
+ "test:assemblyai": "bun test ./test/live/assemblyai.live.test.ts",
27
+ "test:deepgram": "bun test ./test/live/deepgram.live.test.ts",
28
+ "test:elevenlabs": "bun test ./test/live/elevenlabs.live.test.ts",
29
+ "test:openai": "bun test ./test/live/openai.live.test.ts",
30
+ "typecheck": "absolute typecheck"
31
+ },
32
+ "exports": {
33
+ ".": {
34
+ "import": "./dist/index.js",
35
+ "types": "./dist/index.d.ts"
36
+ },
37
+ "./client": {
38
+ "browser": "./dist/client/index.js",
39
+ "import": "./dist/client/index.js",
40
+ "types": "./dist/client/index.d.ts"
41
+ },
42
+ "./react": {
43
+ "browser": "./dist/react/index.js",
44
+ "import": "./dist/react/index.js",
45
+ "types": "./dist/react/index.d.ts"
46
+ },
47
+ "./vue": {
48
+ "browser": "./dist/vue/index.js",
49
+ "import": "./dist/vue/index.js",
50
+ "types": "./dist/vue/index.d.ts"
51
+ },
52
+ "./svelte": {
53
+ "import": "./dist/svelte/index.js",
54
+ "types": "./dist/svelte/index.d.ts"
55
+ },
56
+ "./angular": {
57
+ "import": "./dist/angular/index.js",
58
+ "types": "./dist/angular/index.d.ts"
59
+ },
60
+ "./drizzle": {
61
+ "import": "./dist/drizzle/index.js",
62
+ "types": "./dist/drizzle/index.d.ts",
63
+ "default": "./dist/drizzle/index.js"
64
+ },
65
+ "./testing": {
66
+ "import": "./dist/testing/index.js",
67
+ "types": "./dist/testing/index.d.ts"
68
+ },
69
+ "./embed": {
70
+ "browser": "./dist/embed/index.js",
71
+ "import": "./dist/embed/index.js",
72
+ "types": "./dist/embed/index.d.ts"
73
+ },
74
+ "./embed/voice-widget.js": "./dist/embed/voice-widget.js"
75
+ },
76
+ "typesVersions": {
77
+ "*": {
78
+ "testing": [
79
+ "dist/testing/index.d.ts"
80
+ ],
81
+ "client": [
82
+ "dist/client/index.d.ts"
83
+ ],
84
+ "react": [
85
+ "dist/react/index.d.ts"
86
+ ],
87
+ "svelte": [
88
+ "dist/svelte/index.d.ts"
89
+ ],
90
+ "vue": [
91
+ "dist/vue/index.d.ts"
92
+ ],
93
+ "angular": [
94
+ "dist/angular/index.d.ts"
95
+ ],
96
+ "drizzle": [
97
+ "dist/drizzle/index.d.ts"
98
+ ],
99
+ "embed": [
100
+ "dist/embed/index.d.ts"
101
+ ]
102
+ }
103
+ },
104
+ "peerDependencies": {
105
+ "@absolutejs/absolute": ">=0.19.0-beta.646",
106
+ "@absolutejs/ai": ">=0.0.5",
107
+ "@angular/core": ">=21.0.0",
108
+ "drizzle-orm": ">=1.0.0-rc.1",
109
+ "elysia": ">=1.4.18",
110
+ "react": ">=19.0.0",
111
+ "vue": ">=3.5.0"
112
+ },
113
+ "peerDependenciesMeta": {
114
+ "@absolutejs/ai": {
115
+ "optional": true
116
+ },
117
+ "drizzle-orm": {
118
+ "optional": true
119
+ },
120
+ "@angular/core": {
121
+ "optional": true
122
+ },
123
+ "react": {
124
+ "optional": true
125
+ },
126
+ "vue": {
127
+ "optional": true
128
+ }
129
+ },
130
+ "dependencies": {
131
+ "@absolutejs/media": "0.0.1-beta.19"
132
+ },
133
+ "devDependencies": {
134
+ "@absolutejs/absolute": "0.19.0-beta.1009",
135
+ "@absolutejs/ai": "0.0.5",
136
+ "@angular/core": "^21.0.0",
137
+ "@electric-sql/pglite": "^0.4.5",
138
+ "@eslint/js": "^10.0.1",
139
+ "@stylistic/eslint-plugin": "^5.10.0",
140
+ "@types/bun": "1.3.9",
141
+ "@types/react": "19.2.0",
142
+ "@typescript-eslint/parser": "^8.57.2",
143
+ "drizzle-orm": "1.0.0-rc.3",
144
+ "elysia": "1.4.18",
145
+ "eslint": "^10.1.0",
146
+ "eslint-plugin-absolute": "0.11.0-beta.3",
147
+ "eslint-plugin-promise": "^7.2.1",
148
+ "eslint-plugin-security": "^4.0.0",
149
+ "globals": "^17.4.0",
150
+ "knip": "^6.14.1",
151
+ "prettier": "^3.4.0",
152
+ "react": "19.2.1",
153
+ "typescript": "^5.9.3",
154
+ "typescript-eslint": "^8.57.2",
155
+ "vue": "3.5.27"
156
+ }
157
157
  }