@datar-platform/better-auth-dynamodb 0.1.0-alpha.0 → 0.1.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.
- package/CHANGELOG.md +17 -0
- package/dist/index.d.ts +267 -17
- package/dist/index.js +623 -13
- package/dist/index.js.map +1 -1
- package/package.json +33 -23
- package/dist/adapter.d.ts +0 -19
- package/dist/adapter.d.ts.map +0 -1
- package/dist/adapter.js +0 -153
- package/dist/adapter.js.map +0 -1
- package/dist/index-map.d.ts +0 -33
- package/dist/index-map.d.ts.map +0 -1
- package/dist/index-map.js +0 -1
- package/dist/index-map.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/pagination.d.ts +0 -25
- package/dist/pagination.d.ts.map +0 -1
- package/dist/pagination.js +0 -86
- package/dist/pagination.js.map +0 -1
- package/dist/planner.d.ts +0 -36
- package/dist/planner.d.ts.map +0 -1
- package/dist/planner.js +0 -55
- package/dist/planner.js.map +0 -1
- package/dist/stores/default/derive-index-map.d.ts +0 -18
- package/dist/stores/default/derive-index-map.d.ts.map +0 -1
- package/dist/stores/default/derive-index-map.js +0 -46
- package/dist/stores/default/derive-index-map.js.map +0 -1
- package/dist/stores/default/key-codec.d.ts +0 -57
- package/dist/stores/default/key-codec.d.ts.map +0 -1
- package/dist/stores/default/key-codec.js +0 -106
- package/dist/stores/default/key-codec.js.map +0 -1
- package/dist/stores/default/schema.d.ts +0 -28
- package/dist/stores/default/schema.d.ts.map +0 -1
- package/dist/stores/default/schema.js +0 -91
- package/dist/stores/default/schema.js.map +0 -1
- package/dist/stores/default/single-table-store.d.ts +0 -26
- package/dist/stores/default/single-table-store.d.ts.map +0 -1
- package/dist/stores/default/single-table-store.js +0 -128
- package/dist/stores/default/single-table-store.js.map +0 -1
- package/dist/types.d.ts +0 -97
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
- package/dist/types.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,15 +1,625 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// src/adapter.ts
|
|
2
|
+
import { createAdapterFactory } from "better-auth/adapters";
|
|
3
|
+
|
|
4
|
+
// src/pagination.ts
|
|
5
|
+
async function drainPages(fetch) {
|
|
6
|
+
const out = [];
|
|
7
|
+
let cursor = void 0;
|
|
8
|
+
do {
|
|
9
|
+
const page = await fetch(cursor);
|
|
10
|
+
out.push(...page.items);
|
|
11
|
+
cursor = page.cursor;
|
|
12
|
+
} while (cursor);
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
function matchOne(item, w) {
|
|
16
|
+
const actual = item[w.field];
|
|
17
|
+
const expected = w.value;
|
|
18
|
+
switch (w.operator) {
|
|
19
|
+
case "eq":
|
|
20
|
+
return actual === expected;
|
|
21
|
+
case "ne":
|
|
22
|
+
return actual !== expected;
|
|
23
|
+
case "lt":
|
|
24
|
+
return actual < expected;
|
|
25
|
+
case "lte":
|
|
26
|
+
return actual <= expected;
|
|
27
|
+
case "gt":
|
|
28
|
+
return actual > expected;
|
|
29
|
+
case "gte":
|
|
30
|
+
return actual >= expected;
|
|
31
|
+
case "in":
|
|
32
|
+
return Array.isArray(expected) && expected.includes(actual);
|
|
33
|
+
case "not_in":
|
|
34
|
+
return Array.isArray(expected) && !expected.includes(actual);
|
|
35
|
+
case "contains":
|
|
36
|
+
return String(actual).includes(String(expected));
|
|
37
|
+
case "starts_with":
|
|
38
|
+
return String(actual).startsWith(String(expected));
|
|
39
|
+
case "ends_with":
|
|
40
|
+
return String(actual).endsWith(String(expected));
|
|
41
|
+
default:
|
|
42
|
+
return actual === expected;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function matchesResidual(item, residual) {
|
|
46
|
+
const andOk = residual.filter((w) => w.connector !== "OR").every((w) => matchOne(item, w));
|
|
47
|
+
if (!andOk) return false;
|
|
48
|
+
const or = residual.filter((w) => w.connector === "OR");
|
|
49
|
+
return or.length === 0 || or.some((w) => matchOne(item, w));
|
|
50
|
+
}
|
|
51
|
+
function applySort(items, sortBy) {
|
|
52
|
+
if (!sortBy) return items;
|
|
53
|
+
const { field, direction } = sortBy;
|
|
54
|
+
const dir = direction === "asc" ? 1 : -1;
|
|
55
|
+
return [...items].sort((a, b) => {
|
|
56
|
+
if (a[field] < b[field]) return -dir;
|
|
57
|
+
if (a[field] > b[field]) return dir;
|
|
58
|
+
return 0;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function applyWindow(items, offset, limit) {
|
|
62
|
+
let out = items;
|
|
63
|
+
if (offset && offset > 0) out = out.slice(offset);
|
|
64
|
+
if (limit && limit > 0) out = out.slice(0, limit);
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/planner.ts
|
|
69
|
+
function isKeyable(w) {
|
|
70
|
+
return w.operator === "eq" && w.connector === "AND" && w.value != null;
|
|
71
|
+
}
|
|
72
|
+
function planQuery(model, where, indexMap) {
|
|
73
|
+
const eqByField = /* @__PURE__ */ new Map();
|
|
74
|
+
for (const w of where) {
|
|
75
|
+
if (isKeyable(w) && !eqByField.has(w.field)) eqByField.set(w.field, w);
|
|
76
|
+
}
|
|
77
|
+
const idClause = eqByField.get("id");
|
|
78
|
+
if (idClause) {
|
|
79
|
+
return {
|
|
80
|
+
kind: "byId",
|
|
81
|
+
id: String(idClause.value),
|
|
82
|
+
residual: where.filter((w) => w !== idClause)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const patterns = indexMap[model] ?? [];
|
|
86
|
+
for (const pattern of patterns) {
|
|
87
|
+
if (!pattern.pk.every((f) => eqByField.has(f))) continue;
|
|
88
|
+
const key = {};
|
|
89
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
90
|
+
for (const f of pattern.pk) {
|
|
91
|
+
const clause = eqByField.get(f);
|
|
92
|
+
key[f] = clause.value;
|
|
93
|
+
consumed.add(clause);
|
|
94
|
+
}
|
|
95
|
+
for (const f of pattern.sk ?? []) {
|
|
96
|
+
const clause = eqByField.get(f);
|
|
97
|
+
if (!clause) break;
|
|
98
|
+
key[f] = clause.value;
|
|
99
|
+
consumed.add(clause);
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
kind: "index",
|
|
103
|
+
index: pattern.index,
|
|
104
|
+
key,
|
|
105
|
+
residual: where.filter((w) => !consumed.has(w))
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return { kind: "listByType", residual: where };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/stores/default/derive-index-map.ts
|
|
112
|
+
function lookupIndexName(field) {
|
|
113
|
+
return `by_${field}`;
|
|
114
|
+
}
|
|
115
|
+
function deriveIndexMap(schema) {
|
|
116
|
+
const map = {};
|
|
117
|
+
for (const [model, table] of Object.entries(schema)) {
|
|
118
|
+
const patterns = [];
|
|
119
|
+
const seen = /* @__PURE__ */ new Set();
|
|
120
|
+
const add = (field) => {
|
|
121
|
+
if (field === "id" || seen.has(field)) return;
|
|
122
|
+
seen.add(field);
|
|
123
|
+
patterns.push({ index: lookupIndexName(field), pk: [field] });
|
|
124
|
+
};
|
|
125
|
+
for (const [field, attr] of Object.entries(table.fields)) {
|
|
126
|
+
if (attr.unique) add(field);
|
|
127
|
+
}
|
|
128
|
+
for (const [field, attr] of Object.entries(table.fields)) {
|
|
129
|
+
if (attr.references) add(field);
|
|
130
|
+
}
|
|
131
|
+
for (const [field, attr] of Object.entries(table.fields)) {
|
|
132
|
+
if (attr.index) add(field);
|
|
133
|
+
}
|
|
134
|
+
if (patterns.length > 0) map[model] = patterns;
|
|
135
|
+
}
|
|
136
|
+
return map;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/stores/default/single-table-store.ts
|
|
140
|
+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
141
|
+
import {
|
|
142
|
+
DeleteCommand,
|
|
143
|
+
DynamoDBDocumentClient,
|
|
144
|
+
GetCommand,
|
|
145
|
+
PutCommand,
|
|
146
|
+
QueryCommand
|
|
147
|
+
} from "@aws-sdk/lib-dynamodb";
|
|
148
|
+
|
|
149
|
+
// src/stores/default/key-codec.ts
|
|
150
|
+
var PK = "__ba_pk";
|
|
151
|
+
var SK = "__ba_sk";
|
|
152
|
+
var TYPE_PK = "__ba_tpk";
|
|
153
|
+
var TYPE_SK = "__ba_tsk";
|
|
154
|
+
var TYPE_INDEX = "byType";
|
|
155
|
+
var SK_CONST = "#";
|
|
156
|
+
var RESERVED_PREFIX = "__ba_";
|
|
157
|
+
var gsiName = (slot) => `lookup${slot}`;
|
|
158
|
+
var gsiPk = (slot) => `__ba_g${slot}pk`;
|
|
159
|
+
var gsiSk = (slot) => `__ba_g${slot}sk`;
|
|
160
|
+
var enc = (v) => String(v);
|
|
161
|
+
var joinValues = (values) => values.map(enc).join("#");
|
|
162
|
+
function assignSlots(indexMap) {
|
|
163
|
+
const slots = {};
|
|
164
|
+
let maxSlots = 0;
|
|
165
|
+
for (const [model, patterns] of Object.entries(indexMap)) {
|
|
166
|
+
const modelSlots = {};
|
|
167
|
+
patterns.forEach((pattern, i) => {
|
|
168
|
+
modelSlots[pattern.index] = i + 1;
|
|
169
|
+
});
|
|
170
|
+
slots[model] = modelSlots;
|
|
171
|
+
maxSlots = Math.max(maxSlots, patterns.length);
|
|
172
|
+
}
|
|
173
|
+
return { slots, maxSlots };
|
|
174
|
+
}
|
|
175
|
+
var primaryKey = (model, id) => ({
|
|
176
|
+
[PK]: `${model}#${id}`,
|
|
177
|
+
[SK]: SK_CONST
|
|
178
|
+
});
|
|
179
|
+
var lookupPkValue = (model, index, values) => `${model}#${index}#${joinValues(values)}`;
|
|
180
|
+
function encodeKeys(model, item, indexMap, assignment) {
|
|
181
|
+
const id = String(item.id);
|
|
182
|
+
const keys = {
|
|
183
|
+
...primaryKey(model, id),
|
|
184
|
+
[TYPE_PK]: model,
|
|
185
|
+
[TYPE_SK]: `${enc(item.createdAt ?? "")}#${id}`
|
|
186
|
+
};
|
|
187
|
+
for (const pattern of indexMap[model] ?? []) {
|
|
188
|
+
const pkValues = pattern.pk.map((f) => item[f]);
|
|
189
|
+
if (pkValues.some((v) => v == null)) continue;
|
|
190
|
+
const slot = assignment.slots[model]?.[pattern.index];
|
|
191
|
+
if (!slot) continue;
|
|
192
|
+
keys[gsiPk(slot)] = lookupPkValue(model, pattern.index, pkValues);
|
|
193
|
+
const skValues = (pattern.sk ?? []).map((f) => item[f]);
|
|
194
|
+
keys[gsiSk(slot)] = skValues.some((v) => v == null) ? `${id}` : `${joinValues(skValues)}#${id}`;
|
|
195
|
+
}
|
|
196
|
+
return keys;
|
|
197
|
+
}
|
|
198
|
+
function encodeLookupQuery(model, index, key, indexMap, assignment) {
|
|
199
|
+
const slot = assignment.slots[model]?.[index];
|
|
200
|
+
if (!slot) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`No physical slot for index "${index}" on model "${model}". Ensure the index is present in the adapter's index map.`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const pattern = (indexMap[model] ?? []).find((p) => p.index === index);
|
|
206
|
+
const pkValues = pattern.pk.map((f) => key[f]);
|
|
207
|
+
const skPresent = (pattern.sk ?? []).map((f) => key[f]).filter((v) => v != null);
|
|
208
|
+
return {
|
|
209
|
+
indexName: gsiName(slot),
|
|
210
|
+
pkAttr: gsiPk(slot),
|
|
211
|
+
pkValue: lookupPkValue(model, index, pkValues),
|
|
212
|
+
skAttr: gsiSk(slot),
|
|
213
|
+
skPrefix: skPresent.length > 0 ? `${joinValues(skPresent)}#` : void 0
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function stripReserved(item) {
|
|
217
|
+
if (!item) return null;
|
|
218
|
+
const clean = {};
|
|
219
|
+
for (const [k, v] of Object.entries(item)) {
|
|
220
|
+
if (!k.startsWith(RESERVED_PREFIX)) clean[k] = v;
|
|
221
|
+
}
|
|
222
|
+
return clean;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/stores/default/schema.ts
|
|
226
|
+
import {
|
|
227
|
+
CreateTableCommand,
|
|
228
|
+
ResourceInUseException,
|
|
229
|
+
waitUntilTableExists
|
|
230
|
+
} from "@aws-sdk/client-dynamodb";
|
|
231
|
+
function buildTableDefinition(tableName, lookupSlots) {
|
|
232
|
+
const attr = (name) => ({
|
|
233
|
+
AttributeName: name,
|
|
234
|
+
AttributeType: "S"
|
|
235
|
+
});
|
|
236
|
+
const attributeDefinitions = [
|
|
237
|
+
attr(PK),
|
|
238
|
+
attr(SK),
|
|
239
|
+
attr(TYPE_PK),
|
|
240
|
+
attr(TYPE_SK)
|
|
241
|
+
];
|
|
242
|
+
const globalSecondaryIndexes = [
|
|
243
|
+
{
|
|
244
|
+
IndexName: TYPE_INDEX,
|
|
245
|
+
KeySchema: [
|
|
246
|
+
{ AttributeName: TYPE_PK, KeyType: "HASH" },
|
|
247
|
+
{ AttributeName: TYPE_SK, KeyType: "RANGE" }
|
|
248
|
+
],
|
|
249
|
+
Projection: { ProjectionType: "ALL" }
|
|
250
|
+
}
|
|
251
|
+
];
|
|
252
|
+
for (let slot = 1; slot <= lookupSlots; slot++) {
|
|
253
|
+
attributeDefinitions.push(attr(gsiPk(slot)), attr(gsiSk(slot)));
|
|
254
|
+
globalSecondaryIndexes.push({
|
|
255
|
+
IndexName: gsiName(slot),
|
|
256
|
+
KeySchema: [
|
|
257
|
+
{ AttributeName: gsiPk(slot), KeyType: "HASH" },
|
|
258
|
+
{ AttributeName: gsiSk(slot), KeyType: "RANGE" }
|
|
259
|
+
],
|
|
260
|
+
Projection: { ProjectionType: "ALL" }
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
TableName: tableName,
|
|
265
|
+
BillingMode: "PAY_PER_REQUEST",
|
|
266
|
+
KeySchema: [
|
|
267
|
+
{ AttributeName: PK, KeyType: "HASH" },
|
|
268
|
+
{ AttributeName: SK, KeyType: "RANGE" }
|
|
269
|
+
],
|
|
270
|
+
AttributeDefinitions: attributeDefinitions,
|
|
271
|
+
GlobalSecondaryIndexes: globalSecondaryIndexes
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
async function ensureSchema(opts) {
|
|
275
|
+
const input = buildTableDefinition(opts.tableName, opts.lookupSlots);
|
|
276
|
+
try {
|
|
277
|
+
await opts.client.send(new CreateTableCommand(input));
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (!(error instanceof ResourceInUseException)) throw error;
|
|
280
|
+
}
|
|
281
|
+
await waitUntilTableExists(
|
|
282
|
+
{ client: opts.client, maxWaitTime: 60 },
|
|
283
|
+
{ TableName: opts.tableName }
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
function generateSchemaFile(opts) {
|
|
287
|
+
const table = buildTableDefinition(opts.tableName, opts.lookupSlots);
|
|
288
|
+
const template = {
|
|
289
|
+
AWSTemplateFormatVersion: "2010-09-09",
|
|
290
|
+
Resources: {
|
|
291
|
+
BetterAuthTable: { Type: "AWS::DynamoDB::Table", Properties: table }
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
const code = `/**
|
|
295
|
+
* Auto-generated DynamoDB CloudFormation template for @datar-platform/better-auth-dynamodb.
|
|
296
|
+
* Table: ${opts.tableName} \u2014 ${opts.lookupSlots} lookup GSI(s) + 1 byType GSI.
|
|
297
|
+
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html
|
|
6
298
|
*/
|
|
7
|
-
export {
|
|
8
|
-
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
299
|
+
export const dynamoDBSchema = ${JSON.stringify(template, null, 2)} as const;
|
|
300
|
+
|
|
301
|
+
export default dynamoDBSchema;
|
|
302
|
+
`;
|
|
303
|
+
return {
|
|
304
|
+
code,
|
|
305
|
+
path: opts.file ?? "dynamodb-cloudformation.ts",
|
|
306
|
+
overwrite: true
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/stores/default/single-table-store.ts
|
|
311
|
+
function createSingleTableStore(opts) {
|
|
312
|
+
const tableName = opts.tableName ?? process.env.DYNAMODB_TABLE_NAME ?? "better-auth";
|
|
313
|
+
const doc = opts.documentClient ?? DynamoDBDocumentClient.from(
|
|
314
|
+
new DynamoDBClient({
|
|
315
|
+
...opts.region ? { region: opts.region } : {},
|
|
316
|
+
...opts.endpoint ? { endpoint: opts.endpoint } : {}
|
|
317
|
+
}),
|
|
318
|
+
{ marshallOptions: { removeUndefinedValues: true } }
|
|
319
|
+
);
|
|
320
|
+
const indexMap = opts.indexMap;
|
|
321
|
+
const assignment = assignSlots(indexMap);
|
|
322
|
+
const query = async (input, cursor, countOnly = false) => {
|
|
323
|
+
const res = await doc.send(
|
|
324
|
+
new QueryCommand({
|
|
325
|
+
TableName: tableName,
|
|
326
|
+
ExclusiveStartKey: cursor,
|
|
327
|
+
...countOnly ? { Select: "COUNT" } : {},
|
|
328
|
+
...input
|
|
329
|
+
})
|
|
330
|
+
);
|
|
331
|
+
return {
|
|
332
|
+
items: countOnly ? [] : (res.Items ?? []).map((i) => stripReserved(i)),
|
|
333
|
+
count: res.Count ?? 0,
|
|
334
|
+
cursor: res.LastEvaluatedKey
|
|
335
|
+
};
|
|
336
|
+
};
|
|
337
|
+
return {
|
|
338
|
+
async put(model, item) {
|
|
339
|
+
await doc.send(
|
|
340
|
+
new PutCommand({
|
|
341
|
+
TableName: tableName,
|
|
342
|
+
Item: { ...item, ...encodeKeys(model, item, indexMap, assignment) }
|
|
343
|
+
})
|
|
344
|
+
);
|
|
345
|
+
return item;
|
|
346
|
+
},
|
|
347
|
+
async getById(model, id) {
|
|
348
|
+
const res = await doc.send(
|
|
349
|
+
new GetCommand({ TableName: tableName, Key: primaryKey(model, id) })
|
|
350
|
+
);
|
|
351
|
+
return stripReserved(res.Item);
|
|
352
|
+
},
|
|
353
|
+
async update(model, id, patch) {
|
|
354
|
+
const res = await doc.send(
|
|
355
|
+
new GetCommand({ TableName: tableName, Key: primaryKey(model, id) })
|
|
356
|
+
);
|
|
357
|
+
const existing = stripReserved(res.Item);
|
|
358
|
+
if (!existing) return null;
|
|
359
|
+
const merged = { ...existing, ...patch };
|
|
360
|
+
await doc.send(
|
|
361
|
+
new PutCommand({
|
|
362
|
+
TableName: tableName,
|
|
363
|
+
Item: {
|
|
364
|
+
...merged,
|
|
365
|
+
...encodeKeys(model, merged, indexMap, assignment)
|
|
366
|
+
}
|
|
367
|
+
})
|
|
368
|
+
);
|
|
369
|
+
return merged;
|
|
370
|
+
},
|
|
371
|
+
async deleteById(model, id) {
|
|
372
|
+
await doc.send(
|
|
373
|
+
new DeleteCommand({ TableName: tableName, Key: primaryKey(model, id) })
|
|
374
|
+
);
|
|
375
|
+
},
|
|
376
|
+
async queryIndex({ model, index, key, cursor }) {
|
|
377
|
+
const lookup = encodeLookupQuery(model, index, key, indexMap, assignment);
|
|
378
|
+
const names = { "#pk": lookup.pkAttr };
|
|
379
|
+
const values = { ":pk": lookup.pkValue };
|
|
380
|
+
let condition = "#pk = :pk";
|
|
381
|
+
if (lookup.skPrefix) {
|
|
382
|
+
names["#sk"] = lookup.skAttr;
|
|
383
|
+
values[":skp"] = lookup.skPrefix;
|
|
384
|
+
condition += " AND begins_with(#sk, :skp)";
|
|
385
|
+
}
|
|
386
|
+
const { items, cursor: next } = await query(
|
|
387
|
+
{
|
|
388
|
+
IndexName: lookup.indexName,
|
|
389
|
+
KeyConditionExpression: condition,
|
|
390
|
+
ExpressionAttributeNames: names,
|
|
391
|
+
ExpressionAttributeValues: values
|
|
392
|
+
},
|
|
393
|
+
cursor
|
|
394
|
+
);
|
|
395
|
+
return { items, cursor: next };
|
|
396
|
+
},
|
|
397
|
+
async listByType({ model, cursor }) {
|
|
398
|
+
const { items, cursor: next } = await query(
|
|
399
|
+
{
|
|
400
|
+
IndexName: TYPE_INDEX,
|
|
401
|
+
KeyConditionExpression: "#tpk = :tpk",
|
|
402
|
+
ExpressionAttributeNames: { "#tpk": TYPE_PK },
|
|
403
|
+
ExpressionAttributeValues: { ":tpk": model }
|
|
404
|
+
},
|
|
405
|
+
cursor
|
|
406
|
+
);
|
|
407
|
+
return { items, cursor: next };
|
|
408
|
+
},
|
|
409
|
+
async count({ model, index, key }) {
|
|
410
|
+
let base;
|
|
411
|
+
if (index && key) {
|
|
412
|
+
const lookup = encodeLookupQuery(
|
|
413
|
+
model,
|
|
414
|
+
index,
|
|
415
|
+
key,
|
|
416
|
+
indexMap,
|
|
417
|
+
assignment
|
|
418
|
+
);
|
|
419
|
+
base = {
|
|
420
|
+
IndexName: lookup.indexName,
|
|
421
|
+
KeyConditionExpression: "#pk = :pk",
|
|
422
|
+
ExpressionAttributeNames: { "#pk": lookup.pkAttr },
|
|
423
|
+
ExpressionAttributeValues: { ":pk": lookup.pkValue }
|
|
424
|
+
};
|
|
425
|
+
} else {
|
|
426
|
+
base = {
|
|
427
|
+
IndexName: TYPE_INDEX,
|
|
428
|
+
KeyConditionExpression: "#tpk = :tpk",
|
|
429
|
+
ExpressionAttributeNames: { "#tpk": TYPE_PK },
|
|
430
|
+
ExpressionAttributeValues: { ":tpk": model }
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
let total = 0;
|
|
434
|
+
let cursor = void 0;
|
|
435
|
+
do {
|
|
436
|
+
const page = await query(base, cursor, true);
|
|
437
|
+
total += page.count;
|
|
438
|
+
cursor = page.cursor;
|
|
439
|
+
} while (cursor);
|
|
440
|
+
return total;
|
|
441
|
+
},
|
|
442
|
+
createSchema: ({ file }) => Promise.resolve(
|
|
443
|
+
generateSchemaFile({
|
|
444
|
+
tableName,
|
|
445
|
+
lookupSlots: assignment.maxSlots,
|
|
446
|
+
file
|
|
447
|
+
})
|
|
448
|
+
)
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/adapter.ts
|
|
453
|
+
var dynamoAdapter = (config = {}) => {
|
|
454
|
+
return createAdapterFactory({
|
|
455
|
+
config: {
|
|
456
|
+
adapterId: "dynamodb",
|
|
457
|
+
adapterName: "DynamoDB",
|
|
458
|
+
// DynamoDB keys are strings; let Better Auth generate string ids.
|
|
459
|
+
supportsNumericIds: false,
|
|
460
|
+
// We serialize JSON ourselves via the SDK document client.
|
|
461
|
+
supportsJSON: true,
|
|
462
|
+
// No native Date type — stored as ISO strings (see transforms below).
|
|
463
|
+
supportsDates: false,
|
|
464
|
+
supportsBooleans: true,
|
|
465
|
+
usePlural: false,
|
|
466
|
+
debugLogs: config.debugLogs,
|
|
467
|
+
// Dates <-> ISO strings, since supportsDates is false.
|
|
468
|
+
customTransformInput: ({ data, fieldAttributes }) => fieldAttributes.type === "date" && data instanceof Date ? data.toISOString() : data,
|
|
469
|
+
customTransformOutput: ({ data, fieldAttributes }) => fieldAttributes.type === "date" && typeof data === "string" ? new Date(data) : data
|
|
470
|
+
},
|
|
471
|
+
adapter: ({ schema, debugLog, getDefaultModelName }) => {
|
|
472
|
+
const indexMap = config.indexMap ?? deriveIndexMap(schema);
|
|
473
|
+
const store = config.store ?? createSingleTableStore({
|
|
474
|
+
tableName: config.tableName,
|
|
475
|
+
region: config.region,
|
|
476
|
+
endpoint: config.endpoint,
|
|
477
|
+
indexMap
|
|
478
|
+
});
|
|
479
|
+
const queryAll = async (model, where) => {
|
|
480
|
+
const clauses = where ?? [];
|
|
481
|
+
const plan = planQuery(model, clauses, indexMap);
|
|
482
|
+
debugLog("queryAll", { model, plan: plan.kind });
|
|
483
|
+
let candidates;
|
|
484
|
+
if (plan.kind === "byId") {
|
|
485
|
+
const one = await store.getById(model, plan.id);
|
|
486
|
+
candidates = one ? [one] : [];
|
|
487
|
+
} else if (plan.kind === "index") {
|
|
488
|
+
candidates = await drainPages(
|
|
489
|
+
(cursor) => store.queryIndex({
|
|
490
|
+
model,
|
|
491
|
+
index: plan.index,
|
|
492
|
+
key: plan.key,
|
|
493
|
+
cursor
|
|
494
|
+
})
|
|
495
|
+
);
|
|
496
|
+
} else {
|
|
497
|
+
candidates = await drainPages(
|
|
498
|
+
(cursor) => store.listByType({ model, cursor })
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
return candidates.filter(
|
|
502
|
+
(item) => matchesResidual(item, plan.residual)
|
|
503
|
+
);
|
|
504
|
+
};
|
|
505
|
+
const resolveId = async (model, where) => {
|
|
506
|
+
const plan = planQuery(model, where, indexMap);
|
|
507
|
+
if (plan.kind === "byId") return plan.id;
|
|
508
|
+
const [first] = await queryAll(model, where);
|
|
509
|
+
return first ? String(first.id) : null;
|
|
510
|
+
};
|
|
511
|
+
return {
|
|
512
|
+
async create({
|
|
513
|
+
model,
|
|
514
|
+
data
|
|
515
|
+
}) {
|
|
516
|
+
const m = getDefaultModelName(model);
|
|
517
|
+
const created = await store.put(m, data);
|
|
518
|
+
return created;
|
|
519
|
+
},
|
|
520
|
+
async update({
|
|
521
|
+
model,
|
|
522
|
+
where,
|
|
523
|
+
update
|
|
524
|
+
}) {
|
|
525
|
+
const m = getDefaultModelName(model);
|
|
526
|
+
const id = await resolveId(m, where);
|
|
527
|
+
if (!id) return null;
|
|
528
|
+
const updated = await store.update(m, id, update);
|
|
529
|
+
return updated;
|
|
530
|
+
},
|
|
531
|
+
async updateMany({
|
|
532
|
+
model,
|
|
533
|
+
where,
|
|
534
|
+
update
|
|
535
|
+
}) {
|
|
536
|
+
const m = getDefaultModelName(model);
|
|
537
|
+
const items = await queryAll(m, where);
|
|
538
|
+
await runBatched(
|
|
539
|
+
items,
|
|
540
|
+
(item) => store.update(m, String(item.id), update)
|
|
541
|
+
);
|
|
542
|
+
return items.length;
|
|
543
|
+
},
|
|
544
|
+
async delete({
|
|
545
|
+
model,
|
|
546
|
+
where
|
|
547
|
+
}) {
|
|
548
|
+
const m = getDefaultModelName(model);
|
|
549
|
+
const id = await resolveId(m, where);
|
|
550
|
+
if (id) await store.deleteById(m, id);
|
|
551
|
+
},
|
|
552
|
+
async deleteMany({
|
|
553
|
+
model,
|
|
554
|
+
where
|
|
555
|
+
}) {
|
|
556
|
+
const m = getDefaultModelName(model);
|
|
557
|
+
const items = await queryAll(m, where);
|
|
558
|
+
await runBatched(
|
|
559
|
+
items,
|
|
560
|
+
(item) => store.deleteById(m, String(item.id))
|
|
561
|
+
);
|
|
562
|
+
return items.length;
|
|
563
|
+
},
|
|
564
|
+
async findOne({
|
|
565
|
+
model,
|
|
566
|
+
where
|
|
567
|
+
}) {
|
|
568
|
+
const m = getDefaultModelName(model);
|
|
569
|
+
const [first] = await queryAll(m, where);
|
|
570
|
+
return first ?? null;
|
|
571
|
+
},
|
|
572
|
+
async findMany({
|
|
573
|
+
model,
|
|
574
|
+
where,
|
|
575
|
+
limit,
|
|
576
|
+
sortBy,
|
|
577
|
+
offset
|
|
578
|
+
}) {
|
|
579
|
+
const m = getDefaultModelName(model);
|
|
580
|
+
const items = applyWindow(
|
|
581
|
+
applySort(await queryAll(m, where), sortBy),
|
|
582
|
+
offset,
|
|
583
|
+
limit
|
|
584
|
+
);
|
|
585
|
+
return items;
|
|
586
|
+
},
|
|
587
|
+
async count({
|
|
588
|
+
model,
|
|
589
|
+
where
|
|
590
|
+
}) {
|
|
591
|
+
const m = getDefaultModelName(model);
|
|
592
|
+
const clauses = where ?? [];
|
|
593
|
+
const plan = planQuery(m, clauses, indexMap);
|
|
594
|
+
if (plan.residual.length === 0 && store.count) {
|
|
595
|
+
const fast = await store.count(
|
|
596
|
+
plan.kind === "index" ? { model: m, index: plan.index, key: plan.key } : { model: m }
|
|
597
|
+
);
|
|
598
|
+
if (fast != null) return fast;
|
|
599
|
+
}
|
|
600
|
+
return (await queryAll(m, clauses)).length;
|
|
601
|
+
},
|
|
602
|
+
...store.createSchema ? {
|
|
603
|
+
createSchema: (props) => store.createSchema(props)
|
|
604
|
+
} : {}
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
};
|
|
609
|
+
async function runBatched(items, op, chunkSize = 10) {
|
|
610
|
+
for (let i = 0; i < items.length; i += chunkSize) {
|
|
611
|
+
await Promise.all(items.slice(i, i + chunkSize).map(op));
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
export {
|
|
615
|
+
assignSlots,
|
|
616
|
+
buildTableDefinition,
|
|
617
|
+
createSingleTableStore,
|
|
618
|
+
deriveIndexMap,
|
|
619
|
+
dynamoAdapter,
|
|
620
|
+
ensureSchema,
|
|
621
|
+
generateSchemaFile,
|
|
622
|
+
matchesResidual,
|
|
623
|
+
planQuery
|
|
624
|
+
};
|
|
15
625
|
//# sourceMappingURL=index.js.map
|