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
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