@copilotkitnext/sqlite-runner 0.0.15

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.
@@ -0,0 +1,226 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { SqliteAgentRunner } from "..";
3
+ import {
4
+ AbstractAgent,
5
+ BaseEvent,
6
+ EventType,
7
+ Message,
8
+ RunAgentInput,
9
+ RunStartedEvent,
10
+ TextMessageContentEvent,
11
+ TextMessageEndEvent,
12
+ TextMessageStartEvent,
13
+ } from "@ag-ui/client";
14
+ import { EMPTY, firstValueFrom } from "rxjs";
15
+ import { toArray } from "rxjs/operators";
16
+ import Database from "better-sqlite3";
17
+ import fs from "fs";
18
+ import os from "os";
19
+ import path from "path";
20
+
21
+ type RunCallbacks = {
22
+ onEvent: (event: { event: BaseEvent }) => void | Promise<void>;
23
+ onNewMessage?: (args: { message: Message }) => void | Promise<void>;
24
+ onRunStartedEvent?: () => void | Promise<void>;
25
+ };
26
+
27
+ class MockAgent extends AbstractAgent {
28
+ constructor(
29
+ private readonly events: BaseEvent[] = [],
30
+ private readonly emitDefaultRunStarted = true,
31
+ ) {
32
+ super();
33
+ }
34
+
35
+ async runAgent(input: RunAgentInput, callbacks: RunCallbacks): Promise<void> {
36
+ if (this.emitDefaultRunStarted) {
37
+ const runStarted: RunStartedEvent = {
38
+ type: EventType.RUN_STARTED,
39
+ threadId: input.threadId,
40
+ runId: input.runId,
41
+ };
42
+ await callbacks.onEvent({ event: runStarted });
43
+ await callbacks.onRunStartedEvent?.();
44
+ }
45
+
46
+ for (const event of this.events) {
47
+ await callbacks.onEvent({ event });
48
+ }
49
+ }
50
+
51
+ protected run(): ReturnType<AbstractAgent["run"]> {
52
+ return EMPTY;
53
+ }
54
+
55
+ protected connect(): ReturnType<AbstractAgent["connect"]> {
56
+ return EMPTY;
57
+ }
58
+
59
+ clone(): AbstractAgent {
60
+ return new MockAgent(this.events, this.emitDefaultRunStarted);
61
+ }
62
+ }
63
+
64
+ describe("SqliteAgentRunner", () => {
65
+ let tempDir: string;
66
+ let dbPath: string;
67
+ let runner: SqliteAgentRunner;
68
+
69
+ beforeEach(() => {
70
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "sqlite-runner-test-"));
71
+ dbPath = path.join(tempDir, "test.db");
72
+ runner = new SqliteAgentRunner({ dbPath });
73
+ });
74
+
75
+ afterEach(() => {
76
+ if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath);
77
+ if (fs.existsSync(tempDir)) fs.rmdirSync(tempDir);
78
+ });
79
+
80
+ it("emits RUN_STARTED and agent events", async () => {
81
+ const threadId = "sqlite-basic";
82
+ const agent = new MockAgent([
83
+ { type: EventType.TEXT_MESSAGE_START, messageId: "msg-1", role: "assistant" } as TextMessageStartEvent,
84
+ { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg-1", delta: "Hello" } as TextMessageContentEvent,
85
+ { type: EventType.TEXT_MESSAGE_END, messageId: "msg-1" } as TextMessageEndEvent,
86
+ ]);
87
+
88
+ const events = await firstValueFrom(
89
+ runner
90
+ .run({
91
+ threadId,
92
+ agent,
93
+ input: { threadId, runId: "run-1", messages: [], state: {} },
94
+ })
95
+ .pipe(toArray()),
96
+ );
97
+
98
+ expect(events.map((event) => event.type)).toEqual([
99
+ EventType.RUN_STARTED,
100
+ EventType.TEXT_MESSAGE_START,
101
+ EventType.TEXT_MESSAGE_CONTENT,
102
+ EventType.TEXT_MESSAGE_END,
103
+ ]);
104
+ });
105
+
106
+ it("attaches only new messages on subsequent runs", async () => {
107
+ const threadId = "sqlite-new-messages";
108
+ const existing: Message = { id: "existing", role: "user", content: "hi" };
109
+
110
+ await firstValueFrom(
111
+ runner
112
+ .run({
113
+ threadId,
114
+ agent: new MockAgent(),
115
+ input: { threadId, runId: "run-0", messages: [existing], state: {} },
116
+ })
117
+ .pipe(toArray()),
118
+ );
119
+
120
+ const newMessage: Message = { id: "new", role: "user", content: "follow up" };
121
+
122
+ const secondRun = await firstValueFrom(
123
+ runner
124
+ .run({
125
+ threadId,
126
+ agent: new MockAgent(),
127
+ input: {
128
+ threadId,
129
+ runId: "run-1",
130
+ messages: [existing, newMessage],
131
+ state: { counter: 1 },
132
+ },
133
+ })
134
+ .pipe(toArray()),
135
+ );
136
+
137
+ const runStarted = secondRun[0] as RunStartedEvent;
138
+ expect(runStarted.input?.messages?.map((m) => m.id)).toEqual(["new"]);
139
+
140
+ const db = new Database(dbPath);
141
+ const rows = db
142
+ .prepare("SELECT events FROM agent_runs WHERE thread_id = ? ORDER BY created_at")
143
+ .all(threadId) as { events: string }[];
144
+ db.close();
145
+
146
+ expect(rows).toHaveLength(2);
147
+ const run1Stored = JSON.parse(rows[0].events) as BaseEvent[];
148
+ const run2Stored = JSON.parse(rows[1].events) as BaseEvent[];
149
+
150
+ const run1Started = run1Stored.find((event) => event.type === EventType.RUN_STARTED) as RunStartedEvent;
151
+ expect(run1Started.input?.messages?.map((m) => m.id)).toEqual(["existing"]);
152
+
153
+ const run2Started = run2Stored.find((event) => event.type === EventType.RUN_STARTED) as RunStartedEvent;
154
+ expect(run2Started.input?.messages?.map((m) => m.id)).toEqual(["new"]);
155
+ });
156
+
157
+ it("preserves agent-provided input", async () => {
158
+ const threadId = "sqlite-agent-input";
159
+ const providedInput: RunAgentInput = {
160
+ threadId,
161
+ runId: "run-keep",
162
+ messages: [],
163
+ state: { fromAgent: true },
164
+ };
165
+
166
+ const agent = new MockAgent(
167
+ [
168
+ {
169
+ type: EventType.RUN_STARTED,
170
+ threadId,
171
+ runId: "run-keep",
172
+ input: providedInput,
173
+ } as RunStartedEvent,
174
+ ],
175
+ false,
176
+ );
177
+
178
+ const events = await firstValueFrom(
179
+ runner
180
+ .run({
181
+ threadId,
182
+ agent,
183
+ input: {
184
+ threadId,
185
+ runId: "run-keep",
186
+ messages: [{ id: "ignored", role: "user", content: "hi" }],
187
+ state: {},
188
+ },
189
+ })
190
+ .pipe(toArray()),
191
+ );
192
+
193
+ expect(events).toHaveLength(1);
194
+ const runStarted = events[0] as RunStartedEvent;
195
+ expect(runStarted.input).toBe(providedInput);
196
+ });
197
+
198
+ it("persists events across runner instances", async () => {
199
+ const threadId = "sqlite-persist";
200
+ const agent = new MockAgent([
201
+ { type: EventType.TEXT_MESSAGE_START, messageId: "msg", role: "assistant" } as TextMessageStartEvent,
202
+ { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg", delta: "hi" } as TextMessageContentEvent,
203
+ { type: EventType.TEXT_MESSAGE_END, messageId: "msg" } as TextMessageEndEvent,
204
+ ]);
205
+
206
+ await firstValueFrom(
207
+ runner
208
+ .run({
209
+ threadId,
210
+ agent,
211
+ input: { threadId, runId: "run-1", messages: [], state: {} },
212
+ })
213
+ .pipe(toArray()),
214
+ );
215
+
216
+ const newRunner = new SqliteAgentRunner({ dbPath });
217
+ const replayed = await firstValueFrom(newRunner.connect({ threadId }).pipe(toArray()));
218
+
219
+ expect(replayed[0].type).toBe(EventType.RUN_STARTED);
220
+ expect(replayed.slice(1).map((event) => event.type)).toEqual([
221
+ EventType.TEXT_MESSAGE_START,
222
+ EventType.TEXT_MESSAGE_CONTENT,
223
+ EventType.TEXT_MESSAGE_END,
224
+ ]);
225
+ });
226
+ });
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./sqlite-runner";
@@ -0,0 +1,419 @@
1
+ import {
2
+ AgentRunner,
3
+ type AgentRunnerConnectRequest,
4
+ type AgentRunnerIsRunningRequest,
5
+ type AgentRunnerRunRequest,
6
+ type AgentRunnerStopRequest,
7
+ } from "@copilotkitnext/runtime";
8
+ import { Observable, ReplaySubject } from "rxjs";
9
+ import {
10
+ BaseEvent,
11
+ RunAgentInput,
12
+ EventType,
13
+ RunStartedEvent,
14
+ compactEvents,
15
+ } from "@ag-ui/client";
16
+ import Database from "better-sqlite3";
17
+
18
+ const SCHEMA_VERSION = 1;
19
+
20
+ interface AgentRunRecord {
21
+ id: number;
22
+ thread_id: string;
23
+ run_id: string;
24
+ parent_run_id: string | null;
25
+ events: BaseEvent[];
26
+ input: RunAgentInput;
27
+ created_at: number;
28
+ version: number;
29
+ }
30
+
31
+ export interface SqliteAgentRunnerOptions {
32
+ dbPath?: string;
33
+ }
34
+
35
+ // Active connections for streaming events
36
+ // This is the only in-memory state we need - just for active streaming
37
+ const ACTIVE_CONNECTIONS = new Map<string, ReplaySubject<BaseEvent>>();
38
+
39
+ export class SqliteAgentRunner extends AgentRunner {
40
+ private db: any;
41
+
42
+ constructor(options: SqliteAgentRunnerOptions = {}) {
43
+ super();
44
+ const dbPath = options.dbPath ?? ":memory:";
45
+
46
+ if (!Database) {
47
+ throw new Error(
48
+ 'better-sqlite3 is required for SqliteAgentRunner but was not found.\n' +
49
+ 'Please install it in your project:\n' +
50
+ ' npm install better-sqlite3\n' +
51
+ ' or\n' +
52
+ ' pnpm add better-sqlite3\n' +
53
+ ' or\n' +
54
+ ' yarn add better-sqlite3\n\n' +
55
+ 'If you don\'t need persistence, use InMemoryAgentRunner instead.'
56
+ );
57
+ }
58
+
59
+ this.db = new Database(dbPath);
60
+ this.initializeSchema();
61
+ }
62
+
63
+ private initializeSchema(): void {
64
+ // Create the agent_runs table
65
+ this.db.exec(`
66
+ CREATE TABLE IF NOT EXISTS agent_runs (
67
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
68
+ thread_id TEXT NOT NULL,
69
+ run_id TEXT NOT NULL UNIQUE,
70
+ parent_run_id TEXT,
71
+ events TEXT NOT NULL,
72
+ input TEXT NOT NULL,
73
+ created_at INTEGER NOT NULL,
74
+ version INTEGER NOT NULL
75
+ )
76
+ `);
77
+
78
+ // Create run_state table to track active runs
79
+ this.db.exec(`
80
+ CREATE TABLE IF NOT EXISTS run_state (
81
+ thread_id TEXT PRIMARY KEY,
82
+ is_running INTEGER DEFAULT 0,
83
+ current_run_id TEXT,
84
+ updated_at INTEGER NOT NULL
85
+ )
86
+ `);
87
+
88
+ // Create indexes for efficient queries
89
+ this.db.exec(`
90
+ CREATE INDEX IF NOT EXISTS idx_thread_id ON agent_runs(thread_id);
91
+ CREATE INDEX IF NOT EXISTS idx_parent_run_id ON agent_runs(parent_run_id);
92
+ `);
93
+
94
+ // Create schema version table
95
+ this.db.exec(`
96
+ CREATE TABLE IF NOT EXISTS schema_version (
97
+ version INTEGER PRIMARY KEY,
98
+ applied_at INTEGER NOT NULL
99
+ )
100
+ `);
101
+
102
+ // Check and set schema version
103
+ const currentVersion = this.db
104
+ .prepare("SELECT version FROM schema_version ORDER BY version DESC LIMIT 1")
105
+ .get() as { version: number } | undefined;
106
+
107
+ if (!currentVersion || currentVersion.version < SCHEMA_VERSION) {
108
+ this.db
109
+ .prepare("INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, ?)")
110
+ .run(SCHEMA_VERSION, Date.now());
111
+ }
112
+ }
113
+
114
+ private storeRun(
115
+ threadId: string,
116
+ runId: string,
117
+ events: BaseEvent[],
118
+ input: RunAgentInput,
119
+ parentRunId?: string | null
120
+ ): void {
121
+ // Compact ONLY the events from this run
122
+ const compactedEvents = compactEvents(events);
123
+
124
+ const stmt = this.db.prepare(`
125
+ INSERT INTO agent_runs (thread_id, run_id, parent_run_id, events, input, created_at, version)
126
+ VALUES (?, ?, ?, ?, ?, ?, ?)
127
+ `);
128
+
129
+ stmt.run(
130
+ threadId,
131
+ runId,
132
+ parentRunId ?? null,
133
+ JSON.stringify(compactedEvents), // Store only this run's compacted events
134
+ JSON.stringify(input),
135
+ Date.now(),
136
+ SCHEMA_VERSION
137
+ );
138
+ }
139
+
140
+ private getHistoricRuns(threadId: string): AgentRunRecord[] {
141
+ const stmt = this.db.prepare(`
142
+ WITH RECURSIVE run_chain AS (
143
+ -- Base case: find the root runs (those without parent)
144
+ SELECT * FROM agent_runs
145
+ WHERE thread_id = ? AND parent_run_id IS NULL
146
+
147
+ UNION ALL
148
+
149
+ -- Recursive case: find children of current level
150
+ SELECT ar.* FROM agent_runs ar
151
+ INNER JOIN run_chain rc ON ar.parent_run_id = rc.run_id
152
+ WHERE ar.thread_id = ?
153
+ )
154
+ SELECT * FROM run_chain
155
+ ORDER BY created_at ASC
156
+ `);
157
+
158
+ const rows = stmt.all(threadId, threadId) as any[];
159
+
160
+ return rows.map(row => ({
161
+ id: row.id,
162
+ thread_id: row.thread_id,
163
+ run_id: row.run_id,
164
+ parent_run_id: row.parent_run_id,
165
+ events: JSON.parse(row.events),
166
+ input: JSON.parse(row.input),
167
+ created_at: row.created_at,
168
+ version: row.version
169
+ }));
170
+ }
171
+
172
+ private getLatestRunId(threadId: string): string | null {
173
+ const stmt = this.db.prepare(`
174
+ SELECT run_id FROM agent_runs
175
+ WHERE thread_id = ?
176
+ ORDER BY created_at DESC
177
+ LIMIT 1
178
+ `);
179
+
180
+ const result = stmt.get(threadId) as { run_id: string } | undefined;
181
+ return result?.run_id ?? null;
182
+ }
183
+
184
+ private setRunState(threadId: string, isRunning: boolean, runId?: string): void {
185
+ const stmt = this.db.prepare(`
186
+ INSERT OR REPLACE INTO run_state (thread_id, is_running, current_run_id, updated_at)
187
+ VALUES (?, ?, ?, ?)
188
+ `);
189
+ stmt.run(threadId, isRunning ? 1 : 0, runId ?? null, Date.now());
190
+ }
191
+
192
+ private getRunState(threadId: string): { isRunning: boolean; currentRunId: string | null } {
193
+ const stmt = this.db.prepare(`
194
+ SELECT is_running, current_run_id FROM run_state WHERE thread_id = ?
195
+ `);
196
+ const result = stmt.get(threadId) as { is_running: number; current_run_id: string | null } | undefined;
197
+
198
+ return {
199
+ isRunning: result?.is_running === 1,
200
+ currentRunId: result?.current_run_id ?? null
201
+ };
202
+ }
203
+
204
+ run(request: AgentRunnerRunRequest): Observable<BaseEvent> {
205
+ // Check if thread is already running in database
206
+ const runState = this.getRunState(request.threadId);
207
+ if (runState.isRunning) {
208
+ throw new Error("Thread already running");
209
+ }
210
+
211
+ // Mark thread as running in database
212
+ this.setRunState(request.threadId, true, request.input.runId);
213
+
214
+ // Track seen message IDs and current run events in memory for this run
215
+ const seenMessageIds = new Set<string>();
216
+ const currentRunEvents: BaseEvent[] = [];
217
+
218
+ // Get all previously seen message IDs from historic runs
219
+ const historicRuns = this.getHistoricRuns(request.threadId);
220
+ const historicMessageIds = new Set<string>();
221
+ for (const run of historicRuns) {
222
+ for (const event of run.events) {
223
+ if ('messageId' in event && typeof event.messageId === 'string') {
224
+ historicMessageIds.add(event.messageId);
225
+ }
226
+ if (event.type === EventType.RUN_STARTED) {
227
+ const runStarted = event as RunStartedEvent;
228
+ const messages = runStarted.input?.messages ?? [];
229
+ for (const message of messages) {
230
+ historicMessageIds.add(message.id);
231
+ }
232
+ }
233
+ }
234
+ }
235
+
236
+ // Get or create subject for this thread's connections
237
+ const nextSubject = new ReplaySubject<BaseEvent>(Infinity);
238
+ const prevSubject = ACTIVE_CONNECTIONS.get(request.threadId);
239
+
240
+ // Update the active connection for this thread
241
+ ACTIVE_CONNECTIONS.set(request.threadId, nextSubject);
242
+
243
+ // Create a subject for run() return value
244
+ const runSubject = new ReplaySubject<BaseEvent>(Infinity);
245
+
246
+ // Helper function to run the agent and handle errors
247
+ const runAgent = async () => {
248
+ // Get parent run ID for chaining
249
+ const parentRunId = this.getLatestRunId(request.threadId);
250
+
251
+ try {
252
+ await request.agent.runAgent(request.input, {
253
+ onEvent: ({ event }) => {
254
+ let processedEvent: BaseEvent = event;
255
+ if (event.type === EventType.RUN_STARTED) {
256
+ const runStartedEvent = event as RunStartedEvent;
257
+ if (!runStartedEvent.input) {
258
+ const sanitizedMessages = request.input.messages
259
+ ? request.input.messages.filter(
260
+ (message) => !historicMessageIds.has(message.id),
261
+ )
262
+ : undefined;
263
+ const updatedInput = {
264
+ ...request.input,
265
+ ...(sanitizedMessages !== undefined
266
+ ? { messages: sanitizedMessages }
267
+ : {}),
268
+ };
269
+ processedEvent = {
270
+ ...runStartedEvent,
271
+ input: updatedInput,
272
+ } as RunStartedEvent;
273
+ }
274
+ }
275
+
276
+ runSubject.next(processedEvent); // For run() return - only agent events
277
+ nextSubject.next(processedEvent); // For connect() / store - all events
278
+ currentRunEvents.push(processedEvent); // Accumulate for database storage
279
+ },
280
+ onNewMessage: ({ message }) => {
281
+ // Called for each new message
282
+ if (!seenMessageIds.has(message.id)) {
283
+ seenMessageIds.add(message.id);
284
+ }
285
+ },
286
+ onRunStartedEvent: () => {
287
+ // Mark input messages as seen without emitting duplicates
288
+ if (request.input.messages) {
289
+ for (const message of request.input.messages) {
290
+ if (!seenMessageIds.has(message.id)) {
291
+ seenMessageIds.add(message.id);
292
+ }
293
+ }
294
+ }
295
+ },
296
+ });
297
+
298
+ // Store the run in database
299
+ this.storeRun(
300
+ request.threadId,
301
+ request.input.runId,
302
+ currentRunEvents,
303
+ request.input,
304
+ parentRunId
305
+ );
306
+
307
+ // Mark run as complete in database
308
+ this.setRunState(request.threadId, false);
309
+
310
+ // Complete the subjects
311
+ runSubject.complete();
312
+ nextSubject.complete();
313
+ } catch {
314
+ // Store the run even if it failed (partial events)
315
+ if (currentRunEvents.length > 0) {
316
+ this.storeRun(
317
+ request.threadId,
318
+ request.input.runId,
319
+ currentRunEvents,
320
+ request.input,
321
+ parentRunId
322
+ );
323
+ }
324
+
325
+ // Mark run as complete in database
326
+ this.setRunState(request.threadId, false);
327
+
328
+ // Don't emit error to the subject, just complete it
329
+ // This allows subscribers to get events emitted before the error
330
+ runSubject.complete();
331
+ nextSubject.complete();
332
+ }
333
+ };
334
+
335
+ // Bridge previous events if they exist
336
+ if (prevSubject) {
337
+ prevSubject.subscribe({
338
+ next: (e) => nextSubject.next(e),
339
+ error: (err) => nextSubject.error(err),
340
+ complete: () => {
341
+ // Don't complete nextSubject here - it needs to stay open for new events
342
+ },
343
+ });
344
+ }
345
+
346
+ // Start the agent execution immediately (not lazily)
347
+ runAgent();
348
+
349
+ // Return the run subject (only agent events, no injected messages)
350
+ return runSubject.asObservable();
351
+ }
352
+
353
+ connect(request: AgentRunnerConnectRequest): Observable<BaseEvent> {
354
+ const connectionSubject = new ReplaySubject<BaseEvent>(Infinity);
355
+
356
+ // Load historic runs from database
357
+ const historicRuns = this.getHistoricRuns(request.threadId);
358
+
359
+ // Collect all historic events from database
360
+ const allHistoricEvents: BaseEvent[] = [];
361
+ for (const run of historicRuns) {
362
+ allHistoricEvents.push(...run.events);
363
+ }
364
+
365
+ // Compact all events together before emitting
366
+ const compactedEvents = compactEvents(allHistoricEvents);
367
+
368
+ // Emit compacted events and track message IDs
369
+ const emittedMessageIds = new Set<string>();
370
+ for (const event of compactedEvents) {
371
+ connectionSubject.next(event);
372
+ if ('messageId' in event && typeof event.messageId === 'string') {
373
+ emittedMessageIds.add(event.messageId);
374
+ }
375
+ }
376
+
377
+ // Bridge active run to connection if exists
378
+ const activeSubject = ACTIVE_CONNECTIONS.get(request.threadId);
379
+ const runState = this.getRunState(request.threadId);
380
+
381
+ if (activeSubject && runState.isRunning) {
382
+ activeSubject.subscribe({
383
+ next: (event) => {
384
+ // Skip message events that we've already emitted from historic
385
+ if ('messageId' in event && typeof event.messageId === 'string' && emittedMessageIds.has(event.messageId)) {
386
+ return;
387
+ }
388
+ connectionSubject.next(event);
389
+ },
390
+ complete: () => connectionSubject.complete(),
391
+ error: (err) => connectionSubject.error(err)
392
+ });
393
+ } else {
394
+ // No active run, complete after historic events
395
+ connectionSubject.complete();
396
+ }
397
+
398
+ return connectionSubject.asObservable();
399
+ }
400
+
401
+ isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean> {
402
+ const runState = this.getRunState(request.threadId);
403
+ return Promise.resolve(runState.isRunning);
404
+ }
405
+
406
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
407
+ stop(_request: AgentRunnerStopRequest): Promise<boolean | undefined> {
408
+ throw new Error("Method not implemented.");
409
+ }
410
+
411
+ /**
412
+ * Close the database connection (for cleanup)
413
+ */
414
+ close(): void {
415
+ if (this.db) {
416
+ this.db.close();
417
+ }
418
+ }
419
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "@copilotkitnext/typescript-config/base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "sourceMap": true,
9
+ "types": ["vitest/globals"]
10
+ },
11
+ "include": ["src/**/*"],
12
+ "exclude": ["dist", "node_modules", "src/**/__tests__/**", "src/**/*.test.ts"]
13
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['cjs', 'esm'],
6
+ dts: true,
7
+ sourcemap: true,
8
+ clean: true,
9
+ target: 'es2022',
10
+ outDir: 'dist',
11
+ });
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "node",
6
+ globals: true,
7
+ include: ["src/**/__tests__/**/*.{test,spec}.ts"],
8
+ coverage: {
9
+ reporter: ["text", "lcov", "html"],
10
+ include: ["src/**/*.ts"],
11
+ exclude: ["src/**/*.d.ts", "src/**/index.ts"],
12
+ },
13
+ setupFiles: [],
14
+ },
15
+ });