feat: add src/db/adapter/surrealdb.ts
swadhinbiswas committed 8 months ago
d2c6c0d
1 parent 4b593e0
commit d2c6c0d
1 file changed
+199 -0
Showing 1 changed file+199-0
▼src/db/adapter/surrealdb.ts
+199-0
| @@ -0,0 +1,199 @@ | ||
1 | +import { | |
2 | + BaseDatabaseAdapter, | |
3 | + ColumnType, | |
4 | + DatabaseConfig, | |
5 | + Migration, | |
6 | + MigrationStatus, | |
7 | + QueryOptions, | |
8 | + QueryResult, | |
9 | + TableAlterCommand, | |
10 | + TableSchema, | |
11 | + WhereClause, | |
12 | +} from "./types"; | |
13 | + | |
14 | +const dynamicImport = new Function("m", "return import(m)") as (m: string) => Promise<any>; | |
15 | + | |
16 | +function buildWhere(where?: WhereClause[]): { sql: string; params: Record<string, any> } { | |
17 | + if (!where?.length) return { sql: "", params: {} }; | |
18 | + const parts: string[] = []; | |
19 | + const params: Record<string, any> = {}; | |
20 | + where.forEach((w, i) => { | |
21 | + const k = `p${i}`; | |
22 | + params[k] = w.value; | |
23 | + switch (w.operator) { | |
24 | + case "=": | |
25 | + case "eq": | |
26 | + parts.push(`${w.field} = $${k}`); | |
27 | + break; | |
28 | + case "!=": | |
29 | + case "ne": | |
30 | + parts.push(`${w.field} != $${k}`); | |
31 | + break; | |
32 | + case ">": | |
33 | + case "gt": | |
34 | + parts.push(`${w.field} > $${k}`); | |
35 | + break; | |
36 | + case ">=": | |
37 | + case "gte": | |
38 | + parts.push(`${w.field} >= $${k}`); | |
39 | + break; | |
40 | + case "<": | |
41 | + case "lt": | |
42 | + parts.push(`${w.field} < $${k}`); | |
43 | + break; | |
44 | + case "<=": | |
45 | + case "lte": | |
46 | + parts.push(`${w.field} <= $${k}`); | |
47 | + break; | |
48 | + default: | |
49 | + break; | |
50 | + } | |
51 | + }); | |
52 | + return { sql: parts.length ? ` WHERE ${parts.join(" AND ")}` : "", params }; | |
53 | +} | |
54 | + | |
55 | +export class SurrealDBAdapter extends BaseDatabaseAdapter { | |
56 | + private db: any; | |
57 | + | |
58 | + async connect(): Promise<void> { | |
59 | + if (this.connected) return; | |
60 | + | |
61 | + let mod: any; | |
62 | + try { | |
63 | + mod = await dynamicImport("surrealdb"); | |
64 | + } catch { | |
65 | + mod = await dynamicImport("surrealdb.js"); | |
66 | + } | |
67 | + | |
68 | + const Surreal = mod.Surreal || mod.default || mod; | |
69 | + this.db = new Surreal(); | |
70 | + | |
71 | + await this.db.connect(this.config.url); | |
72 | + if (this.config.user && this.config.password) { | |
73 | + await this.db.signin({ user: this.config.user, pass: this.config.password }); | |
74 | + } | |
75 | + await this.db.use({ | |
76 | + namespace: (this.config.options?.namespace as string) || "opencodehub", | |
77 | + database: this.config.database || "opencodehub", | |
78 | + }); | |
79 | + this.connected = true; | |
80 | + } | |
81 | + | |
82 | + async disconnect(): Promise<void> { | |
83 | + if (!this.connected) return; | |
84 | + if (this.db?.close) await this.db.close(); | |
85 | + this.connected = false; | |
86 | + } | |
87 | + | |
88 | + async findOne<T>(table: string, options?: QueryOptions): Promise<T | null> { | |
89 | + const rows = await this.findMany<T>(table, { ...options, limit: 1 }); | |
90 | + return rows[0] || null; | |
91 | + } | |
92 | + | |
93 | + async findMany<T>(table: string, options?: QueryOptions): Promise<T[]> { | |
94 | + const where = buildWhere(options?.where); | |
95 | + const limit = options?.limit ? ` LIMIT ${options.limit}` : ""; | |
96 | + const query = `SELECT * FROM ${table}${where.sql}${limit};`; | |
97 | + const result = await this.db.query(query, where.params); | |
98 | + const rows = result?.[0]?.result || []; | |
99 | + return rows as T[]; | |
100 | + } | |
101 | + | |
102 | + async create<T>(table: string, data: Partial<T>): Promise<T> { | |
103 | + const row: any = { ...(data as any) }; | |
104 | + if (!row.id) row.id = `${Date.now()}_${Math.random().toString(36).slice(2)}`; | |
105 | + const result = await this.db.create(`${table}:${row.id}`, row); | |
106 | + return (Array.isArray(result) ? result[0] : result) as T; | |
107 | + } | |
108 | + | |
109 | + async createMany<T>(table: string, data: Partial<T>[]): Promise<T[]> { | |
110 | + const out: T[] = []; | |
111 | + for (const item of data) out.push(await this.create<T>(table, item)); | |
112 | + return out; | |
113 | + } | |
114 | + | |
115 | + async update<T>(table: string, id: string | number, data: Partial<T>): Promise<T | null> { | |
116 | + const result = await this.db.merge(`${table}:${id}`, data as any); | |
117 | + return (Array.isArray(result) ? result[0] : result) as T; | |
118 | + } | |
119 | + | |
120 | + async updateMany<T>(table: string, options: QueryOptions, data: Partial<T>): Promise<number> { | |
121 | + const rows = await this.findMany<any>(table, options); | |
122 | + for (const row of rows) { | |
123 | + await this.update(table, row.id || row?.id?.id || row?.id?.toString?.() || "", data); | |
124 | + } | |
125 | + return rows.length; | |
126 | + } | |
127 | + | |
128 | + async delete(table: string, id: string | number): Promise<boolean> { | |
129 | + await this.db.delete(`${table}:${id}`); | |
130 | + return true; | |
131 | + } | |
132 | + | |
133 | + async deleteMany(table: string, options?: QueryOptions): Promise<number> { | |
134 | + const rows = await this.findMany<any>(table, options); | |
135 | + for (const row of rows) { | |
136 | + const id = row.id || row?.id?.id || row?.id?.toString?.(); | |
137 | + if (id) await this.delete(table, id); | |
138 | + } | |
139 | + return rows.length; | |
140 | + } | |
141 | + | |
142 | + async count(table: string, options?: QueryOptions): Promise<number> { | |
143 | + const rows = await this.findMany<any>(table, options); | |
144 | + return rows.length; | |
145 | + } | |
146 | + | |
147 | + async rawQuery<T>(query: string, params?: unknown[]): Promise<QueryResult<T>> { | |
148 | + const bindings = (params as Record<string, any>) || {}; | |
149 | + const result = await this.db.query(query, bindings); | |
150 | + const rows = result?.[0]?.result || []; | |
151 | + return { | |
152 | + rows: rows as T[], | |
153 | + rowCount: rows.length, | |
154 | + }; | |
155 | + } | |
156 | + | |
157 | + async beginTransaction(): Promise<void> { | |
158 | + await this.db.query("BEGIN TRANSACTION;"); | |
159 | + } | |
160 | + | |
161 | + async commitTransaction(): Promise<void> { | |
162 | + await this.db.query("COMMIT TRANSACTION;"); | |
163 | + } | |
164 | + | |
165 | + async rollbackTransaction(): Promise<void> { | |
166 | + await this.db.query("CANCEL TRANSACTION;"); | |
167 | + } | |
168 | + | |
169 | + async tableExists(table: string): Promise<boolean> { | |
170 | + try { | |
171 | + await this.db.query(`INFO FOR TABLE ${table};`); | |
172 | + return true; | |
173 | + } catch { | |
174 | + return false; | |
175 | + } | |
176 | + } | |
177 | + | |
178 | + async createTable(table: string, schema: TableSchema): Promise<void> { | |
179 | + await this.db.query(`DEFINE TABLE ${table} SCHEMALESS;`); | |
180 | + } | |
181 | + | |
182 | + async dropTable(table: string): Promise<void> { | |
183 | + await this.db.query(`REMOVE TABLE ${table};`); | |
184 | + } | |
185 | + | |
186 | + async alterTable(table: string, changes: TableAlterCommand[]): Promise<void> {} | |
187 | + | |
188 | + async runMigration(migration: Migration): Promise<void> { | |
189 | + await migration.up(this); | |
190 | + } | |
191 | + | |
192 | + async getMigrationStatus(): Promise<MigrationStatus[]> { | |
193 | + return []; | |
194 | + } | |
195 | + | |
196 | + protected mapColumnType(type: ColumnType): string { | |
197 | + return type; | |
198 | + } | |
199 | +} | |
0 | 200 | |