@kuralle-syrinx/deepgram 4.2.0 → 4.3.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/package.json +6 -5
- package/src/flux.ts +198 -155
- package/src/stt.ts +544 -495
- package/src/tts.ts +35 -5
- package/.handoff/finalize-contract-probe.test.ts +0 -193
- package/src/flux.test.ts +0 -227
- package/src/stt.test.ts +0 -1323
- package/src/tts.test.ts +0 -359
- package/tsconfig.json +0 -21
package/src/tts.test.ts
DELETED
|
@@ -1,359 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
|
|
3
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
|
-
import * as voice from "@kuralle-syrinx/core";
|
|
5
|
-
import { WebSocketServer, type WebSocket } from "ws";
|
|
6
|
-
import {
|
|
7
|
-
PipelineBusImpl,
|
|
8
|
-
Route,
|
|
9
|
-
type TextToSpeechAudioPacket,
|
|
10
|
-
type TextToSpeechEndPacket,
|
|
11
|
-
type TtsErrorPacket,
|
|
12
|
-
} from "@kuralle-syrinx/core";
|
|
13
|
-
|
|
14
|
-
import { DeepgramTTSPlugin } from "./tts.js";
|
|
15
|
-
|
|
16
|
-
let servers: WebSocketServer[] = [];
|
|
17
|
-
|
|
18
|
-
afterEach(async () => {
|
|
19
|
-
await Promise.all(
|
|
20
|
-
servers.splice(0).map(
|
|
21
|
-
(server) =>
|
|
22
|
-
new Promise<void>((resolve) => {
|
|
23
|
-
for (const client of server.clients) client.terminate();
|
|
24
|
-
server.close(() => resolve());
|
|
25
|
-
}),
|
|
26
|
-
),
|
|
27
|
-
);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
async function createLocalServer(
|
|
31
|
-
onConnection: (socket: WebSocket, requestUrl: string, authHeader: string) => void,
|
|
32
|
-
): Promise<string> {
|
|
33
|
-
const server = await new Promise<WebSocketServer>((resolve) => {
|
|
34
|
-
let next: WebSocketServer;
|
|
35
|
-
next = new WebSocketServer({ port: 0 }, () => resolve(next));
|
|
36
|
-
});
|
|
37
|
-
servers.push(server);
|
|
38
|
-
server.on("connection", (socket, request) => {
|
|
39
|
-
const header = request.headers["authorization"];
|
|
40
|
-
onConnection(socket, request.url ?? "", Array.isArray(header) ? header[0] ?? "" : header ?? "");
|
|
41
|
-
});
|
|
42
|
-
const address = server.address();
|
|
43
|
-
if (!address || typeof address === "string") throw new Error("Expected TCP server address");
|
|
44
|
-
return `ws://127.0.0.1:${address.port}/v1/speak`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function waitForCondition(predicate: () => boolean, timeoutMs = 1000): Promise<void> {
|
|
48
|
-
const startedAt = Date.now();
|
|
49
|
-
while (Date.now() - startedAt < timeoutMs) {
|
|
50
|
-
if (predicate()) return;
|
|
51
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
52
|
-
}
|
|
53
|
-
throw new Error("Timed out waiting for Deepgram test condition");
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
describe("DeepgramTTSPlugin", () => {
|
|
57
|
-
it("streams sentences as Speak messages and ends the turn on Flushed", async () => {
|
|
58
|
-
const received: Array<Record<string, unknown>> = [];
|
|
59
|
-
const endpointUrl = await createLocalServer((socket, requestUrl, authHeader) => {
|
|
60
|
-
expect(requestUrl).toContain("model=aura-2-thalia-en");
|
|
61
|
-
expect(requestUrl).toContain("encoding=linear16");
|
|
62
|
-
expect(requestUrl).toContain("container=none");
|
|
63
|
-
expect(authHeader).toBe("Token test-deepgram-key");
|
|
64
|
-
socket.on("message", (data) => {
|
|
65
|
-
const msg = JSON.parse(data.toString()) as Record<string, unknown>;
|
|
66
|
-
received.push(msg);
|
|
67
|
-
if (msg["type"] === "Speak") {
|
|
68
|
-
socket.send(Buffer.from([1, 2, 3, 4]), { binary: true });
|
|
69
|
-
}
|
|
70
|
-
if (msg["type"] === "Flush") {
|
|
71
|
-
socket.send(JSON.stringify({ type: "Flushed", sequence_id: 0 }));
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const bus = new PipelineBusImpl();
|
|
77
|
-
const started = bus.start();
|
|
78
|
-
const plugin = new DeepgramTTSPlugin();
|
|
79
|
-
const audio: TextToSpeechAudioPacket[] = [];
|
|
80
|
-
const ends: TextToSpeechEndPacket[] = [];
|
|
81
|
-
bus.on("tts.audio", (pkt) => { audio.push(pkt as TextToSpeechAudioPacket); });
|
|
82
|
-
bus.on("tts.end", (pkt) => { ends.push(pkt as TextToSpeechEndPacket); });
|
|
83
|
-
|
|
84
|
-
await plugin.initialize(bus, {
|
|
85
|
-
api_key: "test-deepgram-key",
|
|
86
|
-
endpoint_url: endpointUrl,
|
|
87
|
-
sample_rate: 24000,
|
|
88
|
-
});
|
|
89
|
-
bus.push(Route.Main, { kind: "tts.text", contextId: "turn-1", timestampMs: Date.now(), text: "Hello there." });
|
|
90
|
-
bus.push(Route.Main, { kind: "tts.text", contextId: "turn-1", timestampMs: Date.now(), text: "Second sentence." });
|
|
91
|
-
bus.push(Route.Main, { kind: "tts.done", contextId: "turn-1", timestampMs: Date.now() });
|
|
92
|
-
await waitForCondition(() => ends.length >= 1 && audio.length >= 2);
|
|
93
|
-
|
|
94
|
-
expect(received).toEqual([
|
|
95
|
-
expect.objectContaining({ type: "Speak", text: "Hello there." }),
|
|
96
|
-
expect.objectContaining({ type: "Speak", text: "Second sentence." }),
|
|
97
|
-
expect.objectContaining({ type: "Flush" }),
|
|
98
|
-
]);
|
|
99
|
-
expect(audio.every((a) => a.contextId === "turn-1" && a.sampleRateHz === 24000)).toBe(true);
|
|
100
|
-
expect(audio[0]!.audio).toEqual(new Uint8Array([1, 2, 3, 4]));
|
|
101
|
-
expect(ends).toEqual([expect.objectContaining({ contextId: "turn-1" })]);
|
|
102
|
-
|
|
103
|
-
await plugin.close();
|
|
104
|
-
bus.stop();
|
|
105
|
-
await started;
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("emits tts.end when Deepgram streams audio but never acknowledges Flush", async () => {
|
|
109
|
-
const received: Array<Record<string, unknown>> = [];
|
|
110
|
-
const endpointUrl = await createLocalServer((socket) => {
|
|
111
|
-
socket.on("message", (data) => {
|
|
112
|
-
const msg = JSON.parse(data.toString()) as Record<string, unknown>;
|
|
113
|
-
received.push(msg);
|
|
114
|
-
if (msg["type"] === "Speak") socket.send(Buffer.from([5, 6, 7, 8]), { binary: true });
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const bus = new PipelineBusImpl();
|
|
119
|
-
const started = bus.start();
|
|
120
|
-
const plugin = new DeepgramTTSPlugin();
|
|
121
|
-
const audio: TextToSpeechAudioPacket[] = [];
|
|
122
|
-
const ends: TextToSpeechEndPacket[] = [];
|
|
123
|
-
const metrics: Array<{ name?: string; value?: string; contextId?: string }> = [];
|
|
124
|
-
bus.on("tts.audio", (pkt) => { audio.push(pkt as TextToSpeechAudioPacket); });
|
|
125
|
-
bus.on("tts.end", (pkt) => { ends.push(pkt as TextToSpeechEndPacket); });
|
|
126
|
-
bus.on("metric.conversation", (pkt) => {
|
|
127
|
-
const metric = pkt as { name?: string; value?: string; contextId?: string };
|
|
128
|
-
metrics.push(metric);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
await plugin.initialize(bus, {
|
|
132
|
-
api_key: "test-deepgram-key",
|
|
133
|
-
endpoint_url: endpointUrl,
|
|
134
|
-
finish_timeout_ms: 20,
|
|
135
|
-
});
|
|
136
|
-
bus.push(Route.Main, { kind: "tts.text", contextId: "turn-timeout", timestampMs: Date.now(), text: "Finish me." });
|
|
137
|
-
bus.push(Route.Main, { kind: "tts.done", contextId: "turn-timeout", timestampMs: Date.now() });
|
|
138
|
-
await waitForCondition(() => ends.length >= 1);
|
|
139
|
-
|
|
140
|
-
expect(received).toEqual([
|
|
141
|
-
expect.objectContaining({ type: "Speak", text: "Finish me." }),
|
|
142
|
-
expect.objectContaining({ type: "Flush" }),
|
|
143
|
-
]);
|
|
144
|
-
expect(audio).toEqual([
|
|
145
|
-
expect.objectContaining({
|
|
146
|
-
contextId: "turn-timeout",
|
|
147
|
-
audio: new Uint8Array([5, 6, 7, 8]),
|
|
148
|
-
}),
|
|
149
|
-
]);
|
|
150
|
-
expect(ends).toEqual([expect.objectContaining({ contextId: "turn-timeout" })]);
|
|
151
|
-
expect(metrics).toContainEqual(expect.objectContaining({
|
|
152
|
-
contextId: "turn-timeout",
|
|
153
|
-
name: "tts.deepgram.finish_timeout",
|
|
154
|
-
value: "20",
|
|
155
|
-
}));
|
|
156
|
-
|
|
157
|
-
await plugin.close();
|
|
158
|
-
bus.stop();
|
|
159
|
-
await started;
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("sends Clear and drops further audio on interruption", async () => {
|
|
163
|
-
const received: Array<Record<string, unknown>> = [];
|
|
164
|
-
let socketRef: WebSocket | null = null;
|
|
165
|
-
const endpointUrl = await createLocalServer((socket) => {
|
|
166
|
-
socketRef = socket;
|
|
167
|
-
socket.on("message", (data) => {
|
|
168
|
-
received.push(JSON.parse(data.toString()) as Record<string, unknown>);
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
const bus = new PipelineBusImpl();
|
|
173
|
-
const started = bus.start();
|
|
174
|
-
const plugin = new DeepgramTTSPlugin();
|
|
175
|
-
const audio: TextToSpeechAudioPacket[] = [];
|
|
176
|
-
bus.on("tts.audio", (pkt) => { audio.push(pkt as TextToSpeechAudioPacket); });
|
|
177
|
-
|
|
178
|
-
await plugin.initialize(bus, { api_key: "test-deepgram-key", endpoint_url: endpointUrl });
|
|
179
|
-
bus.push(Route.Main, { kind: "tts.text", contextId: "turn-x", timestampMs: Date.now(), text: "Interrupt me." });
|
|
180
|
-
await waitForCondition(() => received.some((m) => m["type"] === "Speak"));
|
|
181
|
-
bus.push(Route.Critical, { kind: "interrupt.tts", contextId: "turn-x", timestampMs: Date.now() });
|
|
182
|
-
await waitForCondition(() => received.some((m) => m["type"] === "Clear"));
|
|
183
|
-
|
|
184
|
-
// Audio arriving after the interrupt for the cancelled turn must be dropped.
|
|
185
|
-
socketRef!.send(Buffer.from([9, 9]), { binary: true });
|
|
186
|
-
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
187
|
-
|
|
188
|
-
expect(received).toEqual([
|
|
189
|
-
expect.objectContaining({ type: "Speak", text: "Interrupt me." }),
|
|
190
|
-
expect.objectContaining({ type: "Clear" }),
|
|
191
|
-
]);
|
|
192
|
-
expect(audio).toEqual([]);
|
|
193
|
-
|
|
194
|
-
await plugin.close();
|
|
195
|
-
bus.stop();
|
|
196
|
-
await started;
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("drops the interrupted turn's trailing PCM instead of misattributing it to the next turn", async () => {
|
|
200
|
-
const received: Array<Record<string, unknown>> = [];
|
|
201
|
-
let socketRef: WebSocket | null = null;
|
|
202
|
-
const endpointUrl = await createLocalServer((socket) => {
|
|
203
|
-
socketRef = socket;
|
|
204
|
-
socket.on("message", (data) => {
|
|
205
|
-
received.push(JSON.parse(data.toString()) as Record<string, unknown>);
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
const bus = new PipelineBusImpl();
|
|
210
|
-
const started = bus.start();
|
|
211
|
-
const plugin = new DeepgramTTSPlugin();
|
|
212
|
-
const audio: TextToSpeechAudioPacket[] = [];
|
|
213
|
-
bus.on("tts.audio", (pkt) => { audio.push(pkt as TextToSpeechAudioPacket); });
|
|
214
|
-
|
|
215
|
-
await plugin.initialize(bus, { api_key: "test-deepgram-key", endpoint_url: endpointUrl });
|
|
216
|
-
|
|
217
|
-
// Turn 1 speaks, user barges in -> Clear is sent and clearedPending arms.
|
|
218
|
-
bus.push(Route.Main, { kind: "tts.text", contextId: "turn-1", timestampMs: Date.now(), text: "First turn." });
|
|
219
|
-
await waitForCondition(() => received.some((m) => m["type"] === "Speak"));
|
|
220
|
-
bus.push(Route.Critical, { kind: "interrupt.tts", contextId: "turn-1", timestampMs: Date.now() });
|
|
221
|
-
await waitForCondition(() => received.some((m) => m["type"] === "Clear"));
|
|
222
|
-
|
|
223
|
-
// Turn 2 starts before Deepgram acks the Clear: currentContextId is now turn-2.
|
|
224
|
-
bus.push(Route.Main, { kind: "tts.text", contextId: "turn-2", timestampMs: Date.now(), text: "Second turn." });
|
|
225
|
-
await waitForCondition(() => received.filter((m) => m["type"] === "Speak").length >= 2);
|
|
226
|
-
|
|
227
|
-
// Trailing PCM from the interrupted turn arrives before "Cleared" — must be
|
|
228
|
-
// dropped, never attributed to turn-2.
|
|
229
|
-
socketRef!.send(Buffer.from([9, 9]), { binary: true });
|
|
230
|
-
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
231
|
-
|
|
232
|
-
// Deepgram acks the Clear; turn-2's real audio follows and must be emitted.
|
|
233
|
-
socketRef!.send(JSON.stringify({ type: "Cleared" }));
|
|
234
|
-
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
235
|
-
socketRef!.send(Buffer.from([2, 2]), { binary: true });
|
|
236
|
-
await waitForCondition(() => audio.length >= 1);
|
|
237
|
-
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
238
|
-
|
|
239
|
-
expect(audio).toEqual([
|
|
240
|
-
expect.objectContaining({ contextId: "turn-2", audio: new Uint8Array([2, 2]) }),
|
|
241
|
-
]);
|
|
242
|
-
|
|
243
|
-
await plugin.close();
|
|
244
|
-
bus.stop();
|
|
245
|
-
await started;
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it("emits a typed TTS error when Deepgram returns an Error frame", async () => {
|
|
249
|
-
const endpointUrl = await createLocalServer((socket) => {
|
|
250
|
-
socket.on("message", (data) => {
|
|
251
|
-
const msg = JSON.parse(data.toString()) as Record<string, unknown>;
|
|
252
|
-
if (msg["type"] === "Speak") {
|
|
253
|
-
socket.send(JSON.stringify({ type: "Error", description: "model not found" }));
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
const bus = new PipelineBusImpl();
|
|
259
|
-
const started = bus.start();
|
|
260
|
-
const plugin = new DeepgramTTSPlugin();
|
|
261
|
-
const errors: TtsErrorPacket[] = [];
|
|
262
|
-
bus.on("tts.error", (pkt) => { errors.push(pkt as TtsErrorPacket); });
|
|
263
|
-
|
|
264
|
-
await plugin.initialize(bus, { api_key: "test-deepgram-key", endpoint_url: endpointUrl });
|
|
265
|
-
bus.push(Route.Main, { kind: "tts.text", contextId: "turn-err", timestampMs: Date.now(), text: "Fail please." });
|
|
266
|
-
await waitForCondition(() => errors.length > 0);
|
|
267
|
-
|
|
268
|
-
expect(errors[0]).toEqual(
|
|
269
|
-
expect.objectContaining({ kind: "tts.error", contextId: "turn-err", component: "tts" }),
|
|
270
|
-
);
|
|
271
|
-
expect(errors[0]!.cause.message).toContain("model not found");
|
|
272
|
-
|
|
273
|
-
await plugin.close();
|
|
274
|
-
bus.stop();
|
|
275
|
-
await started;
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it("realigns PCM split across binary frame boundaries", async () => {
|
|
279
|
-
const endpointUrl = await createLocalServer((socket) => {
|
|
280
|
-
socket.on("message", (data) => {
|
|
281
|
-
const msg = JSON.parse(data.toString()) as Record<string, unknown>;
|
|
282
|
-
if (msg["type"] === "Speak") {
|
|
283
|
-
// Three bytes then one byte: the plugin must carry the odd byte over so
|
|
284
|
-
// every emitted chunk stays 16-bit aligned (no half-sample corruption).
|
|
285
|
-
socket.send(Buffer.from([0x11, 0x22, 0x33]), { binary: true });
|
|
286
|
-
socket.send(Buffer.from([0x44]), { binary: true });
|
|
287
|
-
}
|
|
288
|
-
if (msg["type"] === "Flush") socket.send(JSON.stringify({ type: "Flushed", sequence_id: 0 }));
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
const bus = new PipelineBusImpl();
|
|
293
|
-
const started = bus.start();
|
|
294
|
-
const plugin = new DeepgramTTSPlugin();
|
|
295
|
-
const audio: TextToSpeechAudioPacket[] = [];
|
|
296
|
-
const ends: TextToSpeechEndPacket[] = [];
|
|
297
|
-
bus.on("tts.audio", (pkt) => { audio.push(pkt as TextToSpeechAudioPacket); });
|
|
298
|
-
bus.on("tts.end", (pkt) => { ends.push(pkt as TextToSpeechEndPacket); });
|
|
299
|
-
|
|
300
|
-
await plugin.initialize(bus, { api_key: "test-deepgram-key", endpoint_url: endpointUrl });
|
|
301
|
-
bus.push(Route.Main, { kind: "tts.text", contextId: "turn-a", timestampMs: Date.now(), text: "Align me." });
|
|
302
|
-
bus.push(Route.Main, { kind: "tts.done", contextId: "turn-a", timestampMs: Date.now() });
|
|
303
|
-
await waitForCondition(() => ends.length >= 1);
|
|
304
|
-
|
|
305
|
-
const allBytes = Buffer.concat(audio.map((a) => Buffer.from(a.audio)));
|
|
306
|
-
expect(allBytes).toEqual(Buffer.from([0x11, 0x22, 0x33, 0x44]));
|
|
307
|
-
expect(audio.every((a) => a.audio.byteLength % 2 === 0)).toBe(true);
|
|
308
|
-
|
|
309
|
-
await plugin.close();
|
|
310
|
-
bus.stop();
|
|
311
|
-
await started;
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
it("emits tts.error when received PCM fails structural validation", async () => {
|
|
315
|
-
const payloadSpy = vi.spyOn(voice, "assertAudioPayload").mockImplementationOnce(() => {
|
|
316
|
-
throw new Error("PCM16 payload must contain an even number of bytes");
|
|
317
|
-
});
|
|
318
|
-
const endpointUrl = await createLocalServer((socket) => {
|
|
319
|
-
socket.on("message", (data) => {
|
|
320
|
-
const msg = JSON.parse(data.toString()) as Record<string, unknown>;
|
|
321
|
-
if (msg["type"] === "Speak") {
|
|
322
|
-
socket.send(Buffer.from([1, 2, 3, 4]), { binary: true });
|
|
323
|
-
}
|
|
324
|
-
if (msg["type"] === "Flush") {
|
|
325
|
-
socket.send(JSON.stringify({ type: "Flushed", sequence_id: 0 }));
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
const bus = new PipelineBusImpl();
|
|
331
|
-
const started = bus.start();
|
|
332
|
-
const plugin = new DeepgramTTSPlugin();
|
|
333
|
-
const audio: TextToSpeechAudioPacket[] = [];
|
|
334
|
-
const errors: TtsErrorPacket[] = [];
|
|
335
|
-
bus.on("tts.audio", (pkt) => { audio.push(pkt as TextToSpeechAudioPacket); });
|
|
336
|
-
bus.on("tts.error", (pkt) => { errors.push(pkt as TtsErrorPacket); });
|
|
337
|
-
|
|
338
|
-
await plugin.initialize(bus, { api_key: "test-deepgram-key", endpoint_url: endpointUrl });
|
|
339
|
-
bus.push(Route.Main, { kind: "tts.text", contextId: "turn-bad", timestampMs: Date.now(), text: "Bad framing." });
|
|
340
|
-
await waitForCondition(() => errors.length > 0);
|
|
341
|
-
|
|
342
|
-
expect(audio).toEqual([]);
|
|
343
|
-
expect(errors[0]).toEqual(
|
|
344
|
-
expect.objectContaining({
|
|
345
|
-
kind: "tts.error",
|
|
346
|
-
contextId: "turn-bad",
|
|
347
|
-
component: "tts",
|
|
348
|
-
cause: expect.objectContaining({
|
|
349
|
-
message: "PCM16 payload must contain an even number of bytes",
|
|
350
|
-
}),
|
|
351
|
-
}),
|
|
352
|
-
);
|
|
353
|
-
payloadSpy.mockRestore();
|
|
354
|
-
|
|
355
|
-
await plugin.close();
|
|
356
|
-
bus.stop();
|
|
357
|
-
await started;
|
|
358
|
-
});
|
|
359
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"lib": ["ES2022"],
|
|
7
|
-
"strict": true,
|
|
8
|
-
"noUncheckedIndexedAccess": true,
|
|
9
|
-
"noImplicitReturns": true,
|
|
10
|
-
"declaration": true,
|
|
11
|
-
"declarationMap": true,
|
|
12
|
-
"sourceMap": true,
|
|
13
|
-
"outDir": "./dist",
|
|
14
|
-
"rootDir": "./src",
|
|
15
|
-
"esModuleInterop": true,
|
|
16
|
-
"skipLibCheck": true,
|
|
17
|
-
"forceConsistentCasingInFileNames": true
|
|
18
|
-
},
|
|
19
|
-
"include": ["src/**/*.ts"],
|
|
20
|
-
"exclude": ["node_modules", "dist"]
|
|
21
|
-
}
|