@pico-brief/speech-services-parallel 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +372 -0
- package/dist/KeyManager.d.ts +41 -0
- package/dist/KeyManager.d.ts.map +1 -0
- package/dist/KeyManager.js +61 -0
- package/dist/KeyManager.js.map +1 -0
- package/dist/audioFormat.d.ts +24 -0
- package/dist/audioFormat.d.ts.map +1 -0
- package/dist/audioFormat.js +52 -0
- package/dist/audioFormat.js.map +1 -0
- package/dist/clientFactory.d.ts +26 -0
- package/dist/clientFactory.d.ts.map +1 -0
- package/dist/clientFactory.js +28 -0
- package/dist/clientFactory.js.map +1 -0
- package/dist/concurrency.d.ts +25 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +84 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +48 -0
- package/dist/errors.js.map +1 -0
- package/dist/helpers.d.ts +48 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +73 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/merge.d.ts +35 -0
- package/dist/merge.d.ts.map +1 -0
- package/dist/merge.js +37 -0
- package/dist/merge.js.map +1 -0
- package/dist/retry.d.ts +38 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +68 -0
- package/dist/retry.js.map +1 -0
- package/dist/synthesizeParallel.d.ts +32 -0
- package/dist/synthesizeParallel.d.ts.map +1 -0
- package/dist/synthesizeParallel.js +144 -0
- package/dist/synthesizeParallel.js.map +1 -0
- package/dist/transcribeParallel.d.ts +35 -0
- package/dist/transcribeParallel.d.ts.map +1 -0
- package/dist/transcribeParallel.js +131 -0
- package/dist/transcribeParallel.js.map +1 -0
- package/dist/types.d.ts +169 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# @pico-brief/speech-services-parallel
|
|
2
|
+
|
|
3
|
+
Transcribe audio to text and generate speech from text — fast — by running multiple requests at the same time.
|
|
4
|
+
|
|
5
|
+
This library takes long audio files, splits them into smaller pieces, and transcribes every piece in parallel. It does the same thing in reverse for text-to-speech: you give it chunks of text, it turns them all into audio at once, and stitches the results together into a single file. Under the hood it handles retries, rotates through your API keys so you don't hit rate limits, and lets you control how many requests run at once.
|
|
6
|
+
|
|
7
|
+
Built on top of [`@pico-brief/speech-services`](https://github.com/PicoBrief/speech-services).
|
|
8
|
+
|
|
9
|
+
## Supported Providers
|
|
10
|
+
|
|
11
|
+
| Provider | Speech-to-Text | Text-to-Speech |
|
|
12
|
+
|---|:---:|:---:|
|
|
13
|
+
| Azure | ✅ | ✅ |
|
|
14
|
+
| AssemblyAI | ✅ | |
|
|
15
|
+
| Cartesia | | ✅ |
|
|
16
|
+
| Deepgram | ✅ | ✅ |
|
|
17
|
+
| ElevenLabs | ✅ | ✅ |
|
|
18
|
+
| Google | ✅ | ✅ |
|
|
19
|
+
| OpenAI | ✅ | ✅ |
|
|
20
|
+
| PlayHT | | ✅ |
|
|
21
|
+
| Rev AI | ✅ | |
|
|
22
|
+
| Speechmatics | ✅ | |
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @pico-brief/speech-services-parallel
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
You also need [ffmpeg](https://ffmpeg.org/) installed on your system. It's used to split and join audio files.
|
|
31
|
+
|
|
32
|
+
## Requirements
|
|
33
|
+
|
|
34
|
+
- Node.js >= 18
|
|
35
|
+
- ffmpeg binary available on your system
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
### Transcribe audio to text
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { transcribeParallel } from "@pico-brief/speech-services-parallel";
|
|
43
|
+
import { readFileSync } from "fs";
|
|
44
|
+
|
|
45
|
+
const audio = readFileSync("interview.mp3");
|
|
46
|
+
|
|
47
|
+
const result = await transcribeParallel({
|
|
48
|
+
provider: "openai",
|
|
49
|
+
credentials: [{ apiKey: "sk-..." }],
|
|
50
|
+
targetChunkDuration: 300, // 5-minute chunks
|
|
51
|
+
chunkOverlap: 30, // 30 seconds of overlap
|
|
52
|
+
audio,
|
|
53
|
+
ffmpegPath: "ffmpeg",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log(result.text);
|
|
57
|
+
// "Hello and welcome to the show..."
|
|
58
|
+
|
|
59
|
+
console.log(result.duration);
|
|
60
|
+
// 1823.5 (seconds)
|
|
61
|
+
|
|
62
|
+
console.log(result.words);
|
|
63
|
+
// [{ word: "Hello", start: 0.0, end: 0.42 }, { word: "and", start: 0.42, end: 0.58 }, ...]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
If the audio is longer than 5 minutes, it is automatically split into chunks and each chunk is transcribed in parallel. The results are merged back together with word-level timestamps.
|
|
67
|
+
|
|
68
|
+
### Generate speech from text
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { synthesizeParallel } from "@pico-brief/speech-services-parallel";
|
|
72
|
+
import { writeFileSync } from "fs";
|
|
73
|
+
|
|
74
|
+
const result = await synthesizeParallel({
|
|
75
|
+
provider: "openai",
|
|
76
|
+
credentials: [{ apiKey: "sk-..." }],
|
|
77
|
+
chunks: [
|
|
78
|
+
{ text: "Chapter one. It was a dark and stormy night." },
|
|
79
|
+
{ text: "Chapter two. The sun rose over the hills." },
|
|
80
|
+
],
|
|
81
|
+
ffmpegPath: "ffmpeg",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
writeFileSync("audiobook.mp3", result.audio);
|
|
85
|
+
|
|
86
|
+
console.log(result.format);
|
|
87
|
+
// "mp3"
|
|
88
|
+
|
|
89
|
+
console.log(result.chunks);
|
|
90
|
+
// [{ chunkIndex: 0, startTime: 0, duration: 3.2, voice: "alloy", ... }, ...]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Each chunk of text is synthesized in parallel and the audio is concatenated into a single file.
|
|
94
|
+
|
|
95
|
+
## Basic Usage
|
|
96
|
+
|
|
97
|
+
### Picking a provider
|
|
98
|
+
|
|
99
|
+
Every call requires a `provider` name and a `credentials` array. The shape of each credential depends on the provider:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
// OpenAI / Deepgram / ElevenLabs / Google / AssemblyAI / Rev AI / Cartesia
|
|
103
|
+
{ apiKey: "..." }
|
|
104
|
+
|
|
105
|
+
// Azure
|
|
106
|
+
{ subscriptionKey: "...", region: "eastus" }
|
|
107
|
+
|
|
108
|
+
// PlayHT
|
|
109
|
+
{ apiKey: "...", userId: "..." }
|
|
110
|
+
|
|
111
|
+
// Speechmatics
|
|
112
|
+
{ apiKey: "...", region: "eu" } // region is optional
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Specifying a language
|
|
116
|
+
|
|
117
|
+
Pass a `languages` array to help the provider pick the right model or voice:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
const result = await transcribeParallel({
|
|
121
|
+
provider: "deepgram",
|
|
122
|
+
credentials: [{ apiKey: "..." }],
|
|
123
|
+
audio,
|
|
124
|
+
ffmpegPath: "ffmpeg",
|
|
125
|
+
languages: ["en"],
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Choosing a voice
|
|
130
|
+
|
|
131
|
+
For text-to-speech, set a default voice for all chunks, or override it per chunk:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const result = await synthesizeParallel({
|
|
135
|
+
provider: "elevenlabs",
|
|
136
|
+
credentials: [{ apiKey: "..." }],
|
|
137
|
+
voice: "rachel",
|
|
138
|
+
chunks: [
|
|
139
|
+
{ text: "Narrated by Rachel." },
|
|
140
|
+
{ text: "Except this part.", voice: "bella" }, // override for this chunk
|
|
141
|
+
{ text: "Back to Rachel." },
|
|
142
|
+
],
|
|
143
|
+
ffmpegPath: "ffmpeg",
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Advanced Usage
|
|
148
|
+
|
|
149
|
+
### Credential rotation
|
|
150
|
+
|
|
151
|
+
If you have multiple API keys, pass them all in the `credentials` array. The library picks the least-recently-used key for each request and automatically rotates to another key when one hits a rate limit or fails:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
const result = await transcribeParallel({
|
|
155
|
+
provider: "openai",
|
|
156
|
+
credentials: [
|
|
157
|
+
{ apiKey: "sk-key-1" },
|
|
158
|
+
{ apiKey: "sk-key-2" },
|
|
159
|
+
{ apiKey: "sk-key-3" },
|
|
160
|
+
],
|
|
161
|
+
audio,
|
|
162
|
+
ffmpegPath: "ffmpeg",
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
When a key fails, it goes into a cool-down period so it isn't immediately retried.
|
|
167
|
+
|
|
168
|
+
### Limiting concurrency
|
|
169
|
+
|
|
170
|
+
By default, all chunks are processed at the same time. If you want to limit how many run in parallel (for example, to stay under a provider's rate limit), use `maxConcurrency`:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
const result = await synthesizeParallel({
|
|
174
|
+
provider: "elevenlabs",
|
|
175
|
+
credentials: [{ apiKey: "..." }],
|
|
176
|
+
chunks: fiftyChunks,
|
|
177
|
+
maxConcurrency: 5, // only 5 requests at a time
|
|
178
|
+
ffmpegPath: "ffmpeg",
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Tracking progress
|
|
183
|
+
|
|
184
|
+
Pass an `onProgress` callback to get notified as chunks complete:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
const result = await transcribeParallel({
|
|
188
|
+
provider: "deepgram",
|
|
189
|
+
credentials: [{ apiKey: "..." }],
|
|
190
|
+
audio: longAudio,
|
|
191
|
+
ffmpegPath: "ffmpeg",
|
|
192
|
+
onProgress: (completed, total) => {
|
|
193
|
+
console.log(`${completed}/${total} chunks done`);
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Controlling chunk size
|
|
199
|
+
|
|
200
|
+
For transcription, the library splits audio into 5-minute chunks by default with a 15-second overlap between chunks (so words at the boundary aren't lost). You can change both values:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
const result = await transcribeParallel({
|
|
204
|
+
provider: "assemblyai",
|
|
205
|
+
credentials: [{ apiKey: "..." }],
|
|
206
|
+
audio,
|
|
207
|
+
ffmpegPath: "ffmpeg",
|
|
208
|
+
targetChunkDuration: 120, // 2-minute chunks
|
|
209
|
+
chunkOverlap: 30, // 30 seconds of overlap
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Retry timeout
|
|
214
|
+
|
|
215
|
+
Failed requests are retried automatically with exponential backoff. The default deadline is 5 minutes. You can change it:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
const result = await synthesizeParallel({
|
|
219
|
+
provider: "azure",
|
|
220
|
+
credentials: [
|
|
221
|
+
{ subscriptionKey: "key-1", region: "eastus" },
|
|
222
|
+
{ subscriptionKey: "key-2", region: "westus" },
|
|
223
|
+
],
|
|
224
|
+
chunks: textChunks,
|
|
225
|
+
retryTimeoutMs: 10 * 60 * 1000, // 10 minutes
|
|
226
|
+
ffmpegPath: "ffmpeg",
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Errors like invalid API keys (401, 403) or bad input (400, 422) are **not** retried — only transient errors (429, 500, 502, 503, 504) are.
|
|
231
|
+
|
|
232
|
+
### Cancellation
|
|
233
|
+
|
|
234
|
+
Pass an `AbortSignal` to cancel an in-progress operation:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
const controller = new AbortController();
|
|
238
|
+
|
|
239
|
+
const promise = transcribeParallel({
|
|
240
|
+
provider: "google",
|
|
241
|
+
credentials: [{ apiKey: "..." }],
|
|
242
|
+
audio,
|
|
243
|
+
ffmpegPath: "ffmpeg",
|
|
244
|
+
signal: controller.signal,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Cancel after 30 seconds
|
|
248
|
+
setTimeout(() => controller.abort(), 30_000);
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Provider-specific options
|
|
252
|
+
|
|
253
|
+
Each provider supports extra options through `providerOptions`. These are passed directly to the underlying provider client:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
const result = await synthesizeParallel({
|
|
257
|
+
provider: "openai",
|
|
258
|
+
credentials: [{ apiKey: "..." }],
|
|
259
|
+
chunks: [
|
|
260
|
+
{ text: "High quality audio.", providerOptions: { model: "tts-1-hd" } },
|
|
261
|
+
{ text: "Standard quality.", providerOptions: { model: "tts-1" } },
|
|
262
|
+
],
|
|
263
|
+
ffmpegPath: "ffmpeg",
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
You can also set default `providerOptions` at the top level, and override them per chunk.
|
|
268
|
+
|
|
269
|
+
### Using KeyManager directly
|
|
270
|
+
|
|
271
|
+
If you need credential rotation for your own code, you can use the `KeyManager` class on its own:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
import { KeyManager } from "@pico-brief/speech-services-parallel";
|
|
275
|
+
|
|
276
|
+
const manager = new KeyManager(["key-1", "key-2", "key-3"]);
|
|
277
|
+
|
|
278
|
+
// Get the least-recently-used key
|
|
279
|
+
const key = manager.getKey();
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
await callSomeApi(key);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
// Put this key on cool-down so it isn't picked again right away
|
|
285
|
+
manager.reportError(key);
|
|
286
|
+
|
|
287
|
+
// Or set a custom cool-down (in milliseconds)
|
|
288
|
+
manager.reportError(key, 60_000); // 1 minute
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## API Reference
|
|
293
|
+
|
|
294
|
+
### `transcribeParallel(params)`
|
|
295
|
+
|
|
296
|
+
Transcribes audio with automatic chunking and parallel processing.
|
|
297
|
+
|
|
298
|
+
**Parameters:**
|
|
299
|
+
|
|
300
|
+
| Name | Type | Required | Default | Description |
|
|
301
|
+
|---|---|---|---|---|
|
|
302
|
+
| `provider` | `string` | Yes | — | Provider name (see supported providers above) |
|
|
303
|
+
| `credentials` | `ProviderConfig[]` | Yes | — | One or more credential objects for the provider |
|
|
304
|
+
| `audio` | `Buffer` | Yes | — | The audio data to transcribe |
|
|
305
|
+
| `ffmpegPath` | `string` | Yes | — | Path to the ffmpeg binary |
|
|
306
|
+
| `languages` | `string[]` | No | — | Language hints for the provider |
|
|
307
|
+
| `targetChunkDuration` | `number` | No | `300` | Target chunk length in seconds |
|
|
308
|
+
| `chunkOverlap` | `number` | No | `15` | Overlap between chunks in seconds |
|
|
309
|
+
| `retryTimeoutMs` | `number` | No | `300000` | Max time to keep retrying (ms) |
|
|
310
|
+
| `maxConcurrency` | `number` | No | — | Max parallel requests |
|
|
311
|
+
| `signal` | `AbortSignal` | No | — | Signal to cancel the operation |
|
|
312
|
+
| `onProgress` | `(completed, total) => void` | No | — | Progress callback |
|
|
313
|
+
| `providerOptions` | `object` | No | — | Provider-specific options |
|
|
314
|
+
|
|
315
|
+
**Returns:** `Promise<TranscribeResult>` with `text`, `words`, `language`, and `duration`.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
### `synthesizeParallel(params)`
|
|
320
|
+
|
|
321
|
+
Synthesizes multiple text chunks into audio in parallel.
|
|
322
|
+
|
|
323
|
+
**Parameters:**
|
|
324
|
+
|
|
325
|
+
| Name | Type | Required | Default | Description |
|
|
326
|
+
|---|---|---|---|---|
|
|
327
|
+
| `provider` | `string` | Yes | — | Provider name (see supported providers above) |
|
|
328
|
+
| `credentials` | `ProviderConfig[]` | Yes | — | One or more credential objects for the provider |
|
|
329
|
+
| `chunks` | `SynthesizeChunkInput[]` | Yes | — | Text chunks to synthesize |
|
|
330
|
+
| `ffmpegPath` | `string` | No | — | Path to ffmpeg (for concatenation) |
|
|
331
|
+
| `gender` | `"male" \| "female"` | No | — | Default voice gender |
|
|
332
|
+
| `voice` | `string` | No | — | Default voice ID or name |
|
|
333
|
+
| `languages` | `string[]` | No | — | Default language hints |
|
|
334
|
+
| `retryTimeoutMs` | `number` | No | `300000` | Max time to keep retrying (ms) |
|
|
335
|
+
| `maxConcurrency` | `number` | No | — | Max parallel requests |
|
|
336
|
+
| `signal` | `AbortSignal` | No | — | Signal to cancel the operation |
|
|
337
|
+
| `onProgress` | `(completed, total) => void` | No | — | Progress callback |
|
|
338
|
+
| `providerOptions` | `object` | No | — | Default provider-specific options |
|
|
339
|
+
|
|
340
|
+
**Returns:** `Promise<SynthesizeParallelResult>`
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
{
|
|
344
|
+
audio: Buffer; // The combined audio data
|
|
345
|
+
format: string; // Audio format (e.g. "mp3")
|
|
346
|
+
chunks: [{
|
|
347
|
+
chunkIndex: number;
|
|
348
|
+
startTime: number; // Offset in combined audio (seconds)
|
|
349
|
+
duration: number; // Duration of this chunk (seconds)
|
|
350
|
+
voice: string; // Voice that was used
|
|
351
|
+
language?: string;
|
|
352
|
+
format: string;
|
|
353
|
+
provider: string;
|
|
354
|
+
}];
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
### `KeyManager<T>`
|
|
361
|
+
|
|
362
|
+
Generic credential rotation manager using a least-recently-used strategy.
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
new KeyManager(credentials: T[]) // requires at least one credential
|
|
366
|
+
manager.getKey(): T // returns the least-recently-used credential
|
|
367
|
+
manager.reportError(key: T, coolDownMs?: number): void // puts a key on cool-down
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## License
|
|
371
|
+
|
|
372
|
+
MIT
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic credential rotation manager using a Least-Recently-Used (LRU) strategy.
|
|
3
|
+
*
|
|
4
|
+
* Credentials that fail are placed on a cool-down so they are temporarily skipped.
|
|
5
|
+
* If every credential is cooling down, the cool-down is ignored and the LRU
|
|
6
|
+
* credential is returned anyway (better to retry than to block forever).
|
|
7
|
+
*
|
|
8
|
+
* @typeParam TCredential - The shape of a single credential (e.g. `{ apiKey: string }`).
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const km = new KeyManager([{ apiKey: "a" }, { apiKey: "b" }]);
|
|
13
|
+
* const key = km.getKey(); // returns least-recently-used key
|
|
14
|
+
* km.reportError(key); // puts it on 3-minute cool-down
|
|
15
|
+
* const next = km.getKey(); // returns the other key
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare class KeyManager<TCredential> {
|
|
19
|
+
/** Internal bookkeeping: tracks usage time and cool-down expiry for each credential. */
|
|
20
|
+
private entries;
|
|
21
|
+
/**
|
|
22
|
+
* Create a new KeyManager.
|
|
23
|
+
* @param credentials - Array of credentials to rotate through. Must contain at least one.
|
|
24
|
+
* @throws {Error} If the credentials array is empty.
|
|
25
|
+
*/
|
|
26
|
+
constructor(credentials: TCredential[]);
|
|
27
|
+
/**
|
|
28
|
+
* Returns the credential with the oldest last-use time, skipping credentials in cool-down.
|
|
29
|
+
* Falls back to all credentials if every one is cooling down.
|
|
30
|
+
* The returned credential's `lastUsedAt` is updated to `Date.now()`.
|
|
31
|
+
*/
|
|
32
|
+
getKey(): TCredential;
|
|
33
|
+
/**
|
|
34
|
+
* Marks a credential as failed so it is temporarily excluded from selection.
|
|
35
|
+
*
|
|
36
|
+
* @param credential - The exact credential reference returned by {@link getKey}.
|
|
37
|
+
* @param coolDownMs - How long to exclude this credential, in milliseconds. @default 180000 (3 minutes)
|
|
38
|
+
*/
|
|
39
|
+
reportError(credential: TCredential, coolDownMs?: number): void;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=KeyManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KeyManager.d.ts","sourceRoot":"","sources":["../src/KeyManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,UAAU,CAAC,WAAW;IAC/B,wFAAwF;IACxF,OAAO,CAAC,OAAO,CAA2E;IAE1F;;;;OAIG;gBACS,WAAW,EAAE,WAAW,EAAE;IAKtC;;;;OAIG;IACH,MAAM,IAAI,WAAW;IAiBrB;;;;;OAKG;IACH,WAAW,CAAC,UAAU,EAAE,WAAW,EAAE,UAAU,GAAE,MAAsB,GAAG,IAAI;CAIjF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic credential rotation manager using a Least-Recently-Used (LRU) strategy.
|
|
3
|
+
*
|
|
4
|
+
* Credentials that fail are placed on a cool-down so they are temporarily skipped.
|
|
5
|
+
* If every credential is cooling down, the cool-down is ignored and the LRU
|
|
6
|
+
* credential is returned anyway (better to retry than to block forever).
|
|
7
|
+
*
|
|
8
|
+
* @typeParam TCredential - The shape of a single credential (e.g. `{ apiKey: string }`).
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const km = new KeyManager([{ apiKey: "a" }, { apiKey: "b" }]);
|
|
13
|
+
* const key = km.getKey(); // returns least-recently-used key
|
|
14
|
+
* km.reportError(key); // puts it on 3-minute cool-down
|
|
15
|
+
* const next = km.getKey(); // returns the other key
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export class KeyManager {
|
|
19
|
+
/** Internal bookkeeping: tracks usage time and cool-down expiry for each credential. */
|
|
20
|
+
entries;
|
|
21
|
+
/**
|
|
22
|
+
* Create a new KeyManager.
|
|
23
|
+
* @param credentials - Array of credentials to rotate through. Must contain at least one.
|
|
24
|
+
* @throws {Error} If the credentials array is empty.
|
|
25
|
+
*/
|
|
26
|
+
constructor(credentials) {
|
|
27
|
+
if (credentials.length === 0)
|
|
28
|
+
throw new Error("KeyManager requires at least one credential");
|
|
29
|
+
this.entries = credentials.map(credential => ({ credential, lastUsedAt: 0, coolDownUntil: 0 }));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns the credential with the oldest last-use time, skipping credentials in cool-down.
|
|
33
|
+
* Falls back to all credentials if every one is cooling down.
|
|
34
|
+
* The returned credential's `lastUsedAt` is updated to `Date.now()`.
|
|
35
|
+
*/
|
|
36
|
+
getKey() {
|
|
37
|
+
const now = Date.now();
|
|
38
|
+
// Filter to credentials whose cool-down has expired
|
|
39
|
+
let available = this.entries.filter(e => e.coolDownUntil <= now);
|
|
40
|
+
// If all credentials are on cool-down, use them all anyway
|
|
41
|
+
if (available.length === 0)
|
|
42
|
+
available = [...this.entries];
|
|
43
|
+
// Sort by oldest usage first (LRU)
|
|
44
|
+
available.sort((a, b) => a.lastUsedAt - b.lastUsedAt);
|
|
45
|
+
const entry = available[0];
|
|
46
|
+
entry.lastUsedAt = now;
|
|
47
|
+
return entry.credential;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Marks a credential as failed so it is temporarily excluded from selection.
|
|
51
|
+
*
|
|
52
|
+
* @param credential - The exact credential reference returned by {@link getKey}.
|
|
53
|
+
* @param coolDownMs - How long to exclude this credential, in milliseconds. @default 180000 (3 minutes)
|
|
54
|
+
*/
|
|
55
|
+
reportError(credential, coolDownMs = 3 * 60 * 1000) {
|
|
56
|
+
const entry = this.entries.find(e => e.credential === credential);
|
|
57
|
+
if (entry)
|
|
58
|
+
entry.coolDownUntil = Date.now() + coolDownMs;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=KeyManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KeyManager.js","sourceRoot":"","sources":["../src/KeyManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,UAAU;IACnB,wFAAwF;IAChF,OAAO,CAA2E;IAE1F;;;;OAIG;IACH,YAAY,WAA0B;QAClC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QAC7F,IAAI,CAAC,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACpG,CAAC;IAED;;;;OAIG;IACH,MAAM;QACF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,oDAAoD;QACpD,IAAI,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,IAAI,GAAG,CAAC,CAAC;QAEjE,2DAA2D;QAC3D,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;QAE1D,mCAAmC;QACnC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC;QAEtD,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QAC3B,KAAK,CAAC,UAAU,GAAG,GAAG,CAAC;QACvB,OAAO,KAAK,CAAC,UAAU,CAAC;IAC5B,CAAC;IAED;;;;;OAKG;IACH,WAAW,CAAC,UAAuB,EAAE,aAAqB,CAAC,GAAG,EAAE,GAAG,IAAI;QACnE,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC;QAClE,IAAI,KAAK;YAAE,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC;IAC7D,CAAC;CACJ"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio format detection by inspecting file magic bytes.
|
|
3
|
+
*
|
|
4
|
+
* Used to determine the correct file extension when writing temporary chunk
|
|
5
|
+
* files to disk. Supports FLAC, OGG, WAV, M4A/MP4, and MP3 (including ID3-tagged files).
|
|
6
|
+
*/
|
|
7
|
+
/** Recognized audio formats, plus `"unknown"` as a fallback. */
|
|
8
|
+
export type AudioFormat = "mp3" | "wav" | "ogg" | "flac" | "m4a" | "unknown";
|
|
9
|
+
/**
|
|
10
|
+
* Detects the audio format of a buffer by inspecting its leading bytes (magic bytes).
|
|
11
|
+
*
|
|
12
|
+
* @param buffer - Raw audio data (at least 12 bytes for reliable detection).
|
|
13
|
+
* @returns The detected {@link AudioFormat}, or `"unknown"` if no signature matches.
|
|
14
|
+
*/
|
|
15
|
+
export declare function detectAudioFormat(buffer: Buffer): AudioFormat;
|
|
16
|
+
/**
|
|
17
|
+
* Maps an {@link AudioFormat} to a file extension string.
|
|
18
|
+
* Falls back to `"mp3"` for `"unknown"` formats.
|
|
19
|
+
*
|
|
20
|
+
* @param format - The detected audio format.
|
|
21
|
+
* @returns A file extension string (without the leading dot).
|
|
22
|
+
*/
|
|
23
|
+
export declare function audioFormatToExtension(format: AudioFormat): string;
|
|
24
|
+
//# sourceMappingURL=audioFormat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audioFormat.d.ts","sourceRoot":"","sources":["../src/audioFormat.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,gEAAgE;AAChE,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC;AAE7E;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAmC7D;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAGlE"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio format detection by inspecting file magic bytes.
|
|
3
|
+
*
|
|
4
|
+
* Used to determine the correct file extension when writing temporary chunk
|
|
5
|
+
* files to disk. Supports FLAC, OGG, WAV, M4A/MP4, and MP3 (including ID3-tagged files).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Detects the audio format of a buffer by inspecting its leading bytes (magic bytes).
|
|
9
|
+
*
|
|
10
|
+
* @param buffer - Raw audio data (at least 12 bytes for reliable detection).
|
|
11
|
+
* @returns The detected {@link AudioFormat}, or `"unknown"` if no signature matches.
|
|
12
|
+
*/
|
|
13
|
+
export function detectAudioFormat(buffer) {
|
|
14
|
+
if (buffer.length < 12)
|
|
15
|
+
return "unknown";
|
|
16
|
+
// FLAC: starts with "fLaC" (0x66 0x4C 0x61 0x43)
|
|
17
|
+
if (buffer[0] === 0x66 && buffer[1] === 0x4C && buffer[2] === 0x61 && buffer[3] === 0x43) {
|
|
18
|
+
return "flac";
|
|
19
|
+
}
|
|
20
|
+
// OGG: starts with "OggS" (0x4F 0x67 0x67 0x53)
|
|
21
|
+
if (buffer[0] === 0x4F && buffer[1] === 0x67 && buffer[2] === 0x67 && buffer[3] === 0x53) {
|
|
22
|
+
return "ogg";
|
|
23
|
+
}
|
|
24
|
+
// WAV: starts with "RIFF" at offset 0 and "WAVE" at offset 8
|
|
25
|
+
if (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46 &&
|
|
26
|
+
buffer[8] === 0x57 && buffer[9] === 0x41 && buffer[10] === 0x56 && buffer[11] === 0x45) {
|
|
27
|
+
return "wav";
|
|
28
|
+
}
|
|
29
|
+
// M4A/MP4: "ftyp" at offset 4 (0x66 0x74 0x79 0x70)
|
|
30
|
+
if (buffer[4] === 0x66 && buffer[5] === 0x74 && buffer[6] === 0x79 && buffer[7] === 0x70) {
|
|
31
|
+
return "m4a";
|
|
32
|
+
}
|
|
33
|
+
// MP3: either a frame sync word (0xFF followed by 0xE0+) or an ID3 tag header ("ID3")
|
|
34
|
+
if ((buffer[0] === 0xFF && (buffer[1] & 0xE0) === 0xE0) ||
|
|
35
|
+
(buffer[0] === 0x49 && buffer[1] === 0x44 && buffer[2] === 0x33)) {
|
|
36
|
+
return "mp3";
|
|
37
|
+
}
|
|
38
|
+
return "unknown";
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Maps an {@link AudioFormat} to a file extension string.
|
|
42
|
+
* Falls back to `"mp3"` for `"unknown"` formats.
|
|
43
|
+
*
|
|
44
|
+
* @param format - The detected audio format.
|
|
45
|
+
* @returns A file extension string (without the leading dot).
|
|
46
|
+
*/
|
|
47
|
+
export function audioFormatToExtension(format) {
|
|
48
|
+
if (format === "unknown")
|
|
49
|
+
return "mp3";
|
|
50
|
+
return format;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=audioFormat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audioFormat.js","sourceRoot":"","sources":["../src/audioFormat.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC5C,IAAI,MAAM,CAAC,MAAM,GAAG,EAAE;QAAE,OAAO,SAAS,CAAC;IAEzC,iDAAiD;IACjD,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACvF,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,gDAAgD;IAChD,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACvF,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,6DAA6D;IAC7D,IACI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI;QACpF,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,IAAI,EACxF,CAAC;QACC,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,oDAAoD;IACpD,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACvF,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,sFAAsF;IACtF,IACI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC;QACnD,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,EAClE,CAAC;QACC,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,OAAO,SAAS,CAAC;AACrB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAmB;IACtD,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACvC,OAAO,MAAM,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory for creating speech service clients from a provider name and credential.
|
|
3
|
+
*
|
|
4
|
+
* This is a thin wrapper around `createSpeechClient` from `@pico-brief/speech-services`
|
|
5
|
+
* that maps a `(provider, credential)` pair to the config shape expected by the
|
|
6
|
+
* underlying library.
|
|
7
|
+
*/
|
|
8
|
+
import type { SpeechClient } from "@pico-brief/speech-services";
|
|
9
|
+
/**
|
|
10
|
+
* Creates a {@link SpeechClient} from a provider name and its corresponding credential object.
|
|
11
|
+
*
|
|
12
|
+
* The credential must match the config type for that provider from `@pico-brief/speech-services`:
|
|
13
|
+
*
|
|
14
|
+
* | Provider | Config Type | Shape |
|
|
15
|
+
* |----------------|----------------------|-----------------------------------------------|
|
|
16
|
+
* | `azure` | `AzureConfig` | `{ region: string; subscriptionKey: string }` |
|
|
17
|
+
* | `playht` | `PlayHTConfig` | `{ userId: string; apiKey: string }` |
|
|
18
|
+
* | `speechmatics` | `SpeechmaticsConfig` | `{ apiKey: string; region?: string }` |
|
|
19
|
+
* | All others | `*Config` | `{ apiKey: string }` |
|
|
20
|
+
*
|
|
21
|
+
* @param provider - The provider name (e.g. `"openai"`, `"azure"`).
|
|
22
|
+
* @param credential - The credential object for that provider.
|
|
23
|
+
* @returns A configured {@link SpeechClient} ready for transcription or synthesis calls.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createClientFromCredential(provider: string, credential: object): SpeechClient;
|
|
26
|
+
//# sourceMappingURL=clientFactory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clientFactory.d.ts","sourceRoot":"","sources":["../src/clientFactory.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAgB,MAAM,6BAA6B,CAAC;AAE9E;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,YAAY,CAE7F"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory for creating speech service clients from a provider name and credential.
|
|
3
|
+
*
|
|
4
|
+
* This is a thin wrapper around `createSpeechClient` from `@pico-brief/speech-services`
|
|
5
|
+
* that maps a `(provider, credential)` pair to the config shape expected by the
|
|
6
|
+
* underlying library.
|
|
7
|
+
*/
|
|
8
|
+
import { createSpeechClient } from "@pico-brief/speech-services";
|
|
9
|
+
/**
|
|
10
|
+
* Creates a {@link SpeechClient} from a provider name and its corresponding credential object.
|
|
11
|
+
*
|
|
12
|
+
* The credential must match the config type for that provider from `@pico-brief/speech-services`:
|
|
13
|
+
*
|
|
14
|
+
* | Provider | Config Type | Shape |
|
|
15
|
+
* |----------------|----------------------|-----------------------------------------------|
|
|
16
|
+
* | `azure` | `AzureConfig` | `{ region: string; subscriptionKey: string }` |
|
|
17
|
+
* | `playht` | `PlayHTConfig` | `{ userId: string; apiKey: string }` |
|
|
18
|
+
* | `speechmatics` | `SpeechmaticsConfig` | `{ apiKey: string; region?: string }` |
|
|
19
|
+
* | All others | `*Config` | `{ apiKey: string }` |
|
|
20
|
+
*
|
|
21
|
+
* @param provider - The provider name (e.g. `"openai"`, `"azure"`).
|
|
22
|
+
* @param credential - The credential object for that provider.
|
|
23
|
+
* @returns A configured {@link SpeechClient} ready for transcription or synthesis calls.
|
|
24
|
+
*/
|
|
25
|
+
export function createClientFromCredential(provider, credential) {
|
|
26
|
+
return createSpeechClient({ [provider]: credential });
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=clientFactory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clientFactory.js","sourceRoot":"","sources":["../src/clientFactory.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAGjE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,0BAA0B,CAAC,QAAgB,EAAE,UAAkB;IAC3E,OAAO,kBAAkB,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAkB,CAAC,CAAC;AAC1E,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semaphore-based concurrency control for parallel operations.
|
|
3
|
+
*
|
|
4
|
+
* Provides {@link mapWithConcurrency}, a `Promise.all`-like helper that caps the
|
|
5
|
+
* number of in-flight promises at any given time. Result order always matches
|
|
6
|
+
* the input order regardless of completion order.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Maps items through an async function with optional concurrency limiting.
|
|
10
|
+
*
|
|
11
|
+
* Behaves like `Promise.all(items.map(fn))` but ensures at most `maxConcurrency`
|
|
12
|
+
* calls to `fn` are in flight at any time. If `maxConcurrency` is `undefined`
|
|
13
|
+
* or greater than or equal to `items.length`, falls back to plain `Promise.all`.
|
|
14
|
+
*
|
|
15
|
+
* Results are always returned in the same order as the input items.
|
|
16
|
+
*
|
|
17
|
+
* @typeParam T - Input item type.
|
|
18
|
+
* @typeParam R - Return type of the async mapper function.
|
|
19
|
+
* @param items - The items to process.
|
|
20
|
+
* @param fn - Async function called for each item with its index.
|
|
21
|
+
* @param maxConcurrency - Optional cap on the number of concurrent `fn` calls.
|
|
22
|
+
* @returns Array of results in the same order as `items`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function mapWithConcurrency<T, R>(items: T[], fn: (item: T, index: number) => Promise<R>, maxConcurrency?: number): Promise<R[]>;
|
|
25
|
+
//# sourceMappingURL=concurrency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"concurrency.d.ts","sourceRoot":"","sources":["../src/concurrency.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA8CH;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,CAAC,EACzC,KAAK,EAAE,CAAC,EAAE,EACV,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EAC1C,cAAc,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,CAAC,EAAE,CAAC,CAiBd"}
|