@absolutejs/voice 0.0.22-beta.561 → 0.0.22-beta.562

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.
@@ -916,6 +916,32 @@ export type CreateVoiceSessionOptions<TContext = unknown, TSession extends Voice
916
916
  fillerPhrases?: ReadonlyArray<string>;
917
917
  /** Milliseconds after turn-commit before the filler fires. Default 250ms — short enough to feel instant, long enough to skip if the LLM is very fast. */
918
918
  fillerDelayMs?: number;
919
+ /**
920
+ * Latency Theater — content-aware filler (Boardy parity move). When
921
+ * defined, the runtime calls `fillerFor({ userText, ... })` in parallel
922
+ * with the main LLM call to generate a brief acknowledgement of what the
923
+ * caller just said ("Freelance CFOs — interesting.", "Yeah, I hear you.").
924
+ * The runtime races the promise against `fillerForTimeoutMs` (default
925
+ * 600ms). If `fillerFor` returns a non-empty string in time, it's spoken
926
+ * INSTEAD of a random `fillerPhrases` entry. On timeout or null return,
927
+ * the runtime falls back to a static random phrase, so a slow / failed
928
+ * acknowledgement call never costs you the filler entirely.
929
+ *
930
+ * Return `null` (or an empty string) to explicitly skip filler for this
931
+ * turn — useful when `userText` is so short ("yes", "no", "okay") that
932
+ * acknowledging it back sounds robotic. Return throws are caught and
933
+ * treated as null.
934
+ *
935
+ * Cost-aware: callers typically wire this to a cheap nano/haiku model
936
+ * (gpt-4.1-nano, claude-haiku-4-5) that returns 2–5 words.
937
+ */
938
+ fillerFor?: (input: {
939
+ sessionId: string;
940
+ turnId: string;
941
+ userText: string;
942
+ }) => Promise<string | null>;
943
+ /** Ceiling for the `fillerFor` call before we fall back to a static phrase. Default 600ms. */
944
+ fillerForTimeoutMs?: number;
919
945
  /**
920
946
  * Default spoken ack if the model returns ONLY tool calls (no text) and the
921
947
  * turn isn't ending. Without this, the caller hears total silence after
package/dist/index.js CHANGED
@@ -3875,6 +3875,8 @@ var createVoiceSession = (options) => {
3875
3875
  let fillerToken = 0;
3876
3876
  const fillerPhrases = (options.fillerPhrases ?? []).filter((p) => typeof p === "string" && p.trim().length > 0);
3877
3877
  const fillerDelayMs = options.fillerDelayMs ?? 250;
3878
+ const fillerFor = options.fillerFor;
3879
+ const fillerForTimeoutMs = options.fillerForTimeoutMs ?? 600;
3878
3880
  const currentTurnAudio = [];
3879
3881
  const pendingUserAttachments = [];
3880
3882
  let fallbackAttemptsForCurrentTurn = 0;
@@ -5295,23 +5297,46 @@ var createVoiceSession = (options) => {
5295
5297
  const injectedInstruction = liveOpsControl?.injectedInstruction?.trim();
5296
5298
  const ttsStreamer = options.tts ? createTurnTTSStreamer(turn, session) : undefined;
5297
5299
  if (fillerPhrases.length > 0 && options.tts && !ttsStreamer) {}
5298
- if (fillerPhrases.length > 0 && options.tts) {
5300
+ if ((fillerPhrases.length > 0 || fillerFor) && options.tts) {
5299
5301
  fillerToken += 1;
5300
5302
  const myToken = fillerToken;
5301
5303
  if (fillerTimer)
5302
5304
  clearTimeout(fillerTimer);
5305
+ const fillerForPromise = fillerFor ? Promise.race([
5306
+ (async () => {
5307
+ try {
5308
+ const v = await fillerFor({
5309
+ sessionId: options.id,
5310
+ turnId: turn.id,
5311
+ userText: turn.text ?? ""
5312
+ });
5313
+ return v && v.trim().length > 0 ? v : null;
5314
+ } catch {
5315
+ return null;
5316
+ }
5317
+ })(),
5318
+ new Promise((resolve) => setTimeout(() => resolve(null), fillerForTimeoutMs))
5319
+ ]) : null;
5303
5320
  fillerTimer = setTimeout(() => {
5304
5321
  fillerTimer = null;
5305
5322
  if (myToken !== fillerToken)
5306
5323
  return;
5307
5324
  if (activeTTSTurnId === turn.id)
5308
5325
  return;
5309
- const phrase = fillerPhrases[Math.floor(Math.random() * fillerPhrases.length)] ?? "";
5310
- if (!phrase)
5311
- return;
5312
5326
  runSerial("filler.send", async () => {
5313
5327
  if (myToken !== fillerToken || activeTTSTurnId === turn.id)
5314
5328
  return;
5329
+ let phrase = null;
5330
+ if (fillerForPromise) {
5331
+ phrase = await fillerForPromise;
5332
+ if (myToken !== fillerToken || activeTTSTurnId === turn.id)
5333
+ return;
5334
+ }
5335
+ if (!phrase && fillerPhrases.length > 0) {
5336
+ phrase = fillerPhrases[Math.floor(Math.random() * fillerPhrases.length)] ?? null;
5337
+ }
5338
+ if (!phrase)
5339
+ return;
5315
5340
  const adapterSession = await ensureTTSSession();
5316
5341
  if (!adapterSession)
5317
5342
  return;
@@ -5347,10 +5372,7 @@ var createVoiceSession = (options) => {
5347
5372
  }, onTurnTimeoutMs);
5348
5373
  });
5349
5374
  try {
5350
- committedOutput = await Promise.race([
5351
- onTurnPromise,
5352
- timeoutPromise
5353
- ]);
5375
+ committedOutput = await Promise.race([onTurnPromise, timeoutPromise]);
5354
5376
  } finally {
5355
5377
  if (timer)
5356
5378
  clearTimeout(timer);
@@ -24720,6 +24742,8 @@ var createTwilioMediaStreamBridge = (socket, options) => {
24720
24742
  ...options.semanticTurnDetector ? { semanticTurnDetector: options.semanticTurnDetector } : {},
24721
24743
  ...options.fillerPhrases ? { fillerPhrases: options.fillerPhrases } : {},
24722
24744
  ...options.fillerDelayMs !== undefined ? { fillerDelayMs: options.fillerDelayMs } : {},
24745
+ ...options.fillerFor ? { fillerFor: options.fillerFor } : {},
24746
+ ...options.fillerForTimeoutMs !== undefined ? { fillerForTimeoutMs: options.fillerForTimeoutMs } : {},
24723
24747
  ...options.defaultSilentTurnAck !== undefined ? { defaultSilentTurnAck: options.defaultSilentTurnAck } : {},
24724
24748
  ...options.routeOnTurnTimeoutMs !== undefined ? { routeOnTurnTimeoutMs: options.routeOnTurnTimeoutMs } : {},
24725
24749
  trace: options.trace,
@@ -132,6 +132,20 @@ export type TwilioMediaStreamBridgeOptions<TContext = unknown, TSession extends
132
132
  fillerPhrases?: ReadonlyArray<string>;
133
133
  /** Milliseconds after turn-commit before the filler fires. Default 250ms. */
134
134
  fillerDelayMs?: number;
135
+ /**
136
+ * Content-aware filler (Latency Theater). Called in parallel with the
137
+ * main LLM turn; if it resolves within `fillerForTimeoutMs` the runtime
138
+ * speaks the result instead of a random `fillerPhrases` entry. Return
139
+ * `null` to skip filler for this turn. See CreateVoiceSessionOptions
140
+ * for full semantics.
141
+ */
142
+ fillerFor?: (input: {
143
+ sessionId: string;
144
+ turnId: string;
145
+ userText: string;
146
+ }) => Promise<string | null>;
147
+ /** Cap on the `fillerFor` race before falling back to a static phrase. Default 600ms. */
148
+ fillerForTimeoutMs?: number;
135
149
  /**
136
150
  * Default spoken ack if the model returns ONLY tool calls (no text) and
137
151
  * the turn isn't ending. Without this, the caller hears silence and
@@ -5746,6 +5746,8 @@ var createVoiceSession = (options) => {
5746
5746
  let fillerToken = 0;
5747
5747
  const fillerPhrases = (options.fillerPhrases ?? []).filter((p) => typeof p === "string" && p.trim().length > 0);
5748
5748
  const fillerDelayMs = options.fillerDelayMs ?? 250;
5749
+ const fillerFor = options.fillerFor;
5750
+ const fillerForTimeoutMs = options.fillerForTimeoutMs ?? 600;
5749
5751
  const currentTurnAudio = [];
5750
5752
  const pendingUserAttachments = [];
5751
5753
  let fallbackAttemptsForCurrentTurn = 0;
@@ -7166,23 +7168,46 @@ var createVoiceSession = (options) => {
7166
7168
  const injectedInstruction = liveOpsControl?.injectedInstruction?.trim();
7167
7169
  const ttsStreamer = options.tts ? createTurnTTSStreamer(turn, session) : undefined;
7168
7170
  if (fillerPhrases.length > 0 && options.tts && !ttsStreamer) {}
7169
- if (fillerPhrases.length > 0 && options.tts) {
7171
+ if ((fillerPhrases.length > 0 || fillerFor) && options.tts) {
7170
7172
  fillerToken += 1;
7171
7173
  const myToken = fillerToken;
7172
7174
  if (fillerTimer)
7173
7175
  clearTimeout(fillerTimer);
7176
+ const fillerForPromise = fillerFor ? Promise.race([
7177
+ (async () => {
7178
+ try {
7179
+ const v = await fillerFor({
7180
+ sessionId: options.id,
7181
+ turnId: turn.id,
7182
+ userText: turn.text ?? ""
7183
+ });
7184
+ return v && v.trim().length > 0 ? v : null;
7185
+ } catch {
7186
+ return null;
7187
+ }
7188
+ })(),
7189
+ new Promise((resolve2) => setTimeout(() => resolve2(null), fillerForTimeoutMs))
7190
+ ]) : null;
7174
7191
  fillerTimer = setTimeout(() => {
7175
7192
  fillerTimer = null;
7176
7193
  if (myToken !== fillerToken)
7177
7194
  return;
7178
7195
  if (activeTTSTurnId === turn.id)
7179
7196
  return;
7180
- const phrase = fillerPhrases[Math.floor(Math.random() * fillerPhrases.length)] ?? "";
7181
- if (!phrase)
7182
- return;
7183
7197
  runSerial("filler.send", async () => {
7184
7198
  if (myToken !== fillerToken || activeTTSTurnId === turn.id)
7185
7199
  return;
7200
+ let phrase = null;
7201
+ if (fillerForPromise) {
7202
+ phrase = await fillerForPromise;
7203
+ if (myToken !== fillerToken || activeTTSTurnId === turn.id)
7204
+ return;
7205
+ }
7206
+ if (!phrase && fillerPhrases.length > 0) {
7207
+ phrase = fillerPhrases[Math.floor(Math.random() * fillerPhrases.length)] ?? null;
7208
+ }
7209
+ if (!phrase)
7210
+ return;
7186
7211
  const adapterSession = await ensureTTSSession();
7187
7212
  if (!adapterSession)
7188
7213
  return;
@@ -7218,10 +7243,7 @@ var createVoiceSession = (options) => {
7218
7243
  }, onTurnTimeoutMs);
7219
7244
  });
7220
7245
  try {
7221
- committedOutput = await Promise.race([
7222
- onTurnPromise,
7223
- timeoutPromise
7224
- ]);
7246
+ committedOutput = await Promise.race([onTurnPromise, timeoutPromise]);
7225
7247
  } finally {
7226
7248
  if (timer)
7227
7249
  clearTimeout(timer);
@@ -13310,6 +13332,8 @@ var createTwilioMediaStreamBridge = (socket, options) => {
13310
13332
  ...options.semanticTurnDetector ? { semanticTurnDetector: options.semanticTurnDetector } : {},
13311
13333
  ...options.fillerPhrases ? { fillerPhrases: options.fillerPhrases } : {},
13312
13334
  ...options.fillerDelayMs !== undefined ? { fillerDelayMs: options.fillerDelayMs } : {},
13335
+ ...options.fillerFor ? { fillerFor: options.fillerFor } : {},
13336
+ ...options.fillerForTimeoutMs !== undefined ? { fillerForTimeoutMs: options.fillerForTimeoutMs } : {},
13313
13337
  ...options.defaultSilentTurnAck !== undefined ? { defaultSilentTurnAck: options.defaultSilentTurnAck } : {},
13314
13338
  ...options.routeOnTurnTimeoutMs !== undefined ? { routeOnTurnTimeoutMs: options.routeOnTurnTimeoutMs } : {},
13315
13339
  trace: options.trace,
package/package.json CHANGED
@@ -1,157 +1,157 @@
1
1
  {
2
- "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.561",
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": "CC BY-NC 4.0",
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": ">=0.36.0",
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": "0.44.7",
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.562",
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": "CC BY-NC 4.0",
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": ">=0.36.0",
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": "0.44.7",
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
  }