@xylex-group/better-auth-athena 1.0.0 → 1.0.2
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.
- package/README.md +7 -4
- package/dist/index.cjs +404 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.js +397 -80
- package/dist/index.js.map +1 -1
- package/package.json +67 -65
package/dist/index.js
CHANGED
|
@@ -2,39 +2,202 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createAdapterFactory
|
|
4
4
|
} from "better-auth/adapters";
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
createClient
|
|
7
|
+
} from "@xylex-group/athena";
|
|
8
|
+
|
|
9
|
+
// src/config.ts
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import YAML from "yaml";
|
|
13
|
+
var defaultAthenaGlobalConfig = {
|
|
14
|
+
athena: {
|
|
15
|
+
url: "http://localhost:3000",
|
|
16
|
+
apiKey: "",
|
|
17
|
+
client: "better-auth"
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var DEFAULT_CONFIG_FILENAME = "config.yaml";
|
|
21
|
+
function resolveConfigPath(configPath) {
|
|
22
|
+
if (configPath) return path.resolve(configPath);
|
|
23
|
+
return path.resolve(process.cwd(), DEFAULT_CONFIG_FILENAME);
|
|
24
|
+
}
|
|
25
|
+
var cached = null;
|
|
26
|
+
var cachedConfigPath = null;
|
|
27
|
+
var version = 0;
|
|
28
|
+
var watcher = null;
|
|
29
|
+
function isObject(value) {
|
|
30
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31
|
+
}
|
|
32
|
+
function deepMerge(base, partial) {
|
|
33
|
+
if (!isObject(partial)) return base;
|
|
34
|
+
const out = { ...base };
|
|
35
|
+
for (const [k, v] of Object.entries(partial)) {
|
|
36
|
+
if (v && isObject(v) && isObject(out[k])) {
|
|
37
|
+
out[k] = deepMerge(out[k], v);
|
|
38
|
+
} else {
|
|
39
|
+
out[k] = v;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
function ensureConfigFile(configPath) {
|
|
45
|
+
const dir = path.dirname(configPath);
|
|
46
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
47
|
+
if (!fs.existsSync(configPath)) {
|
|
48
|
+
const yaml = YAML.stringify(defaultAthenaGlobalConfig);
|
|
49
|
+
fs.writeFileSync(configPath, yaml, "utf-8");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function readConfigFromDisk(configPath) {
|
|
53
|
+
ensureConfigFile(configPath);
|
|
54
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
55
|
+
const parsed = YAML.parse(raw);
|
|
56
|
+
return deepMerge(defaultAthenaGlobalConfig, parsed);
|
|
57
|
+
}
|
|
58
|
+
function startWatcher(configPath) {
|
|
59
|
+
if (cachedConfigPath !== null && cachedConfigPath !== configPath && watcher) {
|
|
60
|
+
try {
|
|
61
|
+
watcher.close();
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
watcher = null;
|
|
65
|
+
}
|
|
66
|
+
if (watcher || cachedConfigPath === configPath) return;
|
|
67
|
+
try {
|
|
68
|
+
watcher = fs.watch(configPath, { persistent: false }, (event) => {
|
|
69
|
+
if (event !== "change" && event !== "rename") return;
|
|
70
|
+
try {
|
|
71
|
+
cached = readConfigFromDisk(configPath);
|
|
72
|
+
version += 1;
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
cachedConfigPath = configPath;
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function getAthenaGlobalConfig(options) {
|
|
81
|
+
const configPath = resolveConfigPath(options?.configPath);
|
|
82
|
+
const shouldWatch = options?.watch ?? true;
|
|
83
|
+
if (!cached || cachedConfigPath !== configPath) {
|
|
84
|
+
cached = readConfigFromDisk(configPath);
|
|
85
|
+
cachedConfigPath = configPath;
|
|
86
|
+
version += 1;
|
|
87
|
+
}
|
|
88
|
+
if (shouldWatch) startWatcher(configPath);
|
|
89
|
+
return { config: cached, version };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/index.ts
|
|
93
|
+
function toSnakeCase(key) {
|
|
94
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/__/g, "_").toLowerCase();
|
|
95
|
+
}
|
|
96
|
+
function toCamelCase(key) {
|
|
97
|
+
return key.replace(/_([a-z0-9])/g, (_, ch) => ch.toUpperCase());
|
|
98
|
+
}
|
|
99
|
+
function hasUppercase(key) {
|
|
100
|
+
return /[A-Z]/.test(key);
|
|
101
|
+
}
|
|
102
|
+
function mapKeys(obj, mapKey) {
|
|
103
|
+
const out = {};
|
|
104
|
+
for (const [k, v] of Object.entries(obj)) out[mapKey(k)] = v;
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
function mapRowToBetterAuth(row) {
|
|
108
|
+
if (!row || typeof row !== "object") return row;
|
|
109
|
+
if (Array.isArray(row)) return row.map(mapRowToBetterAuth);
|
|
110
|
+
return mapKeys(row, toCamelCase);
|
|
111
|
+
}
|
|
112
|
+
function isLikelyIsoDateString(value) {
|
|
113
|
+
if (!/^\d{4}-\d{2}-\d{2}T/.test(value)) return false;
|
|
114
|
+
const ms = Date.parse(value);
|
|
115
|
+
return Number.isFinite(ms);
|
|
116
|
+
}
|
|
117
|
+
function isTimestampKey(key) {
|
|
118
|
+
return key.endsWith("At") || key.endsWith("_at") || key === "expires";
|
|
119
|
+
}
|
|
120
|
+
function coerceDateFields(data) {
|
|
121
|
+
const out = { ...data };
|
|
122
|
+
for (const [key, val] of Object.entries(out)) {
|
|
123
|
+
if (val == null) continue;
|
|
124
|
+
if (typeof val === "string" && isTimestampKey(key) && isLikelyIsoDateString(val)) {
|
|
125
|
+
out[key] = new Date(val);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
function toDbRecord(data) {
|
|
131
|
+
const withDbKeys = mapKeys(
|
|
132
|
+
data,
|
|
133
|
+
(k) => hasUppercase(k) ? toSnakeCase(k) : k
|
|
134
|
+
);
|
|
135
|
+
return coerceDateFields(withDbKeys);
|
|
136
|
+
}
|
|
137
|
+
function applyWhere(builder, field, operator, value, columnMapper = (col) => hasUppercase(col) ? toSnakeCase(col) : col) {
|
|
138
|
+
const dbField = columnMapper(field);
|
|
7
139
|
switch (operator) {
|
|
8
140
|
case "eq":
|
|
9
|
-
return builder.eq(
|
|
141
|
+
return builder.eq(dbField, value);
|
|
10
142
|
case "ne":
|
|
11
|
-
return builder.neq(
|
|
143
|
+
return builder.neq(dbField, value);
|
|
12
144
|
case "gt":
|
|
13
|
-
return builder.gt(
|
|
145
|
+
return builder.gt(dbField, value);
|
|
14
146
|
case "gte":
|
|
15
|
-
return builder.gte(
|
|
147
|
+
return builder.gte(dbField, value);
|
|
16
148
|
case "lt":
|
|
17
|
-
return builder.lt(
|
|
149
|
+
return builder.lt(dbField, value);
|
|
18
150
|
case "lte":
|
|
19
|
-
return builder.lte(
|
|
151
|
+
return builder.lte(dbField, value);
|
|
20
152
|
case "in":
|
|
21
|
-
return builder.in(
|
|
153
|
+
return builder.in(dbField, value);
|
|
22
154
|
case "not_in":
|
|
23
|
-
return builder.not(
|
|
155
|
+
return builder.not(dbField, "in", value);
|
|
24
156
|
case "contains":
|
|
25
|
-
return builder.like(
|
|
157
|
+
return builder.like(dbField, `%${value}%`);
|
|
26
158
|
case "starts_with":
|
|
27
|
-
return builder.like(
|
|
159
|
+
return builder.like(dbField, `${value}%`);
|
|
28
160
|
case "ends_with":
|
|
29
|
-
return builder.like(
|
|
161
|
+
return builder.like(dbField, `%${value}`);
|
|
30
162
|
default:
|
|
31
|
-
return builder.eq(
|
|
163
|
+
return builder.eq(dbField, value);
|
|
32
164
|
}
|
|
33
165
|
}
|
|
166
|
+
function isMissingColumnError(error) {
|
|
167
|
+
const msg = String(error ?? "");
|
|
168
|
+
return msg.includes("specified column does not exist") || msg.includes("column does not exist");
|
|
169
|
+
}
|
|
34
170
|
var athenaAdapter = (config) => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
171
|
+
let dbClient = null;
|
|
172
|
+
let lastDbConfigVersion = -1;
|
|
173
|
+
const shouldUseFixedConfig = typeof config.url === "string" && config.url.length > 0 && typeof config.apiKey === "string" && config.apiKey.length > 0;
|
|
174
|
+
function ensureDbClient() {
|
|
175
|
+
if (shouldUseFixedConfig) {
|
|
176
|
+
if (!dbClient) {
|
|
177
|
+
dbClient = createClient(config.url, config.apiKey, {
|
|
178
|
+
client: config.client
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return dbClient;
|
|
182
|
+
}
|
|
183
|
+
const { config: globalConfig, version: version2 } = getAthenaGlobalConfig({
|
|
184
|
+
configPath: config.configPath,
|
|
185
|
+
watch: config.watchConfig ?? true
|
|
186
|
+
});
|
|
187
|
+
const url = config.url ?? globalConfig.athena.url;
|
|
188
|
+
const apiKey = config.apiKey ?? globalConfig.athena.apiKey;
|
|
189
|
+
const client = config.client ?? globalConfig.athena.client;
|
|
190
|
+
if (!url || !apiKey) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`[AthenaAdapter] Missing Athena connection details. Set both 'athena.url' and 'athena.apiKey' in config.yaml (or pass 'url'/'apiKey' to athenaAdapter).`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
if (!dbClient || version2 !== lastDbConfigVersion) {
|
|
196
|
+
dbClient = createClient(url, apiKey, { client });
|
|
197
|
+
lastDbConfigVersion = version2;
|
|
198
|
+
}
|
|
199
|
+
return dbClient;
|
|
200
|
+
}
|
|
38
201
|
return createAdapterFactory({
|
|
39
202
|
config: {
|
|
40
203
|
adapterId: "athena",
|
|
@@ -45,143 +208,297 @@ var athenaAdapter = (config) => {
|
|
|
45
208
|
supportsJSON: true,
|
|
46
209
|
supportsDates: true,
|
|
47
210
|
supportsBooleans: true,
|
|
48
|
-
supportsNumericIds: true
|
|
211
|
+
supportsNumericIds: true,
|
|
212
|
+
supportsUUIDs: true
|
|
49
213
|
},
|
|
50
214
|
adapter: () => {
|
|
51
215
|
return {
|
|
52
216
|
// ------------------------------------------------------------------
|
|
53
217
|
// CREATE
|
|
54
218
|
// ------------------------------------------------------------------
|
|
55
|
-
create: async ({
|
|
56
|
-
|
|
219
|
+
create: async ({
|
|
220
|
+
model,
|
|
221
|
+
data
|
|
222
|
+
}) => {
|
|
223
|
+
const db = ensureDbClient();
|
|
224
|
+
const insertData = toDbRecord(data);
|
|
225
|
+
const { data: result, error } = await db.from(model).insert(insertData).select();
|
|
57
226
|
if (error) {
|
|
58
|
-
throw new Error(
|
|
227
|
+
throw new Error(
|
|
228
|
+
`[AthenaAdapter] create on "${model}" failed: ${error}`
|
|
229
|
+
);
|
|
59
230
|
}
|
|
60
231
|
const row = Array.isArray(result) ? result[0] : result;
|
|
61
|
-
return row ??
|
|
232
|
+
return mapRowToBetterAuth(row ?? insertData);
|
|
62
233
|
},
|
|
63
234
|
// ------------------------------------------------------------------
|
|
64
235
|
// UPDATE
|
|
65
236
|
// ------------------------------------------------------------------
|
|
66
|
-
update: async ({
|
|
67
|
-
|
|
237
|
+
update: async ({
|
|
238
|
+
model,
|
|
239
|
+
where,
|
|
240
|
+
update
|
|
241
|
+
}) => {
|
|
242
|
+
const db = ensureDbClient();
|
|
243
|
+
const updateData = toDbRecord(update);
|
|
244
|
+
let builder = db.from(model).update(updateData);
|
|
68
245
|
for (const clause of where) {
|
|
69
|
-
builder = applyWhere(
|
|
246
|
+
builder = applyWhere(
|
|
247
|
+
builder,
|
|
248
|
+
clause.field,
|
|
249
|
+
clause.operator,
|
|
250
|
+
clause.value
|
|
251
|
+
);
|
|
70
252
|
}
|
|
71
253
|
const { data: result, error } = await builder.select();
|
|
72
254
|
if (error) {
|
|
73
|
-
throw new Error(
|
|
255
|
+
throw new Error(
|
|
256
|
+
`[AthenaAdapter] update on "${model}" failed: ${error}`
|
|
257
|
+
);
|
|
74
258
|
}
|
|
75
259
|
const row = Array.isArray(result) ? result[0] : result;
|
|
76
|
-
return row
|
|
260
|
+
return row ? mapRowToBetterAuth(row) : null;
|
|
77
261
|
},
|
|
78
262
|
// ------------------------------------------------------------------
|
|
79
263
|
// UPDATE MANY
|
|
80
264
|
// ------------------------------------------------------------------
|
|
81
|
-
updateMany: async ({
|
|
82
|
-
|
|
265
|
+
updateMany: async ({
|
|
266
|
+
model,
|
|
267
|
+
where,
|
|
268
|
+
update
|
|
269
|
+
}) => {
|
|
270
|
+
const db = ensureDbClient();
|
|
271
|
+
const updateData = toDbRecord(update);
|
|
272
|
+
let builder = db.from(model).update(updateData);
|
|
83
273
|
for (const clause of where) {
|
|
84
|
-
builder = applyWhere(
|
|
274
|
+
builder = applyWhere(
|
|
275
|
+
builder,
|
|
276
|
+
clause.field,
|
|
277
|
+
clause.operator,
|
|
278
|
+
clause.value
|
|
279
|
+
);
|
|
85
280
|
}
|
|
86
281
|
const { data: result, error } = await builder.select();
|
|
87
282
|
if (error) {
|
|
88
|
-
throw new Error(
|
|
283
|
+
throw new Error(
|
|
284
|
+
`[AthenaAdapter] updateMany on "${model}" failed: ${error}`
|
|
285
|
+
);
|
|
89
286
|
}
|
|
90
287
|
return Array.isArray(result) ? result.length : result ? 1 : 0;
|
|
91
288
|
},
|
|
92
289
|
// ------------------------------------------------------------------
|
|
93
290
|
// DELETE
|
|
94
291
|
// ------------------------------------------------------------------
|
|
95
|
-
delete: async ({
|
|
292
|
+
delete: async ({
|
|
293
|
+
model,
|
|
294
|
+
where
|
|
295
|
+
}) => {
|
|
296
|
+
const db = ensureDbClient();
|
|
96
297
|
let builder = db.from(model);
|
|
97
298
|
for (const clause of where) {
|
|
98
|
-
builder = applyWhere(
|
|
299
|
+
builder = applyWhere(
|
|
300
|
+
builder,
|
|
301
|
+
clause.field,
|
|
302
|
+
clause.operator,
|
|
303
|
+
clause.value
|
|
304
|
+
);
|
|
99
305
|
}
|
|
100
306
|
const { error } = await builder.delete();
|
|
101
307
|
if (error) {
|
|
102
|
-
throw new Error(
|
|
308
|
+
throw new Error(
|
|
309
|
+
`[AthenaAdapter] delete on "${model}" failed: ${error}`
|
|
310
|
+
);
|
|
103
311
|
}
|
|
104
312
|
},
|
|
105
313
|
// ------------------------------------------------------------------
|
|
106
314
|
// DELETE MANY
|
|
107
315
|
// ------------------------------------------------------------------
|
|
108
|
-
deleteMany: async ({
|
|
316
|
+
deleteMany: async ({
|
|
317
|
+
model,
|
|
318
|
+
where
|
|
319
|
+
}) => {
|
|
320
|
+
const db = ensureDbClient();
|
|
109
321
|
let builder = db.from(model);
|
|
110
322
|
for (const clause of where) {
|
|
111
|
-
builder = applyWhere(
|
|
323
|
+
builder = applyWhere(
|
|
324
|
+
builder,
|
|
325
|
+
clause.field,
|
|
326
|
+
clause.operator,
|
|
327
|
+
clause.value
|
|
328
|
+
);
|
|
112
329
|
}
|
|
113
330
|
const { data: result, error } = await builder.delete().select();
|
|
114
331
|
if (error) {
|
|
115
|
-
throw new Error(
|
|
332
|
+
throw new Error(
|
|
333
|
+
`[AthenaAdapter] deleteMany on "${model}" failed: ${error}`
|
|
334
|
+
);
|
|
116
335
|
}
|
|
117
336
|
return Array.isArray(result) ? result.length : result ? 1 : 0;
|
|
118
337
|
},
|
|
119
338
|
// ------------------------------------------------------------------
|
|
120
339
|
// FIND ONE
|
|
121
340
|
// ------------------------------------------------------------------
|
|
122
|
-
findOne: async ({
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
341
|
+
findOne: async ({
|
|
342
|
+
model,
|
|
343
|
+
where,
|
|
344
|
+
select
|
|
345
|
+
}) => {
|
|
346
|
+
const db = ensureDbClient();
|
|
347
|
+
const snakeMapper = (col) => hasUppercase(col) ? toSnakeCase(col) : col;
|
|
348
|
+
const identityMapper = (col) => col;
|
|
349
|
+
const run = async (columnMapper) => {
|
|
350
|
+
const columns = select && select.length > 0 ? select.map((c) => columnMapper(c)).join(", ") : void 0;
|
|
351
|
+
let builder = db.from(model).select(columns);
|
|
352
|
+
for (const clause of where) {
|
|
353
|
+
builder = applyWhere(
|
|
354
|
+
builder,
|
|
355
|
+
clause.field,
|
|
356
|
+
clause.operator,
|
|
357
|
+
clause.value,
|
|
358
|
+
columnMapper
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
const { data: result, error } = await builder.limit(1);
|
|
362
|
+
return { result, error };
|
|
363
|
+
};
|
|
364
|
+
const first = await run(snakeMapper);
|
|
365
|
+
if (first.error) {
|
|
366
|
+
if (isMissingColumnError(first.error)) {
|
|
367
|
+
const retry = await run(identityMapper);
|
|
368
|
+
if (retry.error) {
|
|
369
|
+
throw new Error(
|
|
370
|
+
`[AthenaAdapter] findOne on "${model}" failed: ${retry.error}`
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
const rows2 = Array.isArray(retry.result) ? retry.result : retry.result ? [retry.result] : [];
|
|
374
|
+
const row2 = rows2[0] ?? null;
|
|
375
|
+
return row2 ? mapRowToBetterAuth(row2) : null;
|
|
376
|
+
}
|
|
377
|
+
throw new Error(
|
|
378
|
+
`[AthenaAdapter] findOne on "${model}" failed: ${first.error}`
|
|
379
|
+
);
|
|
131
380
|
}
|
|
132
|
-
const rows = Array.isArray(result) ? result : result ? [result] : [];
|
|
133
|
-
|
|
381
|
+
const rows = Array.isArray(first.result) ? first.result : first.result ? [first.result] : [];
|
|
382
|
+
const row = rows[0] ?? null;
|
|
383
|
+
return row ? mapRowToBetterAuth(row) : null;
|
|
134
384
|
},
|
|
135
385
|
// ------------------------------------------------------------------
|
|
136
386
|
// FIND MANY
|
|
137
387
|
// ------------------------------------------------------------------
|
|
138
|
-
findMany: async ({
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
388
|
+
findMany: async ({
|
|
389
|
+
model,
|
|
390
|
+
where,
|
|
391
|
+
limit,
|
|
392
|
+
sortBy,
|
|
393
|
+
offset,
|
|
394
|
+
select
|
|
395
|
+
}) => {
|
|
396
|
+
const db = ensureDbClient();
|
|
397
|
+
const snakeMapper = (col) => hasUppercase(col) ? toSnakeCase(col) : col;
|
|
398
|
+
const identityMapper = (col) => col;
|
|
399
|
+
const run = async (columnMapper) => {
|
|
400
|
+
const columns = select && select.length > 0 ? select.map((c) => columnMapper(c)).join(", ") : void 0;
|
|
401
|
+
let builder = db.from(model).select(columns);
|
|
402
|
+
if (where) {
|
|
403
|
+
for (const clause of where) {
|
|
404
|
+
builder = applyWhere(
|
|
405
|
+
builder,
|
|
406
|
+
clause.field,
|
|
407
|
+
clause.operator,
|
|
408
|
+
clause.value,
|
|
409
|
+
columnMapper
|
|
410
|
+
);
|
|
411
|
+
}
|
|
144
412
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
413
|
+
if (limit !== void 0) {
|
|
414
|
+
builder = builder.limit(limit);
|
|
415
|
+
}
|
|
416
|
+
if (offset !== void 0) {
|
|
417
|
+
builder = builder.offset(offset);
|
|
418
|
+
}
|
|
419
|
+
const { data: result, error } = await builder;
|
|
420
|
+
return { result, error };
|
|
421
|
+
};
|
|
422
|
+
const first = await run(snakeMapper);
|
|
423
|
+
const pickRows = (res) => Array.isArray(res) ? res : [];
|
|
424
|
+
const applySort = (rows) => {
|
|
425
|
+
if (!sortBy) return rows;
|
|
426
|
+
const sortField = sortBy.field;
|
|
158
427
|
rows.sort((a, b) => {
|
|
159
|
-
const aVal = a[
|
|
160
|
-
const bVal = b[
|
|
428
|
+
const aVal = a[sortField];
|
|
429
|
+
const bVal = b[sortField];
|
|
161
430
|
if (aVal == null && bVal == null) return 0;
|
|
162
431
|
if (aVal == null) return sortBy.direction === "asc" ? -1 : 1;
|
|
163
432
|
if (bVal == null) return sortBy.direction === "asc" ? 1 : -1;
|
|
164
433
|
const cmp = typeof aVal === "string" && typeof bVal === "string" ? aVal.localeCompare(bVal) : aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
165
434
|
return sortBy.direction === "asc" ? cmp : -cmp;
|
|
166
435
|
});
|
|
436
|
+
return rows;
|
|
437
|
+
};
|
|
438
|
+
const mapAndSort = (rows) => {
|
|
439
|
+
const betterAuthRows = rows.map(
|
|
440
|
+
(r) => mapRowToBetterAuth(r)
|
|
441
|
+
);
|
|
442
|
+
return applySort(betterAuthRows);
|
|
443
|
+
};
|
|
444
|
+
if (first.error) {
|
|
445
|
+
if (isMissingColumnError(first.error)) {
|
|
446
|
+
const retry = await run(identityMapper);
|
|
447
|
+
if (retry.error) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`[AthenaAdapter] findMany on "${model}" failed: ${retry.error}`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
return mapAndSort(pickRows(retry.result));
|
|
453
|
+
}
|
|
454
|
+
throw new Error(
|
|
455
|
+
`[AthenaAdapter] findMany on "${model}" failed: ${first.error}`
|
|
456
|
+
);
|
|
167
457
|
}
|
|
168
|
-
return
|
|
458
|
+
return mapAndSort(pickRows(first.result));
|
|
169
459
|
},
|
|
170
460
|
// ------------------------------------------------------------------
|
|
171
461
|
// COUNT
|
|
172
462
|
// ------------------------------------------------------------------
|
|
173
|
-
count: async ({
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
463
|
+
count: async ({
|
|
464
|
+
model,
|
|
465
|
+
where
|
|
466
|
+
}) => {
|
|
467
|
+
const db = ensureDbClient();
|
|
468
|
+
const snakeMapper = (col) => hasUppercase(col) ? toSnakeCase(col) : col;
|
|
469
|
+
const identityMapper = (col) => col;
|
|
470
|
+
const run = async (columnMapper) => {
|
|
471
|
+
let builder = db.from(model).select();
|
|
472
|
+
if (where) {
|
|
473
|
+
for (const clause of where) {
|
|
474
|
+
builder = applyWhere(
|
|
475
|
+
builder,
|
|
476
|
+
clause.field,
|
|
477
|
+
clause.operator,
|
|
478
|
+
clause.value,
|
|
479
|
+
columnMapper
|
|
480
|
+
);
|
|
481
|
+
}
|
|
178
482
|
}
|
|
483
|
+
const { data: result, error } = await builder;
|
|
484
|
+
return { result, error };
|
|
485
|
+
};
|
|
486
|
+
const first = await run(snakeMapper);
|
|
487
|
+
if (first.error) {
|
|
488
|
+
if (isMissingColumnError(first.error)) {
|
|
489
|
+
const retry = await run(identityMapper);
|
|
490
|
+
if (retry.error) {
|
|
491
|
+
throw new Error(
|
|
492
|
+
`[AthenaAdapter] count on "${model}" failed: ${retry.error}`
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
return Array.isArray(retry.result) ? retry.result.length : 0;
|
|
496
|
+
}
|
|
497
|
+
throw new Error(
|
|
498
|
+
`[AthenaAdapter] count on "${model}" failed: ${first.error}`
|
|
499
|
+
);
|
|
179
500
|
}
|
|
180
|
-
|
|
181
|
-
if (error) {
|
|
182
|
-
throw new Error(`[AthenaAdapter] count on "${model}" failed: ${error}`);
|
|
183
|
-
}
|
|
184
|
-
return Array.isArray(result) ? result.length : 0;
|
|
501
|
+
return Array.isArray(first.result) ? first.result.length : 0;
|
|
185
502
|
}
|
|
186
503
|
};
|
|
187
504
|
}
|