File size: 4,249 Bytes
5569a86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31fb0af
 
5569a86
 
 
 
 
 
 
31fb0af
5569a86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31fb0af
5569a86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31fb0af
 
 
 
 
 
 
 
 
 
 
 
 
5569a86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/**
 * Turso/libSQL database client wrapper.
 *
 * Provides a thin wrapper on top of `@libsql/client` that mirrors the
 * `better-sqlite3` API shapes used throughout the codebase, but async.
 *
 * Key patterns:
 *   db.prepare(sql).run(...args)     → await db.run(sql, args)
 *   db.prepare(sql).get(...args)     → await db.get(sql, args)
 *   db.prepare(sql).all(...args)     → await db.all(sql, args)
 *   db.exec(multiSql)               → await db.execMulti(multiSql)
 *   db.transaction(() => { ... })()  → await db.batch([...])
 *   db.pragma(...)                   → await db.run('PRAGMA ...')
 */

import fs from 'fs';
import path from 'path';
import { createClient, type Client, type InStatement, type ResultSet, type Row } from '@libsql/client';

export interface TursoDb {
  client: Client;
  /** Execute a single statement, return full result set. */
  execute(sql: string, args?: any[]): Promise<ResultSet>;
  /** Execute a single statement (like .prepare(sql).run(args)). Returns { changes, lastInsertRowid }. */
  run(sql: string, args?: any[]): Promise<{ changes: number; lastInsertRowid: number }>;
  /** Execute a single statement and return the first row (like .prepare(sql).get(args)). */
  get<T = any>(sql: string, args?: any[]): Promise<T | undefined>;
  /** Execute a single statement and return all rows (like .prepare(sql).all(args)). */
  all<T = any>(sql: string, args?: any[]): Promise<T[]>;
  /** Execute multi-statement SQL (split by semicolons). */
  execMulti(sql: string): Promise<void>;
  /** Execute a batch of statements transactionally (replaces db.transaction). */
  batch(statements: InStatement[]): Promise<ResultSet[]>;
}

let db: TursoDb | null = null;

function createTursoDb(client: Client): TursoDb {
  return {
    client,

    async execute(sql: string, args: any[] = []): Promise<ResultSet> {
      return client.execute({ sql, args });
    },

    async run(sql: string, args: any[] = []) {
      const result = await client.execute({ sql, args });
      return {
        changes: result.rowsAffected,
        lastInsertRowid: result.lastInsertRowid !== undefined ? Number(result.lastInsertRowid) : 0,
      };
    },

    async get<T = any>(sql: string, args: any[] = []): Promise<T | undefined> {
      const result = await client.execute({ sql, args });
      return (result.rows[0] as T) ?? undefined;
    },

    async all<T = any>(sql: string, args: any[] = []): Promise<T[]> {
      const result = await client.execute({ sql, args });
      return result.rows as T[];
    },

    async execMulti(sql: string): Promise<void> {
      const statements = sql
        .split(';')
        .map(s => s.trim())
        .filter(s => s.length > 0);
      for (const stmt of statements) {
        await client.execute(stmt);
      }
    },

    async batch(statements: InStatement[]): Promise<ResultSet[]> {
      if (statements.length === 0) return [];
      return client.batch(statements, 'write');
    },
  };
}

/**
 * Initialize the Turso client. Call once at startup.
 * Uses TURSO_DATABASE_URL + TURSO_AUTH_TOKEN env vars.
 * Falls back to a local file if TURSO_DATABASE_URL is not set (dev mode).
 */
export function initClient(url?: string): TursoDb {
  const dbUrl = url ?? process.env.TURSO_DATABASE_URL ?? 'file:./data/freeapi.db';

  if (process.env.NODE_ENV === 'production' && !process.env.TURSO_DATABASE_URL && !url) {
    console.warn('\n[WARNING] TURSO_DATABASE_URL is not set in production. Falling back to local SQLite database. Any changes will be lost when the container restarts!\n');
  }

  if (dbUrl.startsWith('file:')) {
    const filePath = dbUrl.slice(5);
    const dir = path.dirname(filePath);
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }
  }

  const authToken = process.env.TURSO_AUTH_TOKEN;

  const client = createClient({
    url: dbUrl,
    authToken: authToken || undefined,
  });

  db = createTursoDb(client);
  return db;
}

/** Get the initialized database wrapper. Throws if not yet created. */
export function getDb(): TursoDb {
  if (!db) {
    throw new Error('Database not initialized. Call initClient() first.');
  }
  return db;
}

// Re-export types
export type { Client, ResultSet, InStatement, Row };