@livekit/agents 1.0.49 → 1.0.51

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.
Files changed (105) hide show
  1. package/dist/index.cjs +12 -10
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +13 -13
  4. package/dist/index.d.ts +13 -13
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +11 -10
  7. package/dist/index.js.map +1 -1
  8. package/dist/inference/api_protos.d.cts +67 -67
  9. package/dist/inference/api_protos.d.ts +67 -67
  10. package/dist/inference/llm.cjs +10 -8
  11. package/dist/inference/llm.cjs.map +1 -1
  12. package/dist/inference/llm.d.cts +1 -1
  13. package/dist/inference/llm.d.ts +1 -1
  14. package/dist/inference/llm.d.ts.map +1 -1
  15. package/dist/inference/llm.js +3 -7
  16. package/dist/inference/llm.js.map +1 -1
  17. package/dist/inference/stt.cjs +20 -12
  18. package/dist/inference/stt.cjs.map +1 -1
  19. package/dist/inference/stt.d.cts +3 -2
  20. package/dist/inference/stt.d.ts +3 -2
  21. package/dist/inference/stt.d.ts.map +1 -1
  22. package/dist/inference/stt.js +20 -12
  23. package/dist/inference/stt.js.map +1 -1
  24. package/dist/inference/stt.test.cjs +14 -0
  25. package/dist/inference/stt.test.cjs.map +1 -1
  26. package/dist/inference/stt.test.js +14 -0
  27. package/dist/inference/stt.test.js.map +1 -1
  28. package/dist/inference/tts.cjs +13 -4
  29. package/dist/inference/tts.cjs.map +1 -1
  30. package/dist/inference/tts.d.cts +2 -1
  31. package/dist/inference/tts.d.ts +2 -1
  32. package/dist/inference/tts.d.ts.map +1 -1
  33. package/dist/inference/tts.js +13 -4
  34. package/dist/inference/tts.js.map +1 -1
  35. package/dist/inference/tts.test.cjs +10 -0
  36. package/dist/inference/tts.test.cjs.map +1 -1
  37. package/dist/inference/tts.test.js +10 -0
  38. package/dist/inference/tts.test.js.map +1 -1
  39. package/dist/inference/utils.cjs +5 -5
  40. package/dist/inference/utils.cjs.map +1 -1
  41. package/dist/inference/utils.js +1 -1
  42. package/dist/inference/utils.js.map +1 -1
  43. package/dist/ipc/job_proc_lazy_main.cjs +13 -4
  44. package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
  45. package/dist/ipc/job_proc_lazy_main.js +13 -4
  46. package/dist/ipc/job_proc_lazy_main.js.map +1 -1
  47. package/dist/language.cjs +394 -0
  48. package/dist/language.cjs.map +1 -0
  49. package/dist/language.d.cts +15 -0
  50. package/dist/language.d.ts +15 -0
  51. package/dist/language.d.ts.map +1 -0
  52. package/dist/language.js +363 -0
  53. package/dist/language.js.map +1 -0
  54. package/dist/language.test.cjs +43 -0
  55. package/dist/language.test.cjs.map +1 -0
  56. package/dist/language.test.js +49 -0
  57. package/dist/language.test.js.map +1 -0
  58. package/dist/stream/deferred_stream.cjs +6 -2
  59. package/dist/stream/deferred_stream.cjs.map +1 -1
  60. package/dist/stream/deferred_stream.d.ts.map +1 -1
  61. package/dist/stream/deferred_stream.js +6 -2
  62. package/dist/stream/deferred_stream.js.map +1 -1
  63. package/dist/stt/stt.cjs.map +1 -1
  64. package/dist/stt/stt.d.cts +2 -1
  65. package/dist/stt/stt.d.ts +2 -1
  66. package/dist/stt/stt.d.ts.map +1 -1
  67. package/dist/stt/stt.js.map +1 -1
  68. package/dist/version.cjs +1 -1
  69. package/dist/version.js +1 -1
  70. package/dist/voice/agent_activity.cjs +4 -1
  71. package/dist/voice/agent_activity.cjs.map +1 -1
  72. package/dist/voice/agent_activity.d.ts.map +1 -1
  73. package/dist/voice/agent_activity.js +4 -1
  74. package/dist/voice/agent_activity.js.map +1 -1
  75. package/dist/voice/agent_activity.test.cjs +135 -0
  76. package/dist/voice/agent_activity.test.cjs.map +1 -0
  77. package/dist/voice/agent_activity.test.js +134 -0
  78. package/dist/voice/agent_activity.test.js.map +1 -0
  79. package/dist/voice/audio_recognition.cjs.map +1 -1
  80. package/dist/voice/audio_recognition.d.cts +3 -2
  81. package/dist/voice/audio_recognition.d.ts +3 -2
  82. package/dist/voice/audio_recognition.d.ts.map +1 -1
  83. package/dist/voice/audio_recognition.js.map +1 -1
  84. package/dist/voice/events.cjs.map +1 -1
  85. package/dist/voice/events.d.cts +3 -2
  86. package/dist/voice/events.d.ts +3 -2
  87. package/dist/voice/events.d.ts.map +1 -1
  88. package/dist/voice/events.js.map +1 -1
  89. package/package.json +1 -1
  90. package/src/index.ts +13 -15
  91. package/src/inference/llm.ts +3 -8
  92. package/src/inference/stt.test.ts +17 -0
  93. package/src/inference/stt.ts +22 -14
  94. package/src/inference/tts.test.ts +12 -0
  95. package/src/inference/tts.ts +14 -5
  96. package/src/inference/utils.ts +1 -1
  97. package/src/ipc/job_proc_lazy_main.ts +15 -4
  98. package/src/language.test.ts +62 -0
  99. package/src/language.ts +380 -0
  100. package/src/stream/deferred_stream.ts +5 -1
  101. package/src/stt/stt.ts +2 -1
  102. package/src/voice/agent_activity.test.ts +194 -0
  103. package/src/voice/agent_activity.ts +11 -1
  104. package/src/voice/audio_recognition.ts +4 -3
  105. package/src/voice/events.ts +3 -2
@@ -0,0 +1,363 @@
1
+ const KNOWN_LANGUAGE_CODES = [
2
+ "af",
3
+ "am",
4
+ "ar",
5
+ "as",
6
+ "az",
7
+ "be",
8
+ "bg",
9
+ "bn",
10
+ "bs",
11
+ "ca",
12
+ "cs",
13
+ "cy",
14
+ "da",
15
+ "de",
16
+ "el",
17
+ "en",
18
+ "es",
19
+ "et",
20
+ "eu",
21
+ "fa",
22
+ "ff",
23
+ "fi",
24
+ "fr",
25
+ "ga",
26
+ "gl",
27
+ "gu",
28
+ "ha",
29
+ "he",
30
+ "hi",
31
+ "hr",
32
+ "hu",
33
+ "hy",
34
+ "id",
35
+ "ig",
36
+ "is",
37
+ "it",
38
+ "ja",
39
+ "jv",
40
+ "ka",
41
+ "kk",
42
+ "km",
43
+ "kn",
44
+ "ko",
45
+ "ku",
46
+ "ky",
47
+ "lb",
48
+ "lg",
49
+ "ln",
50
+ "lo",
51
+ "lt",
52
+ "lv",
53
+ "mi",
54
+ "mk",
55
+ "ml",
56
+ "mn",
57
+ "mr",
58
+ "ms",
59
+ "mt",
60
+ "my",
61
+ "ne",
62
+ "nl",
63
+ "no",
64
+ "ny",
65
+ "oc",
66
+ "or",
67
+ "pa",
68
+ "pl",
69
+ "ps",
70
+ "pt",
71
+ "ro",
72
+ "ru",
73
+ "sd",
74
+ "sk",
75
+ "sl",
76
+ "sn",
77
+ "so",
78
+ "sq",
79
+ "sr",
80
+ "sv",
81
+ "sw",
82
+ "ta",
83
+ "te",
84
+ "tg",
85
+ "th",
86
+ "tl",
87
+ "tr",
88
+ "uk",
89
+ "ur",
90
+ "uz",
91
+ "vi",
92
+ "wo",
93
+ "xh",
94
+ "yo",
95
+ "zh",
96
+ "zu"
97
+ ];
98
+ function asLanguageCode(language) {
99
+ return language;
100
+ }
101
+ const ISO_639_3_TO_1 = {
102
+ afr: "af",
103
+ amh: "am",
104
+ ara: "ar",
105
+ hye: "hy",
106
+ asm: "as",
107
+ ast: void 0,
108
+ aze: "az",
109
+ bel: "be",
110
+ ben: "bn",
111
+ bos: "bs",
112
+ bul: "bg",
113
+ mya: "my",
114
+ yue: void 0,
115
+ cat: "ca",
116
+ ceb: void 0,
117
+ cmn: "zh",
118
+ nya: "ny",
119
+ hrv: "hr",
120
+ ces: "cs",
121
+ dan: "da",
122
+ nld: "nl",
123
+ eng: "en",
124
+ est: "et",
125
+ fil: void 0,
126
+ fin: "fi",
127
+ fra: "fr",
128
+ ful: "ff",
129
+ glg: "gl",
130
+ lug: "lg",
131
+ kat: "ka",
132
+ deu: "de",
133
+ ell: "el",
134
+ guj: "gu",
135
+ hau: "ha",
136
+ heb: "he",
137
+ hin: "hi",
138
+ hun: "hu",
139
+ isl: "is",
140
+ ibo: "ig",
141
+ ind: "id",
142
+ gle: "ga",
143
+ ita: "it",
144
+ jpn: "ja",
145
+ jav: "jv",
146
+ kea: void 0,
147
+ kan: "kn",
148
+ kaz: "kk",
149
+ khm: "km",
150
+ kor: "ko",
151
+ kur: "ku",
152
+ kir: "ky",
153
+ lao: "lo",
154
+ lav: "lv",
155
+ lin: "ln",
156
+ lit: "lt",
157
+ luo: void 0,
158
+ ltz: "lb",
159
+ mkd: "mk",
160
+ msa: "ms",
161
+ mal: "ml",
162
+ mlt: "mt",
163
+ zho: "zh",
164
+ mri: "mi",
165
+ mar: "mr",
166
+ mon: "mn",
167
+ nep: "ne",
168
+ nso: void 0,
169
+ nor: "no",
170
+ oci: "oc",
171
+ ori: "or",
172
+ pus: "ps",
173
+ fas: "fa",
174
+ pol: "pl",
175
+ por: "pt",
176
+ pan: "pa",
177
+ ron: "ro",
178
+ rus: "ru",
179
+ srp: "sr",
180
+ sna: "sn",
181
+ snd: "sd",
182
+ slk: "sk",
183
+ slv: "sl",
184
+ som: "so",
185
+ spa: "es",
186
+ swa: "sw",
187
+ swe: "sv",
188
+ tam: "ta",
189
+ tgk: "tg",
190
+ tel: "te",
191
+ tha: "th",
192
+ tur: "tr",
193
+ ukr: "uk",
194
+ umb: void 0,
195
+ urd: "ur",
196
+ uzb: "uz",
197
+ vie: "vi",
198
+ cym: "cy",
199
+ wol: "wo",
200
+ xho: "xh",
201
+ zul: "zu"
202
+ };
203
+ const LANGUAGE_NAMES_TO_CODE = {
204
+ afrikaans: "af",
205
+ albanian: "sq",
206
+ amharic: "am",
207
+ arabic: "ar",
208
+ armenian: "hy",
209
+ azerbaijani: "az",
210
+ basque: "eu",
211
+ belarusian: "be",
212
+ bengali: "bn",
213
+ bosnian: "bs",
214
+ bulgarian: "bg",
215
+ burmese: "my",
216
+ catalan: "ca",
217
+ chinese: "zh",
218
+ croatian: "hr",
219
+ czech: "cs",
220
+ danish: "da",
221
+ dutch: "nl",
222
+ english: "en",
223
+ estonian: "et",
224
+ finnish: "fi",
225
+ french: "fr",
226
+ galician: "gl",
227
+ georgian: "ka",
228
+ german: "de",
229
+ greek: "el",
230
+ gujarati: "gu",
231
+ hausa: "ha",
232
+ hebrew: "he",
233
+ hindi: "hi",
234
+ hungarian: "hu",
235
+ icelandic: "is",
236
+ indonesian: "id",
237
+ irish: "ga",
238
+ italian: "it",
239
+ japanese: "ja",
240
+ javanese: "jv",
241
+ kannada: "kn",
242
+ kazakh: "kk",
243
+ khmer: "km",
244
+ korean: "ko",
245
+ kurdish: "ku",
246
+ kyrgyz: "ky",
247
+ lao: "lo",
248
+ latvian: "lv",
249
+ lingala: "ln",
250
+ lithuanian: "lt",
251
+ luxembourgish: "lb",
252
+ macedonian: "mk",
253
+ malay: "ms",
254
+ malayalam: "ml",
255
+ maltese: "mt",
256
+ maori: "mi",
257
+ marathi: "mr",
258
+ mongolian: "mn",
259
+ nepali: "ne",
260
+ norwegian: "no",
261
+ occitan: "oc",
262
+ oriya: "or",
263
+ pashto: "ps",
264
+ persian: "fa",
265
+ polish: "pl",
266
+ portuguese: "pt",
267
+ punjabi: "pa",
268
+ romanian: "ro",
269
+ russian: "ru",
270
+ serbian: "sr",
271
+ shona: "sn",
272
+ sindhi: "sd",
273
+ slovak: "sk",
274
+ slovene: "sl",
275
+ slovenian: "sl",
276
+ somali: "so",
277
+ spanish: "es",
278
+ swahili: "sw",
279
+ swedish: "sv",
280
+ tagalog: "tl",
281
+ tamil: "ta",
282
+ tajik: "tg",
283
+ telugu: "te",
284
+ thai: "th",
285
+ turkish: "tr",
286
+ ukrainian: "uk",
287
+ urdu: "ur",
288
+ uzbek: "uz",
289
+ vietnamese: "vi",
290
+ welsh: "cy",
291
+ wolof: "wo",
292
+ xhosa: "xh",
293
+ yoruba: "yo",
294
+ zulu: "zu"
295
+ };
296
+ const CODE_TO_LANGUAGE_NAME = Object.fromEntries(
297
+ Object.entries(LANGUAGE_NAMES_TO_CODE).map(([name, code]) => [code, name])
298
+ );
299
+ CODE_TO_LANGUAGE_NAME.sl = "slovene";
300
+ function normalizeLanguage(language) {
301
+ const lowered = language.trim().toLowerCase();
302
+ if (lowered === "") {
303
+ return asLanguageCode("");
304
+ }
305
+ if (lowered in LANGUAGE_NAMES_TO_CODE) {
306
+ return asLanguageCode(LANGUAGE_NAMES_TO_CODE[lowered]);
307
+ }
308
+ if (lowered in ISO_639_3_TO_1) {
309
+ return asLanguageCode(ISO_639_3_TO_1[lowered] ?? lowered);
310
+ }
311
+ const parts = lowered.replaceAll("_", "-").split("-");
312
+ if (parts.length >= 2) {
313
+ const [base, ...rest] = parts;
314
+ return asLanguageCode(
315
+ [
316
+ base,
317
+ ...rest.map((part) => {
318
+ if (part.length === 4) {
319
+ return part.charAt(0).toUpperCase() + part.slice(1);
320
+ }
321
+ return part.toUpperCase();
322
+ })
323
+ ].join("-")
324
+ );
325
+ }
326
+ return asLanguageCode(lowered);
327
+ }
328
+ function getBaseLanguage(language) {
329
+ const normalized = normalizeLanguage(language);
330
+ const [base = ""] = normalized.split("-");
331
+ return ISO_639_3_TO_1[base] ?? base;
332
+ }
333
+ function getIsoLanguage(language) {
334
+ const normalized = normalizeLanguage(language);
335
+ const region = getLanguageRegion(normalized);
336
+ const baseLanguage = getBaseLanguage(normalized);
337
+ return region ? `${baseLanguage}-${region}` : baseLanguage;
338
+ }
339
+ function getLanguageRegion(language) {
340
+ const normalized = normalizeLanguage(language);
341
+ const [, ...parts] = normalized.split("-");
342
+ return parts.find((part) => part.length === 2);
343
+ }
344
+ function toLanguageName(language) {
345
+ return CODE_TO_LANGUAGE_NAME[getBaseLanguage(language)];
346
+ }
347
+ function areLanguagesEquivalent(left, right) {
348
+ if (left == null || right == null) {
349
+ return left === right;
350
+ }
351
+ return normalizeLanguage(left) === normalizeLanguage(right);
352
+ }
353
+ export {
354
+ KNOWN_LANGUAGE_CODES,
355
+ areLanguagesEquivalent,
356
+ asLanguageCode,
357
+ getBaseLanguage,
358
+ getIsoLanguage,
359
+ getLanguageRegion,
360
+ normalizeLanguage,
361
+ toLanguageName
362
+ };
363
+ //# sourceMappingURL=language.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/language.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\nexport const KNOWN_LANGUAGE_CODES = [\n 'af',\n 'am',\n 'ar',\n 'as',\n 'az',\n 'be',\n 'bg',\n 'bn',\n 'bs',\n 'ca',\n 'cs',\n 'cy',\n 'da',\n 'de',\n 'el',\n 'en',\n 'es',\n 'et',\n 'eu',\n 'fa',\n 'ff',\n 'fi',\n 'fr',\n 'ga',\n 'gl',\n 'gu',\n 'ha',\n 'he',\n 'hi',\n 'hr',\n 'hu',\n 'hy',\n 'id',\n 'ig',\n 'is',\n 'it',\n 'ja',\n 'jv',\n 'ka',\n 'kk',\n 'km',\n 'kn',\n 'ko',\n 'ku',\n 'ky',\n 'lb',\n 'lg',\n 'ln',\n 'lo',\n 'lt',\n 'lv',\n 'mi',\n 'mk',\n 'ml',\n 'mn',\n 'mr',\n 'ms',\n 'mt',\n 'my',\n 'ne',\n 'nl',\n 'no',\n 'ny',\n 'oc',\n 'or',\n 'pa',\n 'pl',\n 'ps',\n 'pt',\n 'ro',\n 'ru',\n 'sd',\n 'sk',\n 'sl',\n 'sn',\n 'so',\n 'sq',\n 'sr',\n 'sv',\n 'sw',\n 'ta',\n 'te',\n 'tg',\n 'th',\n 'tl',\n 'tr',\n 'uk',\n 'ur',\n 'uz',\n 'vi',\n 'wo',\n 'xh',\n 'yo',\n 'zh',\n 'zu',\n] as const;\n\nexport type KnownLanguageCode = (typeof KNOWN_LANGUAGE_CODES)[number];\n\ndeclare const languageCodeBrand: unique symbol;\n\nexport type LanguageCode = string & { readonly [languageCodeBrand]: 'LanguageCode' };\n\nexport function asLanguageCode(language: string): LanguageCode {\n return language as LanguageCode;\n}\n\nconst ISO_639_3_TO_1: Record<string, string | undefined> = {\n afr: 'af',\n amh: 'am',\n ara: 'ar',\n hye: 'hy',\n asm: 'as',\n ast: undefined,\n aze: 'az',\n bel: 'be',\n ben: 'bn',\n bos: 'bs',\n bul: 'bg',\n mya: 'my',\n yue: undefined,\n cat: 'ca',\n ceb: undefined,\n cmn: 'zh',\n nya: 'ny',\n hrv: 'hr',\n ces: 'cs',\n dan: 'da',\n nld: 'nl',\n eng: 'en',\n est: 'et',\n fil: undefined,\n fin: 'fi',\n fra: 'fr',\n ful: 'ff',\n glg: 'gl',\n lug: 'lg',\n kat: 'ka',\n deu: 'de',\n ell: 'el',\n guj: 'gu',\n hau: 'ha',\n heb: 'he',\n hin: 'hi',\n hun: 'hu',\n isl: 'is',\n ibo: 'ig',\n ind: 'id',\n gle: 'ga',\n ita: 'it',\n jpn: 'ja',\n jav: 'jv',\n kea: undefined,\n kan: 'kn',\n kaz: 'kk',\n khm: 'km',\n kor: 'ko',\n kur: 'ku',\n kir: 'ky',\n lao: 'lo',\n lav: 'lv',\n lin: 'ln',\n lit: 'lt',\n luo: undefined,\n ltz: 'lb',\n mkd: 'mk',\n msa: 'ms',\n mal: 'ml',\n mlt: 'mt',\n zho: 'zh',\n mri: 'mi',\n mar: 'mr',\n mon: 'mn',\n nep: 'ne',\n nso: undefined,\n nor: 'no',\n oci: 'oc',\n ori: 'or',\n pus: 'ps',\n fas: 'fa',\n pol: 'pl',\n por: 'pt',\n pan: 'pa',\n ron: 'ro',\n rus: 'ru',\n srp: 'sr',\n sna: 'sn',\n snd: 'sd',\n slk: 'sk',\n slv: 'sl',\n som: 'so',\n spa: 'es',\n swa: 'sw',\n swe: 'sv',\n tam: 'ta',\n tgk: 'tg',\n tel: 'te',\n tha: 'th',\n tur: 'tr',\n ukr: 'uk',\n umb: undefined,\n urd: 'ur',\n uzb: 'uz',\n vie: 'vi',\n cym: 'cy',\n wol: 'wo',\n xho: 'xh',\n zul: 'zu',\n};\n\nconst LANGUAGE_NAMES_TO_CODE: Record<string, string> = {\n afrikaans: 'af',\n albanian: 'sq',\n amharic: 'am',\n arabic: 'ar',\n armenian: 'hy',\n azerbaijani: 'az',\n basque: 'eu',\n belarusian: 'be',\n bengali: 'bn',\n bosnian: 'bs',\n bulgarian: 'bg',\n burmese: 'my',\n catalan: 'ca',\n chinese: 'zh',\n croatian: 'hr',\n czech: 'cs',\n danish: 'da',\n dutch: 'nl',\n english: 'en',\n estonian: 'et',\n finnish: 'fi',\n french: 'fr',\n galician: 'gl',\n georgian: 'ka',\n german: 'de',\n greek: 'el',\n gujarati: 'gu',\n hausa: 'ha',\n hebrew: 'he',\n hindi: 'hi',\n hungarian: 'hu',\n icelandic: 'is',\n indonesian: 'id',\n irish: 'ga',\n italian: 'it',\n japanese: 'ja',\n javanese: 'jv',\n kannada: 'kn',\n kazakh: 'kk',\n khmer: 'km',\n korean: 'ko',\n kurdish: 'ku',\n kyrgyz: 'ky',\n lao: 'lo',\n latvian: 'lv',\n lingala: 'ln',\n lithuanian: 'lt',\n luxembourgish: 'lb',\n macedonian: 'mk',\n malay: 'ms',\n malayalam: 'ml',\n maltese: 'mt',\n maori: 'mi',\n marathi: 'mr',\n mongolian: 'mn',\n nepali: 'ne',\n norwegian: 'no',\n occitan: 'oc',\n oriya: 'or',\n pashto: 'ps',\n persian: 'fa',\n polish: 'pl',\n portuguese: 'pt',\n punjabi: 'pa',\n romanian: 'ro',\n russian: 'ru',\n serbian: 'sr',\n shona: 'sn',\n sindhi: 'sd',\n slovak: 'sk',\n slovene: 'sl',\n slovenian: 'sl',\n somali: 'so',\n spanish: 'es',\n swahili: 'sw',\n swedish: 'sv',\n tagalog: 'tl',\n tamil: 'ta',\n tajik: 'tg',\n telugu: 'te',\n thai: 'th',\n turkish: 'tr',\n ukrainian: 'uk',\n urdu: 'ur',\n uzbek: 'uz',\n vietnamese: 'vi',\n welsh: 'cy',\n wolof: 'wo',\n xhosa: 'xh',\n yoruba: 'yo',\n zulu: 'zu',\n};\n\nconst CODE_TO_LANGUAGE_NAME: Record<string, string> = Object.fromEntries(\n Object.entries(LANGUAGE_NAMES_TO_CODE).map(([name, code]) => [code, name]),\n);\n\nCODE_TO_LANGUAGE_NAME.sl = 'slovene';\n\nexport function normalizeLanguage(language: string): LanguageCode {\n const lowered = language.trim().toLowerCase();\n if (lowered === '') {\n return asLanguageCode('');\n }\n\n if (lowered in LANGUAGE_NAMES_TO_CODE) {\n return asLanguageCode(LANGUAGE_NAMES_TO_CODE[lowered]!);\n }\n\n if (lowered in ISO_639_3_TO_1) {\n return asLanguageCode(ISO_639_3_TO_1[lowered] ?? lowered);\n }\n\n const parts = lowered.replaceAll('_', '-').split('-');\n if (parts.length >= 2) {\n const [base, ...rest] = parts;\n return asLanguageCode(\n [\n base,\n ...rest.map((part) => {\n if (part.length === 4) {\n return part.charAt(0).toUpperCase() + part.slice(1);\n }\n return part.toUpperCase();\n }),\n ].join('-'),\n );\n }\n\n return asLanguageCode(lowered);\n}\n\nexport function getBaseLanguage(language: string): string {\n const normalized = normalizeLanguage(language);\n const [base = ''] = normalized.split('-');\n return ISO_639_3_TO_1[base] ?? base;\n}\n\nexport function getIsoLanguage(language: string): string {\n const normalized = normalizeLanguage(language);\n const region = getLanguageRegion(normalized);\n const baseLanguage = getBaseLanguage(normalized);\n return region ? `${baseLanguage}-${region}` : baseLanguage;\n}\n\nexport function getLanguageRegion(language: string): string | undefined {\n const normalized = normalizeLanguage(language);\n const [, ...parts] = normalized.split('-');\n return parts.find((part) => part.length === 2);\n}\n\nexport function toLanguageName(language: string): string | undefined {\n return CODE_TO_LANGUAGE_NAME[getBaseLanguage(language)];\n}\n\nexport function areLanguagesEquivalent(\n left: string | null | undefined,\n right: string | null | undefined,\n): boolean {\n if (left == null || right == null) {\n return left === right;\n }\n return normalizeLanguage(left) === normalizeLanguage(right);\n}\n"],"mappings":"AAIO,MAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQO,SAAS,eAAe,UAAgC;AAC7D,SAAO;AACT;AAEA,MAAM,iBAAqD;AAAA,EACzD,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAEA,MAAM,yBAAiD;AAAA,EACrD,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,SAAS;AAAA,EACT,UAAU;AAAA,EACV,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,UAAU;AAAA,EACV,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,WAAW;AAAA,EACX,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,SAAS;AAAA,EACT,UAAU;AAAA,EACV,UAAU;AAAA,EACV,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,WAAW;AAAA,EACX,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS;AAAA,EACT,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,SAAS;AAAA,EACT,WAAW;AAAA,EACX,MAAM;AAAA,EACN,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AACR;AAEA,MAAM,wBAAgD,OAAO;AAAA,EAC3D,OAAO,QAAQ,sBAAsB,EAAE,IAAI,CAAC,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC;AAC3E;AAEA,sBAAsB,KAAK;AAEpB,SAAS,kBAAkB,UAAgC;AAChE,QAAM,UAAU,SAAS,KAAK,EAAE,YAAY;AAC5C,MAAI,YAAY,IAAI;AAClB,WAAO,eAAe,EAAE;AAAA,EAC1B;AAEA,MAAI,WAAW,wBAAwB;AACrC,WAAO,eAAe,uBAAuB,OAAO,CAAE;AAAA,EACxD;AAEA,MAAI,WAAW,gBAAgB;AAC7B,WAAO,eAAe,eAAe,OAAO,KAAK,OAAO;AAAA,EAC1D;AAEA,QAAM,QAAQ,QAAQ,WAAW,KAAK,GAAG,EAAE,MAAM,GAAG;AACpD,MAAI,MAAM,UAAU,GAAG;AACrB,UAAM,CAAC,MAAM,GAAG,IAAI,IAAI;AACxB,WAAO;AAAA,MACL;AAAA,QACE;AAAA,QACA,GAAG,KAAK,IAAI,CAAC,SAAS;AACpB,cAAI,KAAK,WAAW,GAAG;AACrB,mBAAO,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC;AAAA,UACpD;AACA,iBAAO,KAAK,YAAY;AAAA,QAC1B,CAAC;AAAA,MACH,EAAE,KAAK,GAAG;AAAA,IACZ;AAAA,EACF;AAEA,SAAO,eAAe,OAAO;AAC/B;AAEO,SAAS,gBAAgB,UAA0B;AACxD,QAAM,aAAa,kBAAkB,QAAQ;AAC7C,QAAM,CAAC,OAAO,EAAE,IAAI,WAAW,MAAM,GAAG;AACxC,SAAO,eAAe,IAAI,KAAK;AACjC;AAEO,SAAS,eAAe,UAA0B;AACvD,QAAM,aAAa,kBAAkB,QAAQ;AAC7C,QAAM,SAAS,kBAAkB,UAAU;AAC3C,QAAM,eAAe,gBAAgB,UAAU;AAC/C,SAAO,SAAS,GAAG,YAAY,IAAI,MAAM,KAAK;AAChD;AAEO,SAAS,kBAAkB,UAAsC;AACtE,QAAM,aAAa,kBAAkB,QAAQ;AAC7C,QAAM,CAAC,EAAE,GAAG,KAAK,IAAI,WAAW,MAAM,GAAG;AACzC,SAAO,MAAM,KAAK,CAAC,SAAS,KAAK,WAAW,CAAC;AAC/C;AAEO,SAAS,eAAe,UAAsC;AACnE,SAAO,sBAAsB,gBAAgB,QAAQ,CAAC;AACxD;AAEO,SAAS,uBACd,MACA,OACS;AACT,MAAI,QAAQ,QAAQ,SAAS,MAAM;AACjC,WAAO,SAAS;AAAA,EAClB;AACA,SAAO,kBAAkB,IAAI,MAAM,kBAAkB,KAAK;AAC5D;","names":[]}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var import_vitest = require("vitest");
3
+ var import_language = require("./language.cjs");
4
+ (0, import_vitest.describe)("normalizeLanguage", () => {
5
+ (0, import_vitest.it)("normalizes language names", () => {
6
+ (0, import_vitest.expect)((0, import_language.normalizeLanguage)("english")).toBe("en");
7
+ });
8
+ (0, import_vitest.it)("normalizes iso 639-3 codes", () => {
9
+ (0, import_vitest.expect)((0, import_language.normalizeLanguage)("eng")).toBe("en");
10
+ });
11
+ (0, import_vitest.it)("normalizes bcp-47 casing and separators", () => {
12
+ (0, import_vitest.expect)((0, import_language.normalizeLanguage)("en_us")).toBe("en-US");
13
+ (0, import_vitest.expect)((0, import_language.normalizeLanguage)("zh_hans_cn")).toBe("zh-Hans-CN");
14
+ });
15
+ (0, import_vitest.it)("preserves iso 639-3 in compound tags", () => {
16
+ (0, import_vitest.expect)((0, import_language.normalizeLanguage)("cmn_hans_cn")).toBe("cmn-Hans-CN");
17
+ });
18
+ (0, import_vitest.it)("passes unknown codes through in lowercase", () => {
19
+ (0, import_vitest.expect)((0, import_language.normalizeLanguage)("MULTI")).toBe("multi");
20
+ });
21
+ (0, import_vitest.it)("preserves empty string sentinel", () => {
22
+ (0, import_vitest.expect)((0, import_language.normalizeLanguage)("")).toBe("");
23
+ });
24
+ });
25
+ (0, import_vitest.describe)("language helpers", () => {
26
+ (0, import_vitest.it)("extracts base language", () => {
27
+ (0, import_vitest.expect)((0, import_language.getBaseLanguage)("cmn-Hans-CN")).toBe("zh");
28
+ });
29
+ (0, import_vitest.it)("builds iso language tag", () => {
30
+ (0, import_vitest.expect)((0, import_language.getIsoLanguage)("cmn-Hans-CN")).toBe("zh-CN");
31
+ });
32
+ (0, import_vitest.it)("extracts region", () => {
33
+ (0, import_vitest.expect)((0, import_language.getLanguageRegion)("en-US")).toBe("US");
34
+ });
35
+ (0, import_vitest.it)("maps normalized code back to language name", () => {
36
+ (0, import_vitest.expect)((0, import_language.toLanguageName)("eng")).toBe("english");
37
+ });
38
+ (0, import_vitest.it)("compares equivalent representations", () => {
39
+ (0, import_vitest.expect)((0, import_language.areLanguagesEquivalent)("english", "en")).toBe(true);
40
+ (0, import_vitest.expect)((0, import_language.areLanguagesEquivalent)("en_us", "en-US")).toBe(true);
41
+ });
42
+ });
43
+ //# sourceMappingURL=language.test.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/language.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { describe, expect, it } from 'vitest';\nimport {\n areLanguagesEquivalent,\n getBaseLanguage,\n getIsoLanguage,\n getLanguageRegion,\n normalizeLanguage,\n toLanguageName,\n} from './language.js';\n\ndescribe('normalizeLanguage', () => {\n it('normalizes language names', () => {\n expect(normalizeLanguage('english')).toBe('en');\n });\n\n it('normalizes iso 639-3 codes', () => {\n expect(normalizeLanguage('eng')).toBe('en');\n });\n\n it('normalizes bcp-47 casing and separators', () => {\n expect(normalizeLanguage('en_us')).toBe('en-US');\n expect(normalizeLanguage('zh_hans_cn')).toBe('zh-Hans-CN');\n });\n\n it('preserves iso 639-3 in compound tags', () => {\n expect(normalizeLanguage('cmn_hans_cn')).toBe('cmn-Hans-CN');\n });\n\n it('passes unknown codes through in lowercase', () => {\n expect(normalizeLanguage('MULTI')).toBe('multi');\n });\n\n it('preserves empty string sentinel', () => {\n expect(normalizeLanguage('')).toBe('');\n });\n});\n\ndescribe('language helpers', () => {\n it('extracts base language', () => {\n expect(getBaseLanguage('cmn-Hans-CN')).toBe('zh');\n });\n\n it('builds iso language tag', () => {\n expect(getIsoLanguage('cmn-Hans-CN')).toBe('zh-CN');\n });\n\n it('extracts region', () => {\n expect(getLanguageRegion('en-US')).toBe('US');\n });\n\n it('maps normalized code back to language name', () => {\n expect(toLanguageName('eng')).toBe('english');\n });\n\n it('compares equivalent representations', () => {\n expect(areLanguagesEquivalent('english', 'en')).toBe(true);\n expect(areLanguagesEquivalent('en_us', 'en-US')).toBe(true);\n });\n});\n"],"mappings":";AAGA,oBAAqC;AACrC,sBAOO;AAAA,IAEP,wBAAS,qBAAqB,MAAM;AAClC,wBAAG,6BAA6B,MAAM;AACpC,kCAAO,mCAAkB,SAAS,CAAC,EAAE,KAAK,IAAI;AAAA,EAChD,CAAC;AAED,wBAAG,8BAA8B,MAAM;AACrC,kCAAO,mCAAkB,KAAK,CAAC,EAAE,KAAK,IAAI;AAAA,EAC5C,CAAC;AAED,wBAAG,2CAA2C,MAAM;AAClD,kCAAO,mCAAkB,OAAO,CAAC,EAAE,KAAK,OAAO;AAC/C,kCAAO,mCAAkB,YAAY,CAAC,EAAE,KAAK,YAAY;AAAA,EAC3D,CAAC;AAED,wBAAG,wCAAwC,MAAM;AAC/C,kCAAO,mCAAkB,aAAa,CAAC,EAAE,KAAK,aAAa;AAAA,EAC7D,CAAC;AAED,wBAAG,6CAA6C,MAAM;AACpD,kCAAO,mCAAkB,OAAO,CAAC,EAAE,KAAK,OAAO;AAAA,EACjD,CAAC;AAED,wBAAG,mCAAmC,MAAM;AAC1C,kCAAO,mCAAkB,EAAE,CAAC,EAAE,KAAK,EAAE;AAAA,EACvC,CAAC;AACH,CAAC;AAAA,IAED,wBAAS,oBAAoB,MAAM;AACjC,wBAAG,0BAA0B,MAAM;AACjC,kCAAO,iCAAgB,aAAa,CAAC,EAAE,KAAK,IAAI;AAAA,EAClD,CAAC;AAED,wBAAG,2BAA2B,MAAM;AAClC,kCAAO,gCAAe,aAAa,CAAC,EAAE,KAAK,OAAO;AAAA,EACpD,CAAC;AAED,wBAAG,mBAAmB,MAAM;AAC1B,kCAAO,mCAAkB,OAAO,CAAC,EAAE,KAAK,IAAI;AAAA,EAC9C,CAAC;AAED,wBAAG,8CAA8C,MAAM;AACrD,kCAAO,gCAAe,KAAK,CAAC,EAAE,KAAK,SAAS;AAAA,EAC9C,CAAC;AAED,wBAAG,uCAAuC,MAAM;AAC9C,kCAAO,wCAAuB,WAAW,IAAI,CAAC,EAAE,KAAK,IAAI;AACzD,kCAAO,wCAAuB,SAAS,OAAO,CAAC,EAAE,KAAK,IAAI;AAAA,EAC5D,CAAC;AACH,CAAC;","names":[]}
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ areLanguagesEquivalent,
4
+ getBaseLanguage,
5
+ getIsoLanguage,
6
+ getLanguageRegion,
7
+ normalizeLanguage,
8
+ toLanguageName
9
+ } from "./language.js";
10
+ describe("normalizeLanguage", () => {
11
+ it("normalizes language names", () => {
12
+ expect(normalizeLanguage("english")).toBe("en");
13
+ });
14
+ it("normalizes iso 639-3 codes", () => {
15
+ expect(normalizeLanguage("eng")).toBe("en");
16
+ });
17
+ it("normalizes bcp-47 casing and separators", () => {
18
+ expect(normalizeLanguage("en_us")).toBe("en-US");
19
+ expect(normalizeLanguage("zh_hans_cn")).toBe("zh-Hans-CN");
20
+ });
21
+ it("preserves iso 639-3 in compound tags", () => {
22
+ expect(normalizeLanguage("cmn_hans_cn")).toBe("cmn-Hans-CN");
23
+ });
24
+ it("passes unknown codes through in lowercase", () => {
25
+ expect(normalizeLanguage("MULTI")).toBe("multi");
26
+ });
27
+ it("preserves empty string sentinel", () => {
28
+ expect(normalizeLanguage("")).toBe("");
29
+ });
30
+ });
31
+ describe("language helpers", () => {
32
+ it("extracts base language", () => {
33
+ expect(getBaseLanguage("cmn-Hans-CN")).toBe("zh");
34
+ });
35
+ it("builds iso language tag", () => {
36
+ expect(getIsoLanguage("cmn-Hans-CN")).toBe("zh-CN");
37
+ });
38
+ it("extracts region", () => {
39
+ expect(getLanguageRegion("en-US")).toBe("US");
40
+ });
41
+ it("maps normalized code back to language name", () => {
42
+ expect(toLanguageName("eng")).toBe("english");
43
+ });
44
+ it("compares equivalent representations", () => {
45
+ expect(areLanguagesEquivalent("english", "en")).toBe(true);
46
+ expect(areLanguagesEquivalent("en_us", "en-US")).toBe(true);
47
+ });
48
+ });
49
+ //# sourceMappingURL=language.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/language.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { describe, expect, it } from 'vitest';\nimport {\n areLanguagesEquivalent,\n getBaseLanguage,\n getIsoLanguage,\n getLanguageRegion,\n normalizeLanguage,\n toLanguageName,\n} from './language.js';\n\ndescribe('normalizeLanguage', () => {\n it('normalizes language names', () => {\n expect(normalizeLanguage('english')).toBe('en');\n });\n\n it('normalizes iso 639-3 codes', () => {\n expect(normalizeLanguage('eng')).toBe('en');\n });\n\n it('normalizes bcp-47 casing and separators', () => {\n expect(normalizeLanguage('en_us')).toBe('en-US');\n expect(normalizeLanguage('zh_hans_cn')).toBe('zh-Hans-CN');\n });\n\n it('preserves iso 639-3 in compound tags', () => {\n expect(normalizeLanguage('cmn_hans_cn')).toBe('cmn-Hans-CN');\n });\n\n it('passes unknown codes through in lowercase', () => {\n expect(normalizeLanguage('MULTI')).toBe('multi');\n });\n\n it('preserves empty string sentinel', () => {\n expect(normalizeLanguage('')).toBe('');\n });\n});\n\ndescribe('language helpers', () => {\n it('extracts base language', () => {\n expect(getBaseLanguage('cmn-Hans-CN')).toBe('zh');\n });\n\n it('builds iso language tag', () => {\n expect(getIsoLanguage('cmn-Hans-CN')).toBe('zh-CN');\n });\n\n it('extracts region', () => {\n expect(getLanguageRegion('en-US')).toBe('US');\n });\n\n it('maps normalized code back to language name', () => {\n expect(toLanguageName('eng')).toBe('english');\n });\n\n it('compares equivalent representations', () => {\n expect(areLanguagesEquivalent('english', 'en')).toBe(true);\n expect(areLanguagesEquivalent('en_us', 'en-US')).toBe(true);\n });\n});\n"],"mappings":"AAGA,SAAS,UAAU,QAAQ,UAAU;AACrC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,qBAAqB,MAAM;AAClC,KAAG,6BAA6B,MAAM;AACpC,WAAO,kBAAkB,SAAS,CAAC,EAAE,KAAK,IAAI;AAAA,EAChD,CAAC;AAED,KAAG,8BAA8B,MAAM;AACrC,WAAO,kBAAkB,KAAK,CAAC,EAAE,KAAK,IAAI;AAAA,EAC5C,CAAC;AAED,KAAG,2CAA2C,MAAM;AAClD,WAAO,kBAAkB,OAAO,CAAC,EAAE,KAAK,OAAO;AAC/C,WAAO,kBAAkB,YAAY,CAAC,EAAE,KAAK,YAAY;AAAA,EAC3D,CAAC;AAED,KAAG,wCAAwC,MAAM;AAC/C,WAAO,kBAAkB,aAAa,CAAC,EAAE,KAAK,aAAa;AAAA,EAC7D,CAAC;AAED,KAAG,6CAA6C,MAAM;AACpD,WAAO,kBAAkB,OAAO,CAAC,EAAE,KAAK,OAAO;AAAA,EACjD,CAAC;AAED,KAAG,mCAAmC,MAAM;AAC1C,WAAO,kBAAkB,EAAE,CAAC,EAAE,KAAK,EAAE;AAAA,EACvC,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB,MAAM;AACjC,KAAG,0BAA0B,MAAM;AACjC,WAAO,gBAAgB,aAAa,CAAC,EAAE,KAAK,IAAI;AAAA,EAClD,CAAC;AAED,KAAG,2BAA2B,MAAM;AAClC,WAAO,eAAe,aAAa,CAAC,EAAE,KAAK,OAAO;AAAA,EACpD,CAAC;AAED,KAAG,mBAAmB,MAAM;AAC1B,WAAO,kBAAkB,OAAO,CAAC,EAAE,KAAK,IAAI;AAAA,EAC9C,CAAC;AAED,KAAG,8CAA8C,MAAM;AACrD,WAAO,eAAe,KAAK,CAAC,EAAE,KAAK,SAAS;AAAA,EAC9C,CAAC;AAED,KAAG,uCAAuC,MAAM;AAC9C,WAAO,uBAAuB,WAAW,IAAI,CAAC,EAAE,KAAK,IAAI;AACzD,WAAO,uBAAuB,SAAS,OAAO,CAAC,EAAE,KAAK,IAAI;AAAA,EAC5D,CAAC;AACH,CAAC;","names":[]}
@@ -28,9 +28,13 @@ function isStreamReaderReleaseError(e) {
28
28
  "Invalid state: Releasing reader",
29
29
  "Invalid state: The reader is not attached to a stream",
30
30
  "Controller is already closed",
31
- "WritableStream is closed"
31
+ "WritableStream is closed",
32
+ // bun
33
+ "Stream reader cancelled via releaseLock()",
34
+ // deno
35
+ "The reader was released."
32
36
  ];
33
- if (e instanceof TypeError) {
37
+ if (e instanceof TypeError || e instanceof Error && e.name === "AbortError") {
34
38
  return allowedMessages.some((message) => e.message.includes(message));
35
39
  }
36
40
  return false;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/stream/deferred_stream.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type {\n ReadableStream,\n ReadableStreamDefaultReader,\n WritableStreamDefaultWriter,\n} from 'node:stream/web';\nimport { IdentityTransform } from './identity_transform.js';\n\n/**\n * Check if error is related to stream cleanup operations.\n *\n * These errors are expected when calling reader.read() after releaseLock()\n * or when writing to already closed streams during cleanup:\n *\n * Invalid state: Releasing reader\n * Invalid state: The reader is not attached to a stream\n * Invalid state: Controller is already closed\n * Invalid state: WritableStream is closed\n */\nexport function isStreamReaderReleaseError(e: unknown) {\n const allowedMessages = [\n 'Invalid state: Releasing reader',\n 'Invalid state: The reader is not attached to a stream',\n 'Controller is already closed',\n 'WritableStream is closed',\n ];\n\n if (e instanceof TypeError) {\n return allowedMessages.some((message) => e.message.includes(message));\n }\n\n return false;\n}\nexport class DeferredReadableStream<T> {\n private transform: IdentityTransform<T>;\n private writer: WritableStreamDefaultWriter<T>;\n private sourceReader?: ReadableStreamDefaultReader<T>;\n\n constructor() {\n this.transform = new IdentityTransform<T>();\n this.writer = this.transform.writable.getWriter();\n }\n\n get stream() {\n return this.transform.readable;\n }\n\n get isSourceSet() {\n return !!this.sourceReader;\n }\n\n /**\n * Call once the actual source is ready.\n */\n setSource(source: ReadableStream<T>) {\n if (this.isSourceSet) {\n throw new Error('Stream source already set');\n }\n\n const sourceReader = source.getReader();\n this.sourceReader = sourceReader;\n void this.pump(sourceReader);\n }\n\n private async pump(sourceReader: ReadableStreamDefaultReader<T>) {\n let sourceError: unknown;\n\n try {\n while (true) {\n const { done, value } = await sourceReader.read();\n if (done) break;\n await this.writer.write(value);\n }\n } catch (e) {\n // skip stream cleanup related errors\n if (isStreamReaderReleaseError(e)) return;\n\n sourceError = e;\n } finally {\n // any other error from source will be propagated to the consumer\n if (sourceError) {\n try {\n await this.writer.abort(sourceError);\n } catch (e) {\n // ignore if writer is already closed\n }\n return;\n }\n\n // release lock so this.stream.getReader().read() will terminate with done: true\n try {\n this.writer.releaseLock();\n } catch (e) {\n // ignore if writer lock is already released\n }\n\n // we only close the writable stream after done\n try {\n await this.transform.writable.close();\n // NOTE: we do not cancel this.transform.readable as there might be access to\n // this.transform.readable.getReader() outside that blocks this cancellation\n // hence, user is responsible for canceling reader on their own\n } catch (e) {\n // ignore TypeError: Invalid state: WritableStream is closed\n // in case stream reader is already closed, this will throw\n // but we ignore it as we are closing the stream anyway\n }\n }\n }\n\n /**\n * Detach the source stream and clean up resources.\n */\n async detachSource() {\n if (!this.isSourceSet) {\n // No-op if source was never set - this is a common case during cleanup\n return;\n }\n\n const sourceReader = this.sourceReader!;\n // Clear source first so future setSource() calls can reattach cleanly.\n this.sourceReader = undefined;\n\n // release lock will make any pending read() throw TypeError\n // which are expected, and we intentionally catch those error\n // using isStreamReaderReleaseError\n // this will unblock any pending read() inside the async for loop\n try {\n sourceReader.releaseLock();\n } catch (e) {\n if (!isStreamReaderReleaseError(e)) {\n throw e;\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,gCAAkC;AAa3B,SAAS,2BAA2B,GAAY;AACrD,QAAM,kBAAkB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,aAAa,WAAW;AAC1B,WAAO,gBAAgB,KAAK,CAAC,YAAY,EAAE,QAAQ,SAAS,OAAO,CAAC;AAAA,EACtE;AAEA,SAAO;AACT;AACO,MAAM,uBAA0B;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EAER,cAAc;AACZ,SAAK,YAAY,IAAI,4CAAqB;AAC1C,SAAK,SAAS,KAAK,UAAU,SAAS,UAAU;AAAA,EAClD;AAAA,EAEA,IAAI,SAAS;AACX,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA,EAEA,IAAI,cAAc;AAChB,WAAO,CAAC,CAAC,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAA2B;AACnC,QAAI,KAAK,aAAa;AACpB,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AAEA,UAAM,eAAe,OAAO,UAAU;AACtC,SAAK,eAAe;AACpB,SAAK,KAAK,KAAK,YAAY;AAAA,EAC7B;AAAA,EAEA,MAAc,KAAK,cAA8C;AAC/D,QAAI;AAEJ,QAAI;AACF,aAAO,MAAM;AACX,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,aAAa,KAAK;AAChD,YAAI,KAAM;AACV,cAAM,KAAK,OAAO,MAAM,KAAK;AAAA,MAC/B;AAAA,IACF,SAAS,GAAG;AAEV,UAAI,2BAA2B,CAAC,EAAG;AAEnC,oBAAc;AAAA,IAChB,UAAE;AAEA,UAAI,aAAa;AACf,YAAI;AACF,gBAAM,KAAK,OAAO,MAAM,WAAW;AAAA,QACrC,SAAS,GAAG;AAAA,QAEZ;AACA;AAAA,MACF;AAGA,UAAI;AACF,aAAK,OAAO,YAAY;AAAA,MAC1B,SAAS,GAAG;AAAA,MAEZ;AAGA,UAAI;AACF,cAAM,KAAK,UAAU,SAAS,MAAM;AAAA,MAItC,SAAS,GAAG;AAAA,MAIZ;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe;AACnB,QAAI,CAAC,KAAK,aAAa;AAErB;AAAA,IACF;AAEA,UAAM,eAAe,KAAK;AAE1B,SAAK,eAAe;AAMpB,QAAI;AACF,mBAAa,YAAY;AAAA,IAC3B,SAAS,GAAG;AACV,UAAI,CAAC,2BAA2B,CAAC,GAAG;AAClC,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/stream/deferred_stream.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type {\n ReadableStream,\n ReadableStreamDefaultReader,\n WritableStreamDefaultWriter,\n} from 'node:stream/web';\nimport { IdentityTransform } from './identity_transform.js';\n\n/**\n * Check if error is related to stream cleanup operations.\n *\n * These errors are expected when calling reader.read() after releaseLock()\n * or when writing to already closed streams during cleanup:\n *\n * Invalid state: Releasing reader\n * Invalid state: The reader is not attached to a stream\n * Invalid state: Controller is already closed\n * Invalid state: WritableStream is closed\n */\nexport function isStreamReaderReleaseError(e: unknown) {\n const allowedMessages = [\n 'Invalid state: Releasing reader',\n 'Invalid state: The reader is not attached to a stream',\n 'Controller is already closed',\n 'WritableStream is closed',\n // bun\n 'Stream reader cancelled via releaseLock()',\n // deno\n 'The reader was released.',\n ];\n\n if (e instanceof TypeError || (e instanceof Error && e.name === 'AbortError')) {\n return allowedMessages.some((message) => e.message.includes(message));\n }\n\n return false;\n}\nexport class DeferredReadableStream<T> {\n private transform: IdentityTransform<T>;\n private writer: WritableStreamDefaultWriter<T>;\n private sourceReader?: ReadableStreamDefaultReader<T>;\n\n constructor() {\n this.transform = new IdentityTransform<T>();\n this.writer = this.transform.writable.getWriter();\n }\n\n get stream() {\n return this.transform.readable;\n }\n\n get isSourceSet() {\n return !!this.sourceReader;\n }\n\n /**\n * Call once the actual source is ready.\n */\n setSource(source: ReadableStream<T>) {\n if (this.isSourceSet) {\n throw new Error('Stream source already set');\n }\n\n const sourceReader = source.getReader();\n this.sourceReader = sourceReader;\n void this.pump(sourceReader);\n }\n\n private async pump(sourceReader: ReadableStreamDefaultReader<T>) {\n let sourceError: unknown;\n\n try {\n while (true) {\n const { done, value } = await sourceReader.read();\n if (done) break;\n await this.writer.write(value);\n }\n } catch (e) {\n // skip stream cleanup related errors\n if (isStreamReaderReleaseError(e)) return;\n\n sourceError = e;\n } finally {\n // any other error from source will be propagated to the consumer\n if (sourceError) {\n try {\n await this.writer.abort(sourceError);\n } catch (e) {\n // ignore if writer is already closed\n }\n return;\n }\n\n // release lock so this.stream.getReader().read() will terminate with done: true\n try {\n this.writer.releaseLock();\n } catch (e) {\n // ignore if writer lock is already released\n }\n\n // we only close the writable stream after done\n try {\n await this.transform.writable.close();\n // NOTE: we do not cancel this.transform.readable as there might be access to\n // this.transform.readable.getReader() outside that blocks this cancellation\n // hence, user is responsible for canceling reader on their own\n } catch (e) {\n // ignore TypeError: Invalid state: WritableStream is closed\n // in case stream reader is already closed, this will throw\n // but we ignore it as we are closing the stream anyway\n }\n }\n }\n\n /**\n * Detach the source stream and clean up resources.\n */\n async detachSource() {\n if (!this.isSourceSet) {\n // No-op if source was never set - this is a common case during cleanup\n return;\n }\n\n const sourceReader = this.sourceReader!;\n // Clear source first so future setSource() calls can reattach cleanly.\n this.sourceReader = undefined;\n\n // release lock will make any pending read() throw TypeError\n // which are expected, and we intentionally catch those error\n // using isStreamReaderReleaseError\n // this will unblock any pending read() inside the async for loop\n try {\n sourceReader.releaseLock();\n } catch (e) {\n if (!isStreamReaderReleaseError(e)) {\n throw e;\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,gCAAkC;AAa3B,SAAS,2BAA2B,GAAY;AACrD,QAAM,kBAAkB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,EACF;AAEA,MAAI,aAAa,aAAc,aAAa,SAAS,EAAE,SAAS,cAAe;AAC7E,WAAO,gBAAgB,KAAK,CAAC,YAAY,EAAE,QAAQ,SAAS,OAAO,CAAC;AAAA,EACtE;AAEA,SAAO;AACT;AACO,MAAM,uBAA0B;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EAER,cAAc;AACZ,SAAK,YAAY,IAAI,4CAAqB;AAC1C,SAAK,SAAS,KAAK,UAAU,SAAS,UAAU;AAAA,EAClD;AAAA,EAEA,IAAI,SAAS;AACX,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA,EAEA,IAAI,cAAc;AAChB,WAAO,CAAC,CAAC,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAA2B;AACnC,QAAI,KAAK,aAAa;AACpB,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AAEA,UAAM,eAAe,OAAO,UAAU;AACtC,SAAK,eAAe;AACpB,SAAK,KAAK,KAAK,YAAY;AAAA,EAC7B;AAAA,EAEA,MAAc,KAAK,cAA8C;AAC/D,QAAI;AAEJ,QAAI;AACF,aAAO,MAAM;AACX,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,aAAa,KAAK;AAChD,YAAI,KAAM;AACV,cAAM,KAAK,OAAO,MAAM,KAAK;AAAA,MAC/B;AAAA,IACF,SAAS,GAAG;AAEV,UAAI,2BAA2B,CAAC,EAAG;AAEnC,oBAAc;AAAA,IAChB,UAAE;AAEA,UAAI,aAAa;AACf,YAAI;AACF,gBAAM,KAAK,OAAO,MAAM,WAAW;AAAA,QACrC,SAAS,GAAG;AAAA,QAEZ;AACA;AAAA,MACF;AAGA,UAAI;AACF,aAAK,OAAO,YAAY;AAAA,MAC1B,SAAS,GAAG;AAAA,MAEZ;AAGA,UAAI;AACF,cAAM,KAAK,UAAU,SAAS,MAAM;AAAA,MAItC,SAAS,GAAG;AAAA,MAIZ;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe;AACnB,QAAI,CAAC,KAAK,aAAa;AAErB;AAAA,IACF;AAEA,UAAM,eAAe,KAAK;AAE1B,SAAK,eAAe;AAMpB,QAAI;AACF,mBAAa,YAAY;AAAA,IAC3B,SAAS,GAAG;AACV,UAAI,CAAC,2BAA2B,CAAC,GAAG;AAClC,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"deferred_stream.d.ts","sourceRoot":"","sources":["../../src/stream/deferred_stream.ts"],"names":[],"mappings":";AAGA,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,iBAAiB,CAAC;AAGzB;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,OAAO,WAapD;AACD,qBAAa,sBAAsB,CAAC,CAAC;IACnC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,YAAY,CAAC,CAAiC;;IAOtD,IAAI,MAAM,sBAET;IAED,IAAI,WAAW,YAEd;IAED;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;YAUrB,IAAI;IA8ClB;;OAEG;IACG,YAAY;CAsBnB"}
1
+ {"version":3,"file":"deferred_stream.d.ts","sourceRoot":"","sources":["../../src/stream/deferred_stream.ts"],"names":[],"mappings":";AAGA,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,iBAAiB,CAAC;AAGzB;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,OAAO,WAiBpD;AACD,qBAAa,sBAAsB,CAAC,CAAC;IACnC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,YAAY,CAAC,CAAiC;;IAOtD,IAAI,MAAM,sBAET;IAED,IAAI,WAAW,YAEd;IAED;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;YAUrB,IAAI;IA8ClB;;OAEG;IACG,YAAY;CAsBnB"}
@@ -4,9 +4,13 @@ function isStreamReaderReleaseError(e) {
4
4
  "Invalid state: Releasing reader",
5
5
  "Invalid state: The reader is not attached to a stream",
6
6
  "Controller is already closed",
7
- "WritableStream is closed"
7
+ "WritableStream is closed",
8
+ // bun
9
+ "Stream reader cancelled via releaseLock()",
10
+ // deno
11
+ "The reader was released."
8
12
  ];
9
- if (e instanceof TypeError) {
13
+ if (e instanceof TypeError || e instanceof Error && e.name === "AbortError") {
10
14
  return allowedMessages.some((message) => e.message.includes(message));
11
15
  }
12
16
  return false;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/stream/deferred_stream.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type {\n ReadableStream,\n ReadableStreamDefaultReader,\n WritableStreamDefaultWriter,\n} from 'node:stream/web';\nimport { IdentityTransform } from './identity_transform.js';\n\n/**\n * Check if error is related to stream cleanup operations.\n *\n * These errors are expected when calling reader.read() after releaseLock()\n * or when writing to already closed streams during cleanup:\n *\n * Invalid state: Releasing reader\n * Invalid state: The reader is not attached to a stream\n * Invalid state: Controller is already closed\n * Invalid state: WritableStream is closed\n */\nexport function isStreamReaderReleaseError(e: unknown) {\n const allowedMessages = [\n 'Invalid state: Releasing reader',\n 'Invalid state: The reader is not attached to a stream',\n 'Controller is already closed',\n 'WritableStream is closed',\n ];\n\n if (e instanceof TypeError) {\n return allowedMessages.some((message) => e.message.includes(message));\n }\n\n return false;\n}\nexport class DeferredReadableStream<T> {\n private transform: IdentityTransform<T>;\n private writer: WritableStreamDefaultWriter<T>;\n private sourceReader?: ReadableStreamDefaultReader<T>;\n\n constructor() {\n this.transform = new IdentityTransform<T>();\n this.writer = this.transform.writable.getWriter();\n }\n\n get stream() {\n return this.transform.readable;\n }\n\n get isSourceSet() {\n return !!this.sourceReader;\n }\n\n /**\n * Call once the actual source is ready.\n */\n setSource(source: ReadableStream<T>) {\n if (this.isSourceSet) {\n throw new Error('Stream source already set');\n }\n\n const sourceReader = source.getReader();\n this.sourceReader = sourceReader;\n void this.pump(sourceReader);\n }\n\n private async pump(sourceReader: ReadableStreamDefaultReader<T>) {\n let sourceError: unknown;\n\n try {\n while (true) {\n const { done, value } = await sourceReader.read();\n if (done) break;\n await this.writer.write(value);\n }\n } catch (e) {\n // skip stream cleanup related errors\n if (isStreamReaderReleaseError(e)) return;\n\n sourceError = e;\n } finally {\n // any other error from source will be propagated to the consumer\n if (sourceError) {\n try {\n await this.writer.abort(sourceError);\n } catch (e) {\n // ignore if writer is already closed\n }\n return;\n }\n\n // release lock so this.stream.getReader().read() will terminate with done: true\n try {\n this.writer.releaseLock();\n } catch (e) {\n // ignore if writer lock is already released\n }\n\n // we only close the writable stream after done\n try {\n await this.transform.writable.close();\n // NOTE: we do not cancel this.transform.readable as there might be access to\n // this.transform.readable.getReader() outside that blocks this cancellation\n // hence, user is responsible for canceling reader on their own\n } catch (e) {\n // ignore TypeError: Invalid state: WritableStream is closed\n // in case stream reader is already closed, this will throw\n // but we ignore it as we are closing the stream anyway\n }\n }\n }\n\n /**\n * Detach the source stream and clean up resources.\n */\n async detachSource() {\n if (!this.isSourceSet) {\n // No-op if source was never set - this is a common case during cleanup\n return;\n }\n\n const sourceReader = this.sourceReader!;\n // Clear source first so future setSource() calls can reattach cleanly.\n this.sourceReader = undefined;\n\n // release lock will make any pending read() throw TypeError\n // which are expected, and we intentionally catch those error\n // using isStreamReaderReleaseError\n // this will unblock any pending read() inside the async for loop\n try {\n sourceReader.releaseLock();\n } catch (e) {\n if (!isStreamReaderReleaseError(e)) {\n throw e;\n }\n }\n }\n}\n"],"mappings":"AAQA,SAAS,yBAAyB;AAa3B,SAAS,2BAA2B,GAAY;AACrD,QAAM,kBAAkB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,aAAa,WAAW;AAC1B,WAAO,gBAAgB,KAAK,CAAC,YAAY,EAAE,QAAQ,SAAS,OAAO,CAAC;AAAA,EACtE;AAEA,SAAO;AACT;AACO,MAAM,uBAA0B;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EAER,cAAc;AACZ,SAAK,YAAY,IAAI,kBAAqB;AAC1C,SAAK,SAAS,KAAK,UAAU,SAAS,UAAU;AAAA,EAClD;AAAA,EAEA,IAAI,SAAS;AACX,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA,EAEA,IAAI,cAAc;AAChB,WAAO,CAAC,CAAC,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAA2B;AACnC,QAAI,KAAK,aAAa;AACpB,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AAEA,UAAM,eAAe,OAAO,UAAU;AACtC,SAAK,eAAe;AACpB,SAAK,KAAK,KAAK,YAAY;AAAA,EAC7B;AAAA,EAEA,MAAc,KAAK,cAA8C;AAC/D,QAAI;AAEJ,QAAI;AACF,aAAO,MAAM;AACX,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,aAAa,KAAK;AAChD,YAAI,KAAM;AACV,cAAM,KAAK,OAAO,MAAM,KAAK;AAAA,MAC/B;AAAA,IACF,SAAS,GAAG;AAEV,UAAI,2BAA2B,CAAC,EAAG;AAEnC,oBAAc;AAAA,IAChB,UAAE;AAEA,UAAI,aAAa;AACf,YAAI;AACF,gBAAM,KAAK,OAAO,MAAM,WAAW;AAAA,QACrC,SAAS,GAAG;AAAA,QAEZ;AACA;AAAA,MACF;AAGA,UAAI;AACF,aAAK,OAAO,YAAY;AAAA,MAC1B,SAAS,GAAG;AAAA,MAEZ;AAGA,UAAI;AACF,cAAM,KAAK,UAAU,SAAS,MAAM;AAAA,MAItC,SAAS,GAAG;AAAA,MAIZ;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe;AACnB,QAAI,CAAC,KAAK,aAAa;AAErB;AAAA,IACF;AAEA,UAAM,eAAe,KAAK;AAE1B,SAAK,eAAe;AAMpB,QAAI;AACF,mBAAa,YAAY;AAAA,IAC3B,SAAS,GAAG;AACV,UAAI,CAAC,2BAA2B,CAAC,GAAG;AAClC,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/stream/deferred_stream.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type {\n ReadableStream,\n ReadableStreamDefaultReader,\n WritableStreamDefaultWriter,\n} from 'node:stream/web';\nimport { IdentityTransform } from './identity_transform.js';\n\n/**\n * Check if error is related to stream cleanup operations.\n *\n * These errors are expected when calling reader.read() after releaseLock()\n * or when writing to already closed streams during cleanup:\n *\n * Invalid state: Releasing reader\n * Invalid state: The reader is not attached to a stream\n * Invalid state: Controller is already closed\n * Invalid state: WritableStream is closed\n */\nexport function isStreamReaderReleaseError(e: unknown) {\n const allowedMessages = [\n 'Invalid state: Releasing reader',\n 'Invalid state: The reader is not attached to a stream',\n 'Controller is already closed',\n 'WritableStream is closed',\n // bun\n 'Stream reader cancelled via releaseLock()',\n // deno\n 'The reader was released.',\n ];\n\n if (e instanceof TypeError || (e instanceof Error && e.name === 'AbortError')) {\n return allowedMessages.some((message) => e.message.includes(message));\n }\n\n return false;\n}\nexport class DeferredReadableStream<T> {\n private transform: IdentityTransform<T>;\n private writer: WritableStreamDefaultWriter<T>;\n private sourceReader?: ReadableStreamDefaultReader<T>;\n\n constructor() {\n this.transform = new IdentityTransform<T>();\n this.writer = this.transform.writable.getWriter();\n }\n\n get stream() {\n return this.transform.readable;\n }\n\n get isSourceSet() {\n return !!this.sourceReader;\n }\n\n /**\n * Call once the actual source is ready.\n */\n setSource(source: ReadableStream<T>) {\n if (this.isSourceSet) {\n throw new Error('Stream source already set');\n }\n\n const sourceReader = source.getReader();\n this.sourceReader = sourceReader;\n void this.pump(sourceReader);\n }\n\n private async pump(sourceReader: ReadableStreamDefaultReader<T>) {\n let sourceError: unknown;\n\n try {\n while (true) {\n const { done, value } = await sourceReader.read();\n if (done) break;\n await this.writer.write(value);\n }\n } catch (e) {\n // skip stream cleanup related errors\n if (isStreamReaderReleaseError(e)) return;\n\n sourceError = e;\n } finally {\n // any other error from source will be propagated to the consumer\n if (sourceError) {\n try {\n await this.writer.abort(sourceError);\n } catch (e) {\n // ignore if writer is already closed\n }\n return;\n }\n\n // release lock so this.stream.getReader().read() will terminate with done: true\n try {\n this.writer.releaseLock();\n } catch (e) {\n // ignore if writer lock is already released\n }\n\n // we only close the writable stream after done\n try {\n await this.transform.writable.close();\n // NOTE: we do not cancel this.transform.readable as there might be access to\n // this.transform.readable.getReader() outside that blocks this cancellation\n // hence, user is responsible for canceling reader on their own\n } catch (e) {\n // ignore TypeError: Invalid state: WritableStream is closed\n // in case stream reader is already closed, this will throw\n // but we ignore it as we are closing the stream anyway\n }\n }\n }\n\n /**\n * Detach the source stream and clean up resources.\n */\n async detachSource() {\n if (!this.isSourceSet) {\n // No-op if source was never set - this is a common case during cleanup\n return;\n }\n\n const sourceReader = this.sourceReader!;\n // Clear source first so future setSource() calls can reattach cleanly.\n this.sourceReader = undefined;\n\n // release lock will make any pending read() throw TypeError\n // which are expected, and we intentionally catch those error\n // using isStreamReaderReleaseError\n // this will unblock any pending read() inside the async for loop\n try {\n sourceReader.releaseLock();\n } catch (e) {\n if (!isStreamReaderReleaseError(e)) {\n throw e;\n }\n }\n }\n}\n"],"mappings":"AAQA,SAAS,yBAAyB;AAa3B,SAAS,2BAA2B,GAAY;AACrD,QAAM,kBAAkB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,EACF;AAEA,MAAI,aAAa,aAAc,aAAa,SAAS,EAAE,SAAS,cAAe;AAC7E,WAAO,gBAAgB,KAAK,CAAC,YAAY,EAAE,QAAQ,SAAS,OAAO,CAAC;AAAA,EACtE;AAEA,SAAO;AACT;AACO,MAAM,uBAA0B;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EAER,cAAc;AACZ,SAAK,YAAY,IAAI,kBAAqB;AAC1C,SAAK,SAAS,KAAK,UAAU,SAAS,UAAU;AAAA,EAClD;AAAA,EAEA,IAAI,SAAS;AACX,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA,EAEA,IAAI,cAAc;AAChB,WAAO,CAAC,CAAC,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAA2B;AACnC,QAAI,KAAK,aAAa;AACpB,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AAEA,UAAM,eAAe,OAAO,UAAU;AACtC,SAAK,eAAe;AACpB,SAAK,KAAK,KAAK,YAAY;AAAA,EAC7B;AAAA,EAEA,MAAc,KAAK,cAA8C;AAC/D,QAAI;AAEJ,QAAI;AACF,aAAO,MAAM;AACX,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,aAAa,KAAK;AAChD,YAAI,KAAM;AACV,cAAM,KAAK,OAAO,MAAM,KAAK;AAAA,MAC/B;AAAA,IACF,SAAS,GAAG;AAEV,UAAI,2BAA2B,CAAC,EAAG;AAEnC,oBAAc;AAAA,IAChB,UAAE;AAEA,UAAI,aAAa;AACf,YAAI;AACF,gBAAM,KAAK,OAAO,MAAM,WAAW;AAAA,QACrC,SAAS,GAAG;AAAA,QAEZ;AACA;AAAA,MACF;AAGA,UAAI;AACF,aAAK,OAAO,YAAY;AAAA,MAC1B,SAAS,GAAG;AAAA,MAEZ;AAGA,UAAI;AACF,cAAM,KAAK,UAAU,SAAS,MAAM;AAAA,MAItC,SAAS,GAAG;AAAA,MAIZ;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe;AACnB,QAAI,CAAC,KAAK,aAAa;AAErB;AAAA,IACF;AAEA,UAAM,eAAe,KAAK;AAE1B,SAAK,eAAe;AAMpB,QAAI;AACF,mBAAa,YAAY;AAAA,IAC3B,SAAS,GAAG;AACV,UAAI,CAAC,2BAA2B,CAAC,GAAG;AAClC,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;","names":[]}