surreal-better-auth 0.2.1 → 0.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.
Files changed (2) hide show
  1. package/package.json +6 -4
  2. package/src/index.ts +154 -220
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "surreal-better-auth",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "author": {
5
5
  "name": "Oskar Gmerek",
6
6
  "url": "https://oskargmerek.com",
@@ -32,11 +32,13 @@
32
32
  "LICENSE"
33
33
  ],
34
34
  "devDependencies": {
35
- "@types/bun": "^1.1.14"
35
+ "@better-auth/utils": "^0.2.3",
36
+ "@types/bun": "^1.1.17",
37
+ "dayjs": "^1.11.13"
36
38
  },
37
39
  "peerDependencies": {
38
- "better-auth": "^1.0.22",
40
+ "better-auth": "^1.1.14",
39
41
  "surrealdb": "^1.1.0",
40
- "typescript": "^5.7.2"
42
+ "typescript": "^5.7.3"
41
43
  }
42
44
  }
package/src/index.ts CHANGED
@@ -1,228 +1,162 @@
1
- import type { Adapter, BetterAuthOptions, Where } from "better-auth/types";
2
- import { PreparedQuery, type Surreal } from "surrealdb";
3
-
4
- function composeWhereClause(where: Where[], model: string): string {
5
- if (!where.length) return "";
6
-
7
- return where
8
- .map(({ field, value, operator = "eq", connector = "AND" }, index) => {
9
- const val =
10
- typeof value === "string" ? `'${value.replace(/'/g, "\\'")}'` : value;
11
- const mod = `'${model.replace(/'/g, "\\'")}'`;
12
-
13
- const condition = {
14
- eq: () =>
15
- field === "id"
16
- ? `${field} = type::thing(${mod}, ${val})`
17
- : `${field} = ${val}`,
18
- in: () =>
19
- field === "id"
20
- ? `${field} IN [${
21
- Array.isArray(val)
22
- ? val.map((v) => `type::thing('${model}', ${v})`).join(", ")
23
- : `type::thing('${model}', ${val})`
24
- }]`
25
- : `${field} IN [${
26
- Array.isArray(value) ? value.map((v) => `${v}`).join(", ") : val
27
- }]`,
28
- gt: () => `${field} > ${val}`,
29
- gte: () => `${field} >= ${val}`,
30
- lt: () => `${field} < ${val}`,
31
- lte: () => `${field} <= ${val}`,
32
- ne: () => `${field} != ${val}`,
33
- contains: () => `${field} CONTAINS ${val}`,
34
- starts_with: () => `string::starts_with(${field}, ${val})`,
35
- ends_with: () => `string::ends_with(${field}, ${val})`,
36
- }[operator.toLowerCase() as typeof operator]();
37
-
38
- return index > 0 ? `${connector} ${condition}` : condition;
39
- })
40
- .join(" ");
41
- }
42
-
43
- function checkForIdInWhereClause(
44
- where: Where[],
45
- ): string | number | boolean | string[] | number[] | undefined {
46
- if (where.some(({ field }) => field === "id")) {
47
- return where.find(({ field }) => field === "id")?.value;
48
- }
49
- }
50
-
51
- export const surrealAdapter = (db: Surreal) => (options: BetterAuthOptions) => {
52
- if (!db) {
53
- throw new Error("SurrealDB adapter requires a SurrealDB client");
54
- }
55
- return {
56
- id: "surrealdb",
57
- async create(data) {
58
- const { model, data: val } = data;
59
-
60
- const query = new PreparedQuery(
61
- `CREATE type::table($model) CONTENT $val `,
62
- {
63
- model: model,
64
- val: val,
1
+ import { generateId } from 'better-auth';
2
+ import { getAuthTables } from 'better-auth/db';
3
+ import { Adapter, BetterAuthOptions, Where } from 'better-auth/types';
4
+ import { jsonify, RecordId, Surreal } from 'surrealdb';
5
+ import { withApplyDefault } from './utils';
6
+
7
+ const createTransform = (options: BetterAuthOptions) => {
8
+ const schema = getAuthTables(options);
9
+
10
+ function transformSelect(select: string[], model: string): string[] {
11
+ if (!select || select.length === 0) return [];
12
+ return select.map(field => getField(model, field));
13
+ }
14
+
15
+ function getField(model: string, field: string) {
16
+ if (field === "id") {
17
+ return field;
18
+ }
19
+ const f = schema[model].fields[field];
20
+ console.log({ f })
21
+ return f.fieldName || field;
22
+ }
23
+
24
+ return {
25
+ transformInput(data: Record<string, any>, model: string, action: "update" | "create") {
26
+ const transformedData: Record<string, any> =
27
+ action === "update"
28
+ ? {}
29
+ : {
30
+ id: options.advanced?.generateId
31
+ ? options.advanced.generateId({ model })
32
+ : data.id || generateId(),
33
+ };
34
+
35
+ const fields = schema[model].fields;
36
+ for (const field in fields) {
37
+ const value = data[field];
38
+ if (value === undefined && !fields[field].defaultValue) {
39
+ continue;
40
+ }
41
+ transformedData[fields[field].fieldName || field] = withApplyDefault(value, fields[field], action);
42
+ }
43
+ return transformedData;
65
44
  },
66
- );
67
-
68
- const response = await db.query<[any[]]>(query);
69
- const result = response[0][0];
70
- return result;
71
- },
72
- async findOne(data) {
73
- const { model, where, select = [] } = data;
74
-
75
- const wheres = composeWhereClause(where, model);
76
-
77
- const query =
78
- select.length > 0
79
- ? new PreparedQuery(
80
- `SELECT type::fields($selects) FROM IF $thing {type::thing($model, $thing)} ELSE {type::table($model)} WHERE $wheres;`,
81
- {
82
- id: select.includes("id")
83
- ? 'meta::id("id") as id, '
84
- : undefined,
85
- thing: checkForIdInWhereClause(where) || undefined,
86
- selects: select,
87
- model: model,
88
- wheres: wheres,
89
- },
90
- )
91
- : new PreparedQuery(
92
- `SELECT * FROM IF $thing {type::thing($model, $thing)} ELSE {type::table($model)} WHERE $wheres;`,
93
- {
94
- id: select.includes("id")
95
- ? 'meta::id("id") as id, '
96
- : undefined,
97
- thing: checkForIdInWhereClause(where) || undefined,
98
- model: model,
99
- wheres: wheres,
100
- },
101
- );
102
-
103
- const response = await db.query<[any[]]>(query);
104
- const result = response[0][0];
105
-
106
- if (!result) {
107
- return null;
108
- }
109
-
110
- return result;
111
- },
112
- async findMany(data) {
113
- const { model, where, limit, offset, sortBy } = data;
114
- const clauses: string[] = [];
115
-
116
- if (where) {
117
- const wheres = composeWhereClause(where, model);
118
- clauses.push(`WHERE ${wheres}`);
119
- }
120
- if (sortBy !== undefined) {
121
- clauses.push(`ORDER BY ${sortBy.field} ${sortBy.direction}`);
122
- }
123
- if (limit !== undefined) {
124
- clauses.push(`LIMIT type::number('${limit}')`);
125
- }
126
- if (offset !== undefined) {
127
- clauses.push(`START type::number('${offset}')`);
128
- }
129
-
130
- const query = new PreparedQuery(
131
- `SELECT * FROM type::table($model) ${
132
- clauses.length > 0 ? clauses.join(" ") : ""
133
- }`,
134
- {
135
- model: model,
45
+ transformOutput(data: Record<string, any>, model: string, select: string[] = []) {
46
+ if (!data) return null;
47
+ const transformedData: Record<string, any> =
48
+ data.id || data._id
49
+ ? select.length === 0 || select.includes("id")
50
+ ? { id: data.id }
51
+ : {}
52
+ : {};
53
+ const tableSchema = schema[model].fields;
54
+ for (const key in tableSchema) {
55
+ if (select.length && !select.includes(key)) {
56
+ continue;
57
+ }
58
+ const field = tableSchema[key];
59
+ if (field) {
60
+ transformedData[key] = data[field.fieldName || key];
61
+ }
62
+ }
63
+ return transformedData as any;
136
64
  },
137
- );
138
-
139
- const response = await db.query<[any[]]>(query);
140
- const result = response[0];
141
-
142
- return result;
143
- },
144
- async update(data) {
145
- const { model, where, update } = data;
146
- const wheres = composeWhereClause(where, model);
147
- if (!wheres)
148
- throw new Error("Empty conditions - possible unintended operation");
149
-
150
- if (update.id) {
151
- update.id = undefined;
152
- }
153
-
154
- const query = new PreparedQuery(
155
- "UPDATE type::table($model) MERGE { $update } WHERE $wheres",
156
- {
157
- model: model,
158
- update: update,
159
- wheres: wheres,
65
+ convertWhereClause(where: Where[], model: string) {
66
+ return where.map(clause => {
67
+ const { field: _field, value, operator } = clause;
68
+ const field = getField(model, _field);
69
+ switch (operator) {
70
+ case "eq":
71
+ return (field === 'id' || value instanceof RecordId) ?
72
+ `${field} = ${jsonify(value)}`
73
+ :
74
+ `${field} = '${jsonify(value)}'`
75
+ case "in":
76
+ return `${field} IN [${jsonify(value)}]`;
77
+ case "contains":
78
+ return `${field} CONTAINS '${jsonify(value)}'`;
79
+ case "starts_with":
80
+ return `string::starts_with(${field},'${value}')`;
81
+ case "ends_with":
82
+ return `string::ends_with(${field},'${value}')`;
83
+ default:
84
+ console.log({ field, value, recordid: value instanceof RecordId })
85
+ return (field === 'id' || value instanceof RecordId) ?
86
+ `${field} = ${jsonify(value)}`
87
+ :
88
+ `${field} = '${jsonify(value)}'`
89
+ }
90
+ }).join(' AND ');
160
91
  },
161
- );
162
-
163
- const response = await db.query<[any[]]>(query);
164
- const result = response[0][0];
165
-
166
- return result;
167
- },
168
- async updateMany(data) {
169
- const { model, where, update } = data;
170
- const wheres = composeWhereClause(where, model);
171
- if (!wheres)
172
- throw new Error("Empty conditions - possible unintended operation");
173
-
174
- if (update.id) {
175
- update.id = undefined;
176
- }
92
+ transformSelect,
93
+ getField,
94
+ };
95
+ };
177
96
 
178
- const query = new PreparedQuery(
179
- "UPDATE type::table($model) MERGE { $update } WHERE $wheres",
180
- {
181
- model: model,
182
- update: update,
183
- wheres: wheres,
97
+ export const surrealAdapter = (db: Surreal) => async (options: BetterAuthOptions) => {
98
+ if (!db) {
99
+ throw new Error("SurrealDB adapter requires a SurrealDB client");
100
+ }
101
+ const { transformInput, transformOutput, convertWhereClause, getField } = createTransform(options);
102
+
103
+ return {
104
+ id: "surreal",
105
+ create: async ({ model, data }) => {
106
+ const transformed = transformInput(data, model, "create");
107
+ const [result] = await db.create(model, transformed);
108
+ console.log({ model, transformed })
109
+ return transformOutput(result, model);
184
110
  },
185
- );
186
-
187
- const response = await db.query<[any[]]>(query);
188
- const result = response[0];
189
-
190
- return result.length;
191
- },
192
- async delete(data) {
193
- const { model, where } = data;
194
- const wheres = composeWhereClause(where, model);
195
- if (!wheres)
196
- throw new Error("Empty conditions - possible unintended operation");
197
-
198
- const query = new PreparedQuery(
199
- `DELETE type::table($model) WHERE ${wheres}`,
200
- {
201
- model: model,
202
- wheres: wheres,
111
+ findOne: async ({ model, where, select = [] }) => {
112
+ const whereClause = convertWhereClause(where, model);
113
+ const selectClause = select.length > 0 && select.map((f) => getField(model, f)) || []
114
+ const query = select.length > 0 ? `SELECT ${selectClause.join(', ')} FROM ${model} WHERE ${whereClause} LIMIT 1` : `SELECT * FROM ${model} WHERE ${whereClause} LIMIT 1`;
115
+ const result = await db.query<[any[]]>(query)
116
+ console.log({ whereClause, query, result: result[0][0] })
117
+ const output = transformOutput(result[0][0], model, select);
118
+ console.log({ output });
119
+ return transformOutput(result[0][0], model, select);
203
120
  },
204
- );
205
-
206
- await db.query(query);
207
- },
208
- async deleteMany(data) {
209
- const { model, where } = data;
210
- const wheres = composeWhereClause(where, model);
211
- if (!wheres)
212
- throw new Error("Empty conditions - possible unintended operation");
213
-
214
- const query = new PreparedQuery(
215
- `DELETE type::table($model) WHERE ${wheres}`,
216
- {
217
- model: model,
218
- wheres: wheres,
121
+ findMany: async ({ model, where, sortBy, limit, offset }) => {
122
+ let query = `SELECT * FROM ${model}`;
123
+ if (where) {
124
+ const whereClause = convertWhereClause(where, model);
125
+ query += ` WHERE ${whereClause}`;
126
+ }
127
+ if (sortBy) {
128
+ query += ` ORDER BY ${getField(model, sortBy.field)} ${sortBy.direction}`;
129
+ }
130
+ if (limit !== undefined) {
131
+ query += ` LIMIT ${limit}`;
132
+ }
133
+ if (offset !== undefined) {
134
+ query += ` START ${offset}`;
135
+ }
136
+ console.log({ query })
137
+ const [results] = await db.query<[any[]]>(query);
138
+ return results.map(record => transformOutput(record, model));
219
139
  },
220
- );
221
-
222
- const response = await db.query<[any[]]>(query);
223
- const result = response[0];
224
-
225
- return result.length;
226
- },
227
- } satisfies Adapter;
140
+ update: async ({ model, where, update }) => {
141
+ const whereClause = convertWhereClause(where, model);
142
+ const transformedUpdate = transformInput(update, model, "update");
143
+ const [result] = await db.query<[any[]]>(`UPDATE ${model} MERGE ${JSON.stringify(transformedUpdate)} WHERE ${whereClause}`);
144
+ return transformOutput(result[0], model);
145
+ },
146
+ delete: async ({ model, where }) => {
147
+ const whereClause = convertWhereClause(where, model);
148
+ await db.query(`DELETE FROM ${model} WHERE ${whereClause}`);
149
+ },
150
+ deleteMany: async ({ model, where }) => {
151
+ const whereClause = convertWhereClause(where, model);
152
+ const [result] = await db.query<[any[]]>(`DELETE FROM ${model} WHERE ${whereClause}`);
153
+ return result.length;
154
+ },
155
+ updateMany: async ({ model, where, update }) => {
156
+ const whereClause = convertWhereClause(where, model);
157
+ const transformedUpdate = transformInput(update, model, "update");
158
+ const [result] = await db.query<[any[]]>(`UPDATE ${model} MERGE ${JSON.stringify(transformedUpdate)} WHERE ${whereClause}`);
159
+ return transformOutput(result[0], model);
160
+ },
161
+ } satisfies Adapter;
228
162
  };