@datar-platform/better-auth-dynamodb 0.2.0 → 0.2.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/CHANGELOG.md +58 -0
- package/README.md +67 -10
- package/dist/index.d.ts +72 -16
- package/dist/index.js +170 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# @datar-platform/better-auth-dynamodb
|
|
2
2
|
|
|
3
|
+
## 0.2.2
|
|
4
|
+
|
|
5
|
+
Documentation only — no runtime change.
|
|
6
|
+
|
|
7
|
+
- The uniqueness section still said markers "do not find duplicates already
|
|
8
|
+
there", which 0.2.1's `migrateKeys()` made untrue, and which contradicted the
|
|
9
|
+
upgrade section a few paragraphs above it.
|
|
10
|
+
- The install step now states the `better-auth >= 1.7` and Node >= 22
|
|
11
|
+
requirements, rather than leaving them to be discovered as a peer error.
|
|
12
|
+
- The opening claim that any model or plugin "just works" now says what the
|
|
13
|
+
no-hidden-scan default actually does, instead of implying every query is
|
|
14
|
+
served.
|
|
15
|
+
- Documents the exported error types, so a caller can tell "this email is taken"
|
|
16
|
+
from a failed write by catching `UniqueConstraintError`.
|
|
17
|
+
- The query-planning description covers the `id` and `id in [...]` paths added
|
|
18
|
+
in 0.2.0, not only index selection.
|
|
19
|
+
- The bring-your-own-store sketch calls out `consumeOne`/`incrementOne` — they
|
|
20
|
+
are optional, but they are what makes single-use codes and counters atomic.
|
|
21
|
+
|
|
22
|
+
## 0.2.1
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **`migrateKeys()` — an upgrade path from 0.1.x that does not mean recreating
|
|
27
|
+
the table.** 0.2.0 changed the physical key format, so rows written by 0.1.x
|
|
28
|
+
survive but stop being found. For a development table, recreating it is fine;
|
|
29
|
+
for one holding real users it means an outage. This rewrites the rows in
|
|
30
|
+
place instead, and backfills the uniqueness markers 0.1.x never wrote — so
|
|
31
|
+
existing rows end up protected, not merely readable.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import {
|
|
35
|
+
deriveIndexMap,
|
|
36
|
+
migrateKeys,
|
|
37
|
+
} from "@datar-platform/better-auth-dynamodb";
|
|
38
|
+
import { getAuthTables } from "better-auth/db";
|
|
39
|
+
|
|
40
|
+
const indexMap = deriveIndexMap(getAuthTables(betterAuthOptions));
|
|
41
|
+
|
|
42
|
+
// Look first. A dry run writes nothing and names any duplicate that would
|
|
43
|
+
// block the migration.
|
|
44
|
+
console.log(await migrateKeys({ tableName, indexMap, client, dryRun: true }));
|
|
45
|
+
|
|
46
|
+
await migrateKeys({ tableName, indexMap, client });
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- **Explicit, never automatic.** Upgrading the package changes nothing on its
|
|
50
|
+
own; rewriting an auth table on first boot is not a decision a library
|
|
51
|
+
should make for you.
|
|
52
|
+
- **A no-op when there is nothing to do**, so it is safe in a deploy step and
|
|
53
|
+
safe to run twice. A table created on 0.2.0+ is left alone.
|
|
54
|
+
- **Stops before writing if two old rows claim the same `unique` value.**
|
|
55
|
+
0.1.x enforced uniqueness in Better Auth's application layer, which two
|
|
56
|
+
concurrent sign-ups could both pass; choosing which row keeps the email is
|
|
57
|
+
not a migration's decision. The report names the conflicting ids.
|
|
58
|
+
- Writes each new row before deleting the old one, so an interrupted run
|
|
59
|
+
leaves both rather than neither.
|
|
60
|
+
|
|
3
61
|
## 0.2.0
|
|
4
62
|
|
|
5
63
|
### Breaking
|
package/README.md
CHANGED
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
A generic [DynamoDB](https://aws.amazon.com/dynamodb/) adapter for [Better Auth](https://better-auth.com).
|
|
4
4
|
|
|
5
5
|
- **Works out of the box** with any Better Auth model or plugin — the built-in
|
|
6
|
-
single-table store derives its indexes from your schema (`unique` fields
|
|
7
|
-
foreign keys), so two-factor, passkey,
|
|
6
|
+
single-table store derives its indexes from your schema (`unique` fields,
|
|
7
|
+
foreign keys, and anything marked `index: true`), so two-factor, passkey,
|
|
8
|
+
API-key, organization, etc. work with no per-model configuration. Queries no
|
|
9
|
+
index can serve are refused rather than silently scanned, so an access pattern
|
|
10
|
+
nobody designed for shows up in development (see `unsafeAllowScan`).
|
|
8
11
|
- **Bring your own store.** The adapter talks to a small `DynamoStore` seam, so
|
|
9
12
|
you can back it with an existing single-table design (ElectroDB, custom key
|
|
10
13
|
encoding, a shared table) without changing the adapter.
|
|
@@ -26,6 +29,9 @@ A generic [DynamoDB](https://aws.amazon.com/dynamodb/) adapter for [Better Auth]
|
|
|
26
29
|
npm install @datar-platform/better-auth-dynamodb @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
|
|
27
30
|
```
|
|
28
31
|
|
|
32
|
+
Requires `better-auth` 1.7 or newer, and Node 22 or newer. On `better-auth`
|
|
33
|
+
1.5/1.6, stay on 0.1.x.
|
|
34
|
+
|
|
29
35
|
## Quick start (built-in single-table store)
|
|
30
36
|
|
|
31
37
|
```ts
|
|
@@ -85,6 +91,40 @@ it. Reads treat it as _logical_ expiry too: DynamoDB reaps lazily, often a day
|
|
|
85
91
|
or two late, so without that an expired session would keep working until AWS got
|
|
86
92
|
round to deleting it.
|
|
87
93
|
|
|
94
|
+
### Upgrading from 0.1.x
|
|
95
|
+
|
|
96
|
+
0.2.0 changed the physical key format, so rows written by 0.1.x survive but are
|
|
97
|
+
no longer found by any lookup. Recreating the table is fine in development; if
|
|
98
|
+
it holds real users, migrate it instead:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import {
|
|
102
|
+
deriveIndexMap,
|
|
103
|
+
migrateKeys,
|
|
104
|
+
} from "@datar-platform/better-auth-dynamodb";
|
|
105
|
+
import { getAuthTables } from "better-auth/db";
|
|
106
|
+
|
|
107
|
+
const indexMap = deriveIndexMap(getAuthTables(betterAuthOptions));
|
|
108
|
+
|
|
109
|
+
// Look before you leap: a dry run writes nothing, and names any duplicate that
|
|
110
|
+
// would block the migration.
|
|
111
|
+
console.log(await migrateKeys({ tableName, indexMap, client, dryRun: true }));
|
|
112
|
+
|
|
113
|
+
await migrateKeys({ tableName, indexMap, client });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
It rewrites each row into the current format and backfills the uniqueness
|
|
117
|
+
markers 0.1.x never wrote, so existing rows end up protected rather than merely
|
|
118
|
+
readable. Nothing happens automatically on upgrade — rewriting an auth table on
|
|
119
|
+
first boot is not a decision this package makes for you — and running it on a
|
|
120
|
+
table with nothing to migrate is a no-op, so it is safe in a deploy step and
|
|
121
|
+
safe to run twice.
|
|
122
|
+
|
|
123
|
+
If two old rows claim the same `unique` value, it stops before writing anything
|
|
124
|
+
and reports their ids. 0.1.x enforced uniqueness in Better Auth's application
|
|
125
|
+
layer, which two concurrent sign-ups could both pass; deciding which row keeps
|
|
126
|
+
the email is yours to make, not the migration's.
|
|
127
|
+
|
|
88
128
|
### Uniqueness
|
|
89
129
|
|
|
90
130
|
Fields the Better Auth schema marks `unique` — `user.email`, `session.token`,
|
|
@@ -97,13 +137,25 @@ Better Auth's own enforcement is a check-then-insert, which both racers can
|
|
|
97
137
|
pass. Set `atomicUniqueness: false` to fall back to it and halve the write cost
|
|
98
138
|
of creates.
|
|
99
139
|
|
|
100
|
-
Two
|
|
140
|
+
Two things worth knowing:
|
|
101
141
|
|
|
102
|
-
-
|
|
103
|
-
|
|
142
|
+
- On a table upgraded from 0.1.x, markers only cover rows written since the
|
|
143
|
+
upgrade until you run [`migrateKeys()`](#upgrading-from-01x), which backfills
|
|
144
|
+
them for the rows already there.
|
|
104
145
|
- Do not write Better Auth rows into the table with a raw `PutItem`. Entity
|
|
105
146
|
rows, index keys, markers, and TTL attributes have to move together.
|
|
106
147
|
|
|
148
|
+
A write that loses a uniqueness race throws `UniqueConstraintError`, so you can
|
|
149
|
+
tell "this email is taken" apart from a failed write:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { UniqueConstraintError } from "@datar-platform/better-auth-dynamodb";
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`DynamoDBAdapterError` is the base of everything this package throws;
|
|
156
|
+
`UnsupportedQueryError` and `OptimisticLockError` are the other two you might
|
|
157
|
+
catch by name.
|
|
158
|
+
|
|
107
159
|
## Bring your own store
|
|
108
160
|
|
|
109
161
|
Implement `DynamoStore` to run the same adapter against your own table layout.
|
|
@@ -137,7 +189,10 @@ const store: DynamoStore = {
|
|
|
137
189
|
listByType({ model }) {
|
|
138
190
|
/* list all rows of a model */
|
|
139
191
|
},
|
|
140
|
-
//
|
|
192
|
+
// Optional, and worth implementing: `consumeOne` (atomic delete-and-return,
|
|
193
|
+
// which is what stops a one-time code being used twice) and `incrementOne`
|
|
194
|
+
// (atomic counter). Without them the adapter falls back to a non-atomic
|
|
195
|
+
// get-then-write. Also optional: `count()`, `createSchema()`.
|
|
141
196
|
};
|
|
142
197
|
|
|
143
198
|
// Describe which fields each model is looked up by:
|
|
@@ -153,10 +208,12 @@ const indexMap: IndexMap = {
|
|
|
153
208
|
betterAuth({ database: dynamoAdapter({ store, indexMap }) });
|
|
154
209
|
```
|
|
155
210
|
|
|
156
|
-
Given a `where`, the adapter
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
211
|
+
Given a `where`, the adapter takes the cheapest path it can prove: an `id`
|
|
212
|
+
equality becomes a direct get, an `id in [...]` becomes a bounded set of gets,
|
|
213
|
+
and otherwise it picks the first access pattern whose partition-key fields are
|
|
214
|
+
all present (attaching any leading sort-key fields). Whatever the chosen path
|
|
215
|
+
does not cover is filtered in memory. Provide an `indexMap` to match your table,
|
|
216
|
+
or omit it to auto-derive from the schema.
|
|
160
217
|
|
|
161
218
|
## Configuration
|
|
162
219
|
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { AdapterFactory } from 'better-auth/adapters';
|
|
|
3
3
|
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
4
4
|
import { DBAdapterDebugLogOption, CleanedWhere } from '@better-auth/core/db/adapter';
|
|
5
5
|
import { BetterAuthDBSchema } from '@better-auth/core/db';
|
|
6
|
-
import {
|
|
6
|
+
import { DynamoDBClient, CreateTableCommandInput } from '@aws-sdk/client-dynamodb';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Declarative description of a DynamoDB access pattern (one logical index).
|
|
@@ -389,6 +389,76 @@ declare function createSingleTableStore(opts: SingleTableStoreOptions): DynamoSt
|
|
|
389
389
|
*/
|
|
390
390
|
declare function deriveIndexMap(schema: BetterAuthDBSchema): IndexMap;
|
|
391
391
|
|
|
392
|
+
/** Default DynamoDB TTL attribute (epoch seconds). Configurable per store. */
|
|
393
|
+
declare const DEFAULT_TTL_ATTRIBUTE = "__ba_ttl";
|
|
394
|
+
/** Physical slot assignment: model -> (logical index name -> GSI slot number). */
|
|
395
|
+
interface SlotAssignment {
|
|
396
|
+
slots: Record<string, Record<string, number>>;
|
|
397
|
+
maxSlots: number;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Assign each logical index to a physical GSI slot. Because indexes on
|
|
401
|
+
* different models never coexist on one item, a model's Nth index always maps
|
|
402
|
+
* to slot N — so the table needs only `max(indexes per model)` lookup GSIs.
|
|
403
|
+
*/
|
|
404
|
+
declare function assignSlots(indexMap: IndexMap): SlotAssignment;
|
|
405
|
+
|
|
406
|
+
interface MigrateKeysOptions {
|
|
407
|
+
/** Table to migrate. */
|
|
408
|
+
tableName: string;
|
|
409
|
+
/**
|
|
410
|
+
* The same access-pattern map the adapter runs with — normally
|
|
411
|
+
* `deriveIndexMap(getAuthTables(betterAuthOptions))`. Keys and markers are
|
|
412
|
+
* rebuilt from it, so a mismatch here produces a table the adapter cannot
|
|
413
|
+
* read.
|
|
414
|
+
*/
|
|
415
|
+
indexMap: IndexMap;
|
|
416
|
+
/** Pre-built document client. Preferred, for the usual credential reasons. */
|
|
417
|
+
documentClient?: DynamoDBDocumentClient;
|
|
418
|
+
/** Raw client, wrapped internally. Ignored when `documentClient` is given. */
|
|
419
|
+
client?: DynamoDBClient;
|
|
420
|
+
/**
|
|
421
|
+
* Report what would change without writing anything. Run this first — the
|
|
422
|
+
* report names any duplicate that would block the migration.
|
|
423
|
+
*/
|
|
424
|
+
dryRun?: boolean;
|
|
425
|
+
/** Backfill uniqueness markers. Default `true`. */
|
|
426
|
+
atomicUniqueness?: boolean;
|
|
427
|
+
/** Same TTL configuration the adapter uses, so migrated rows carry it too. */
|
|
428
|
+
ttl?: TtlOptions | false;
|
|
429
|
+
/** Called after each page, for progress on a large table. */
|
|
430
|
+
onProgress?: (progress: {
|
|
431
|
+
scanned: number;
|
|
432
|
+
migrated: number;
|
|
433
|
+
}) => void;
|
|
434
|
+
}
|
|
435
|
+
/** A value that more than one row claims, so a unique marker cannot cover it. */
|
|
436
|
+
interface UniquenessConflict {
|
|
437
|
+
model: string;
|
|
438
|
+
index: string;
|
|
439
|
+
/** Ids of every row holding the duplicated value. */
|
|
440
|
+
ids: string[];
|
|
441
|
+
}
|
|
442
|
+
interface MigrationReport {
|
|
443
|
+
/** Rows examined. */
|
|
444
|
+
scanned: number;
|
|
445
|
+
/** Rows rewritten into the current key format (or that would be, on a dry run). */
|
|
446
|
+
migrated: number;
|
|
447
|
+
/** Rows already in the current format, left untouched. */
|
|
448
|
+
alreadyCurrent: number;
|
|
449
|
+
/** Uniqueness marker rows created. */
|
|
450
|
+
markersCreated: number;
|
|
451
|
+
/**
|
|
452
|
+
* Duplicates found. Non-empty means the migration did not run: 0.1.x could
|
|
453
|
+
* admit two rows with the same `unique` value, and there is no correct way
|
|
454
|
+
* for a migration to choose which one keeps it.
|
|
455
|
+
*/
|
|
456
|
+
conflicts: UniquenessConflict[];
|
|
457
|
+
/** True when nothing needed doing. */
|
|
458
|
+
noop: boolean;
|
|
459
|
+
}
|
|
460
|
+
declare function migrateKeys(opts: MigrateKeysOptions): Promise<MigrationReport>;
|
|
461
|
+
|
|
392
462
|
/** Build the CreateTable input for the built-in store's single-table layout. */
|
|
393
463
|
declare function buildTableDefinition(tableName: string, lookupSlots: number): CreateTableCommandInput;
|
|
394
464
|
/**
|
|
@@ -424,18 +494,4 @@ declare function generateSchemaFile(opts: {
|
|
|
424
494
|
overwrite: boolean;
|
|
425
495
|
};
|
|
426
496
|
|
|
427
|
-
|
|
428
|
-
declare const DEFAULT_TTL_ATTRIBUTE = "__ba_ttl";
|
|
429
|
-
/** Physical slot assignment: model -> (logical index name -> GSI slot number). */
|
|
430
|
-
interface SlotAssignment {
|
|
431
|
-
slots: Record<string, Record<string, number>>;
|
|
432
|
-
maxSlots: number;
|
|
433
|
-
}
|
|
434
|
-
/**
|
|
435
|
-
* Assign each logical index to a physical GSI slot. Because indexes on
|
|
436
|
-
* different models never coexist on one item, a model's Nth index always maps
|
|
437
|
-
* to slot N — so the table needs only `max(indexes per model)` lookup GSIs.
|
|
438
|
-
*/
|
|
439
|
-
declare function assignSlots(indexMap: IndexMap): SlotAssignment;
|
|
440
|
-
|
|
441
|
-
export { type AccessPattern, DEFAULT_TTL_ATTRIBUTE, type DynamoAdapterConfig, DynamoDBAdapterError, type DynamoStore, type IndexMap, type ModelIndexMap, OptimisticLockError, type QueryPage, type QueryPlan, type SingleTableStoreOptions, type StoreItem, type TtlOptions, UniqueConstraintError, UnsupportedQueryError, assignSlots, buildTableDefinition, createSingleTableStore, deriveIndexMap, dynamoAdapter, ensureSchema, generateSchemaFile, isConditionalCheckFailed, isConditionalTransactionCanceled, isTransactionCanceled, matchesResidual, planQuery, transactionCancellationCodes };
|
|
497
|
+
export { type AccessPattern, DEFAULT_TTL_ATTRIBUTE, type DynamoAdapterConfig, DynamoDBAdapterError, type DynamoStore, type IndexMap, type MigrateKeysOptions, type MigrationReport, type ModelIndexMap, OptimisticLockError, type QueryPage, type QueryPlan, type SingleTableStoreOptions, type StoreItem, type TtlOptions, UniqueConstraintError, type UniquenessConflict, UnsupportedQueryError, assignSlots, buildTableDefinition, createSingleTableStore, deriveIndexMap, dynamoAdapter, ensureSchema, generateSchemaFile, isConditionalCheckFailed, isConditionalTransactionCanceled, isTransactionCanceled, matchesResidual, migrateKeys, planQuery, transactionCancellationCodes };
|
package/dist/index.js
CHANGED
|
@@ -1225,6 +1225,175 @@ async function runBatched(items, op, chunkSize = 10) {
|
|
|
1225
1225
|
await Promise.all(items.slice(i, i + chunkSize).map(op));
|
|
1226
1226
|
}
|
|
1227
1227
|
}
|
|
1228
|
+
|
|
1229
|
+
// src/stores/default/migrate.ts
|
|
1230
|
+
import {
|
|
1231
|
+
DeleteCommand as DeleteCommand2,
|
|
1232
|
+
DynamoDBDocumentClient as DynamoDBDocumentClient2,
|
|
1233
|
+
PutCommand as PutCommand2,
|
|
1234
|
+
ScanCommand,
|
|
1235
|
+
TransactWriteCommand as TransactWriteCommand2
|
|
1236
|
+
} from "@aws-sdk/lib-dynamodb";
|
|
1237
|
+
var epochSeconds2 = (value) => {
|
|
1238
|
+
const date = value instanceof Date ? value : typeof value === "string" || typeof value === "number" ? new Date(value) : void 0;
|
|
1239
|
+
if (!date || Number.isNaN(date.getTime())) return void 0;
|
|
1240
|
+
return Math.floor(date.getTime() / 1e3);
|
|
1241
|
+
};
|
|
1242
|
+
var isLegacyRow = (item) => {
|
|
1243
|
+
const pk = item[PK];
|
|
1244
|
+
if (typeof pk !== "string") return false;
|
|
1245
|
+
return !pk.startsWith(`${ENTITY_PREFIX}#`) && !pk.startsWith(`${UNIQUE_PREFIX}#`);
|
|
1246
|
+
};
|
|
1247
|
+
var isMarkerRow = (item) => {
|
|
1248
|
+
const pk = item[PK];
|
|
1249
|
+
return typeof pk === "string" && pk.startsWith(`${UNIQUE_PREFIX}#`);
|
|
1250
|
+
};
|
|
1251
|
+
async function migrateKeys(opts) {
|
|
1252
|
+
const doc = opts.documentClient ?? (opts.client ? DynamoDBDocumentClient2.from(opts.client, {
|
|
1253
|
+
marshallOptions: { removeUndefinedValues: true }
|
|
1254
|
+
}) : void 0);
|
|
1255
|
+
if (!doc) {
|
|
1256
|
+
throw new DynamoDBAdapterError(
|
|
1257
|
+
"better-auth-dynamodb: migrateKeys needs a `documentClient` or a `client`."
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
const { tableName, indexMap } = opts;
|
|
1261
|
+
const assignment = assignSlots(indexMap);
|
|
1262
|
+
const atomicUniqueness = opts.atomicUniqueness ?? true;
|
|
1263
|
+
const ttl = opts.ttl === false ? void 0 : opts.ttl;
|
|
1264
|
+
const ttlAttribute = ttl?.attributeName ?? DEFAULT_TTL_ATTRIBUTE;
|
|
1265
|
+
const ttlAttributesFor = (model, item) => {
|
|
1266
|
+
const field = ttl ? ttl.fields?.[model] ?? ttl.defaultField : void 0;
|
|
1267
|
+
if (!field) return {};
|
|
1268
|
+
const seconds = epochSeconds2(item[field]);
|
|
1269
|
+
return seconds === void 0 ? {} : { [ttlAttribute]: seconds };
|
|
1270
|
+
};
|
|
1271
|
+
const uniquePatternsFor = (model) => atomicUniqueness ? (indexMap[model] ?? []).filter((p) => p.unique) : [];
|
|
1272
|
+
const legacy = [];
|
|
1273
|
+
let scanned = 0;
|
|
1274
|
+
let alreadyCurrent = 0;
|
|
1275
|
+
let cursor;
|
|
1276
|
+
do {
|
|
1277
|
+
const page = await doc.send(
|
|
1278
|
+
new ScanCommand({ TableName: tableName, ExclusiveStartKey: cursor })
|
|
1279
|
+
);
|
|
1280
|
+
for (const raw of page.Items ?? []) {
|
|
1281
|
+
const item = raw;
|
|
1282
|
+
scanned++;
|
|
1283
|
+
if (isMarkerRow(item)) {
|
|
1284
|
+
alreadyCurrent++;
|
|
1285
|
+
} else if (isLegacyRow(item)) {
|
|
1286
|
+
legacy.push(item);
|
|
1287
|
+
} else {
|
|
1288
|
+
alreadyCurrent++;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
cursor = page.LastEvaluatedKey;
|
|
1292
|
+
opts.onProgress?.({ scanned, migrated: 0 });
|
|
1293
|
+
} while (cursor);
|
|
1294
|
+
if (legacy.length === 0) {
|
|
1295
|
+
return {
|
|
1296
|
+
scanned,
|
|
1297
|
+
migrated: 0,
|
|
1298
|
+
alreadyCurrent,
|
|
1299
|
+
markersCreated: 0,
|
|
1300
|
+
conflicts: [],
|
|
1301
|
+
noop: true
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
const claims = /* @__PURE__ */ new Map();
|
|
1305
|
+
for (const item of legacy) {
|
|
1306
|
+
const model = String(item[TYPE_PK] ?? "");
|
|
1307
|
+
const id = String(item.id);
|
|
1308
|
+
for (const pattern of uniquePatternsFor(model)) {
|
|
1309
|
+
const values = pattern.pk.map((f) => item[f]);
|
|
1310
|
+
if (values.some((v) => v == null)) continue;
|
|
1311
|
+
const key = `${model}\0${pattern.index}\0${values.map(encodeValue).join("\0")}`;
|
|
1312
|
+
const existing = claims.get(key);
|
|
1313
|
+
if (existing) existing.ids.push(id);
|
|
1314
|
+
else claims.set(key, { model, index: pattern.index, ids: [id] });
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
const conflicts = [...claims.values()].filter((c) => c.ids.length > 1).map(({ model, index, ids }) => ({ model, index, ids }));
|
|
1318
|
+
if (conflicts.length > 0) {
|
|
1319
|
+
return {
|
|
1320
|
+
scanned,
|
|
1321
|
+
migrated: 0,
|
|
1322
|
+
alreadyCurrent,
|
|
1323
|
+
markersCreated: 0,
|
|
1324
|
+
conflicts,
|
|
1325
|
+
noop: false
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
let migrated = 0;
|
|
1329
|
+
let markersCreated = 0;
|
|
1330
|
+
for (const item of legacy) {
|
|
1331
|
+
const model = String(item[TYPE_PK] ?? "");
|
|
1332
|
+
const clean = stripReserved(item);
|
|
1333
|
+
const id = String(clean.id);
|
|
1334
|
+
const newItem = {
|
|
1335
|
+
...clean,
|
|
1336
|
+
...encodeKeys(model, clean, indexMap, assignment),
|
|
1337
|
+
...ttlAttributesFor(model, clean),
|
|
1338
|
+
// A fresh revision, so the optimistic guard has something to hold onto.
|
|
1339
|
+
[REVISION]: `migrated-${id}`
|
|
1340
|
+
};
|
|
1341
|
+
const markers = uniquePatternsFor(model).flatMap(
|
|
1342
|
+
(pattern) => {
|
|
1343
|
+
const values = pattern.pk.map((f) => clean[f]);
|
|
1344
|
+
if (values.some((v) => v == null)) return [];
|
|
1345
|
+
return [
|
|
1346
|
+
{
|
|
1347
|
+
Put: {
|
|
1348
|
+
TableName: tableName,
|
|
1349
|
+
Item: {
|
|
1350
|
+
...uniqueMarkerKey(model, pattern.index, values),
|
|
1351
|
+
__ba_owner: id,
|
|
1352
|
+
...ttlAttributesFor(model, clean)
|
|
1353
|
+
},
|
|
1354
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
1355
|
+
ExpressionAttributeNames: { "#pk": PK }
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
];
|
|
1359
|
+
}
|
|
1360
|
+
);
|
|
1361
|
+
if (opts.dryRun) {
|
|
1362
|
+
migrated++;
|
|
1363
|
+
markersCreated += markers.length;
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
if (markers.length === 0) {
|
|
1367
|
+
await doc.send(new PutCommand2({ TableName: tableName, Item: newItem }));
|
|
1368
|
+
} else {
|
|
1369
|
+
await doc.send(
|
|
1370
|
+
new TransactWriteCommand2({
|
|
1371
|
+
TransactItems: [
|
|
1372
|
+
{ Put: { TableName: tableName, Item: newItem } },
|
|
1373
|
+
...markers
|
|
1374
|
+
]
|
|
1375
|
+
})
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
await doc.send(
|
|
1379
|
+
new DeleteCommand2({
|
|
1380
|
+
TableName: tableName,
|
|
1381
|
+
Key: { [PK]: item[PK], [SK]: item[SK] }
|
|
1382
|
+
})
|
|
1383
|
+
);
|
|
1384
|
+
migrated++;
|
|
1385
|
+
markersCreated += markers.length;
|
|
1386
|
+
opts.onProgress?.({ scanned, migrated });
|
|
1387
|
+
}
|
|
1388
|
+
return {
|
|
1389
|
+
scanned,
|
|
1390
|
+
migrated,
|
|
1391
|
+
alreadyCurrent,
|
|
1392
|
+
markersCreated,
|
|
1393
|
+
conflicts: [],
|
|
1394
|
+
noop: false
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1228
1397
|
export {
|
|
1229
1398
|
DEFAULT_TTL_ATTRIBUTE,
|
|
1230
1399
|
DynamoDBAdapterError,
|
|
@@ -1242,6 +1411,7 @@ export {
|
|
|
1242
1411
|
isConditionalTransactionCanceled,
|
|
1243
1412
|
isTransactionCanceled,
|
|
1244
1413
|
matchesResidual,
|
|
1414
|
+
migrateKeys,
|
|
1245
1415
|
planQuery,
|
|
1246
1416
|
transactionCancellationCodes
|
|
1247
1417
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapter.ts","../src/errors.ts","../src/pagination.ts","../src/planner.ts","../src/stores/default/derive-index-map.ts","../src/stores/default/single-table-store.ts","../src/stores/default/key-codec.ts","../src/stores/default/schema.ts"],"sourcesContent":["import type { CleanedWhere } from \"@better-auth/core/db/adapter\";\nimport type { BetterAuthOptions } from \"better-auth\";\nimport type { AdapterFactory } from \"better-auth/adapters\";\nimport { createAdapterFactory } from \"better-auth/adapters\";\n\nimport { UnsupportedQueryError } from \"./errors\";\nimport type { QueryPlan } from \"./planner\";\nimport type { DynamoAdapterConfig, DynamoStore, StoreItem } from \"./types\";\nimport {\n applySort,\n applyWindow,\n DEFAULT_MAX_PAGES,\n drainPages,\n matchesResidual,\n} from \"./pagination\";\nimport { planQuery } from \"./planner\";\nimport { deriveIndexMap } from \"./stores/default/derive-index-map\";\nimport { createSingleTableStore } from \"./stores/default/single-table-store\";\n\n/**\n * A generic DynamoDB adapter for Better Auth.\n *\n * The factory handles field mapping, JSON/date/boolean coercion, ID generation\n * and `where`-clause normalization; this adapter only implements the raw DB\n * operations. Those operations are expressed against a pluggable\n * {@link DynamoStore} and a declarative access-pattern map, so the same core\n * drives both the built-in single-table store and any custom store.\n *\n * @example\n * ```ts\n * betterAuth({ database: dynamoAdapter({ tableName: \"auth\", region: \"us-east-1\" }) });\n * ```\n */\nexport const dynamoAdapter = (\n config: DynamoAdapterConfig = {},\n): AdapterFactory<BetterAuthOptions> => {\n return createAdapterFactory({\n config: {\n adapterId: \"dynamodb\",\n adapterName: \"DynamoDB\",\n // DynamoDB keys are strings; let Better Auth generate string ids.\n supportsNumericIds: false,\n // We serialize JSON ourselves via the SDK document client.\n supportsJSON: true,\n // No native Date type — stored as ISO strings (see transforms below).\n supportsDates: false,\n supportsBooleans: true,\n usePlural: false,\n debugLogs: config.debugLogs,\n\n // Dates <-> ISO strings, since supportsDates is false.\n customTransformInput: ({ data, fieldAttributes }) =>\n fieldAttributes.type === \"date\" && data instanceof Date\n ? data.toISOString()\n : data,\n customTransformOutput: ({ data, fieldAttributes }) =>\n fieldAttributes.type === \"date\" && typeof data === \"string\"\n ? new Date(data)\n : data,\n },\n\n adapter: ({ schema, debugLog, getDefaultModelName, getFieldName }) => {\n const indexMap = config.indexMap ?? deriveIndexMap(schema);\n const maxPages = config.maxPages ?? DEFAULT_MAX_PAGES;\n const store: DynamoStore =\n config.store ??\n createSingleTableStore({\n tableName: config.tableName,\n region: config.region,\n endpoint: config.endpoint,\n documentClient: config.documentClient,\n atomicUniqueness: config.atomicUniqueness,\n maxPages,\n pageSize: config.pageSize,\n ttl: config.ttl,\n indexMap,\n });\n\n /** Resolve every row matching `where`, using the best access path. */\n const queryAll = async (\n model: string,\n where?: CleanedWhere[],\n ): Promise<StoreItem[]> => {\n const clauses = where ?? [];\n const plan = planQuery(model, clauses, indexMap);\n debugLog(\"queryAll\", { model, plan: plan.kind });\n\n let candidates: StoreItem[];\n if (plan.kind === \"byId\") {\n const one = await store.getById(model, plan.id);\n candidates = one ? [one] : [];\n } else if (plan.kind === \"byIds\") {\n const found = await mapBatched(plan.ids, (id) =>\n store.getById(model, id),\n );\n candidates = found.filter((item): item is StoreItem => item !== null);\n } else if (plan.kind === \"index\") {\n candidates = await drainPages(\n (cursor) =>\n store.queryIndex({\n model,\n index: plan.index,\n key: plan.key,\n cursor,\n }),\n maxPages,\n `index query on \"${model}\"`,\n );\n } else {\n if (!config.unsafeAllowScan) {\n throw new UnsupportedQueryError(\n `better-auth-dynamodb: no index can serve this query on ` +\n `\"${model}\" (${describeWhere(clauses)}), so it would have to ` +\n `read every row of the model and filter in memory. Add the ` +\n `field to the index map — or to the Better Auth schema as ` +\n `\\`unique\\`, \\`references\\`, or \\`index: true\\` — or set ` +\n `\\`unsafeAllowScan: true\\` to accept the cost.`,\n );\n }\n candidates = await drainPages(\n (cursor) => store.listByType({ model, cursor }),\n maxPages,\n `model scan of \"${model}\"`,\n );\n }\n return candidates.filter((item) =>\n matchesResidual(item, plan.residual),\n );\n };\n\n /** Find the primary id for a `where`, cheaply if it names `id` directly. */\n const resolveId = async (\n model: string,\n where: CleanedWhere[],\n ): Promise<string | null> => {\n const plan: QueryPlan = planQuery(model, where, indexMap);\n // The shortcut is only sound when `id` was the *whole* predicate.\n // `where: [id = x, status = \"active\"]` must still check the status, or\n // a guarded update would fire against a row that does not qualify.\n if (plan.kind === \"byId\" && plan.residual.length === 0) return plan.id;\n const [first] = await queryAll(model, where);\n return first ? String(first.id) : null;\n };\n\n /**\n * Narrow rows to the requested fields. Better Auth asks for projection at\n * the API surface; DynamoDB could do it server-side, but only by giving\n * up the reserved key attributes the store needs, so it happens here.\n */\n const project = (\n model: string,\n items: StoreItem[],\n select?: string[],\n ): StoreItem[] => {\n if (!select?.length) return items;\n // `select` arrives in Better Auth's field names; rows are stored under\n // whatever `fieldName` the schema maps them to, so the names have to be\n // translated before picking or every field comes back undefined.\n const stored = select.map((field) => getFieldName({ model, field }));\n return items.map((item) => {\n const picked: StoreItem = {};\n for (const field of stored) {\n if (field in item) picked[field] = item[field];\n }\n return picked;\n });\n };\n\n return {\n async create<T extends Record<string, any>>({\n model,\n data,\n }: {\n model: string;\n data: T;\n }) {\n const m = getDefaultModelName(model);\n const created = await store.put(m, data);\n return created as T;\n },\n\n async update<T>({\n model,\n where,\n update,\n }: {\n model: string;\n where: CleanedWhere[];\n update: T;\n }) {\n const m = getDefaultModelName(model);\n const id = await resolveId(m, where);\n if (!id) return null;\n const updated = await store.update(m, id, update as StoreItem);\n return updated as T | null;\n },\n\n async updateMany({\n model,\n where,\n update,\n }: {\n model: string;\n where: CleanedWhere[];\n update: Record<string, any>;\n }) {\n const m = getDefaultModelName(model);\n const items = await queryAll(m, where);\n await runBatched(items, (item) =>\n store.update(m, String(item.id), update),\n );\n return items.length;\n },\n\n async delete({\n model,\n where,\n }: {\n model: string;\n where: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const id = await resolveId(m, where);\n if (id) await store.deleteById(m, id);\n },\n\n async deleteMany({\n model,\n where,\n }: {\n model: string;\n where: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const items = await queryAll(m, where);\n await runBatched(items, (item) =>\n store.deleteById(m, String(item.id)),\n );\n return items.length;\n },\n\n async findOne<T>({\n model,\n where,\n select,\n }: {\n model: string;\n where: CleanedWhere[];\n select?: string[];\n }) {\n const m = getDefaultModelName(model);\n const [first] = project(m, await queryAll(m, where), select);\n return (first ?? null) as T | null;\n },\n\n async findMany<T>({\n model,\n where,\n limit,\n sortBy,\n offset,\n select,\n }: {\n model: string;\n where?: CleanedWhere[];\n limit: number;\n sortBy?: { field: string; direction: \"asc\" | \"desc\" };\n offset?: number;\n select?: string[];\n }) {\n const m = getDefaultModelName(model);\n // Projection runs last: sorting by a field the caller did not select\n // must still work.\n const items = project(\n m,\n applyWindow(\n applySort(await queryAll(m, where), sortBy),\n offset,\n limit,\n ),\n select,\n );\n return items as T[];\n },\n\n async count({\n model,\n where,\n }: {\n model: string;\n where?: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const clauses = where ?? [];\n const plan = planQuery(m, clauses, indexMap);\n // Fast path: no residual predicates and the store can count natively.\n // Only for the two plans a native count actually describes — counting\n // a `byId`/`byIds` plan this way would count the whole model instead.\n if (plan.residual.length === 0 && store.count) {\n if (plan.kind === \"index\") {\n const fast = await store.count({\n model: m,\n index: plan.index,\n key: plan.key,\n });\n if (fast != null) return fast;\n } else if (plan.kind === \"listByType\") {\n const fast = await store.count({ model: m });\n if (fast != null) return fast;\n }\n }\n return (await queryAll(m, clauses)).length;\n },\n\n async consumeOne<T>({\n model,\n where,\n }: {\n model: string;\n where: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const id = await resolveId(m, where);\n if (!id) return null;\n if (store.consumeOne)\n return (await store.consumeOne(m, id)) as T | null;\n // Fallback for stores without a native atomic delete-and-return: not\n // safe under true concurrency, but correct for the common single\n // in-flight verification case.\n const item = await store.getById(m, id);\n if (!item) return null;\n await store.deleteById(m, id);\n return item as T;\n },\n\n async incrementOne<T>({\n model,\n where,\n increment,\n set,\n }: {\n model: string;\n where: CleanedWhere[];\n increment: Record<string, number>;\n set?: Record<string, any>;\n }) {\n const m = getDefaultModelName(model);\n const id = await resolveId(m, where);\n if (!id) return null;\n if (store.incrementOne) {\n return (await store.incrementOne(m, id, {\n increment,\n set,\n })) as T | null;\n }\n // Fallback for stores without a native atomic add/set: not safe\n // under true concurrency, but correct for the common single\n // in-flight update case.\n const current = await store.getById(m, id);\n if (!current) return null;\n const patch: StoreItem = { ...set };\n for (const [field, delta] of Object.entries(increment)) {\n patch[field] = (Number(current[field]) || 0) + delta;\n }\n return (await store.update(m, id, patch)) as T | null;\n },\n\n ...(store.createSchema\n ? {\n createSchema: (props: { file?: string; tables: unknown }) =>\n store.createSchema!(props),\n }\n : {}),\n };\n },\n });\n};\n\n/** Render a `where` for an error message, without leaking the values. */\nfunction describeWhere(where: CleanedWhere[]): string {\n if (where.length === 0) return \"no where clause\";\n return where.map((w) => `${w.field} ${w.operator}`).join(\", \");\n}\n\n/** Map items through an async op in small concurrent chunks, keeping order. */\nasync function mapBatched<T, R>(\n items: T[],\n op: (item: T) => Promise<R>,\n chunkSize = 10,\n): Promise<R[]> {\n const out: R[] = [];\n for (let i = 0; i < items.length; i += chunkSize) {\n out.push(...(await Promise.all(items.slice(i, i + chunkSize).map(op))));\n }\n return out;\n}\n\n/** Run an async op over items in small concurrent chunks to avoid throttling. */\nasync function runBatched<T>(\n items: T[],\n op: (item: T) => Promise<unknown>,\n chunkSize = 10,\n): Promise<void> {\n for (let i = 0; i < items.length; i += chunkSize) {\n await Promise.all(items.slice(i, i + chunkSize).map(op));\n }\n}\n","/**\n * Error types raised by this adapter.\n *\n * Everything thrown from the adapter core or the built-in store derives from\n * {@link DynamoDBAdapterError}, so a consumer can distinguish \"the adapter\n * refused/failed\" from a raw AWS SDK error escaping the seam.\n */\nexport class DynamoDBAdapterError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"DynamoDBAdapterError\";\n }\n}\n\n/**\n * A write lost a race against a uniqueness constraint — another row already\n * holds the value for a `unique` field (e.g. `user.email`, `organization.slug`).\n */\nexport class UniqueConstraintError extends DynamoDBAdapterError {\n constructor(\n readonly model: string,\n readonly fields: string[],\n options?: ErrorOptions,\n ) {\n super(\n `better-auth-dynamodb: unique constraint violation on \"${model}\" ` +\n `(duplicate value for ${fields.join(\", \")})`,\n options,\n );\n this.name = \"UniqueConstraintError\";\n }\n}\n\n/**\n * The requested access pattern cannot be served without a table/model scan, and\n * scans have not been explicitly enabled.\n */\nexport class UnsupportedQueryError extends DynamoDBAdapterError {\n constructor(message: string) {\n super(message);\n this.name = \"UnsupportedQueryError\";\n }\n}\n\n/**\n * A guarded write was rejected because the row changed between the adapter's\n * read and its write. Better Auth's own retry/null semantics decide what\n * happens next; this exists so the cause is legible rather than an opaque\n * `TransactionCanceledException`.\n */\nexport class OptimisticLockError extends DynamoDBAdapterError {\n constructor(model: string, id: string, options?: ErrorOptions) {\n super(\n `better-auth-dynamodb: concurrent modification of \"${model}\" row \"${id}\" ` +\n `— the record changed between read and write`,\n options,\n );\n this.name = \"OptimisticLockError\";\n }\n}\n\nconst named = (error: unknown, name: string): boolean =>\n typeof error === \"object\" &&\n error !== null &&\n \"name\" in error &&\n (error as { name?: unknown }).name === name;\n\n/** True for a bare `ConditionalCheckFailedException` from a non-transactional write. */\nexport const isConditionalCheckFailed = (error: unknown): boolean =>\n named(error, \"ConditionalCheckFailedException\");\n\n/** True for any cancelled `TransactWriteItems`, whatever the reason. */\nexport const isTransactionCanceled = (error: unknown): boolean =>\n named(error, \"TransactionCanceledException\");\n\n/**\n * The per-action cancellation codes AWS attaches to a cancelled transaction\n * (e.g. `ConditionalCheckFailed`, `TransactionConflict`, `ThrottlingError`,\n * `ValidationError`, or `None` for actions that were fine).\n */\nexport function transactionCancellationCodes(error: unknown): string[] {\n if (\n typeof error !== \"object\" ||\n error === null ||\n !(\"CancellationReasons\" in error)\n ) {\n return [];\n }\n const reasons = (error as { CancellationReasons?: unknown })\n .CancellationReasons;\n if (!Array.isArray(reasons)) return [];\n return reasons\n .map((reason) =>\n typeof reason === \"object\" && reason !== null && \"Code\" in reason\n ? (reason as { Code?: unknown }).Code\n : undefined,\n )\n .filter((code): code is string => typeof code === \"string\");\n}\n\n/**\n * True only when a transaction was cancelled *because a condition failed* —\n * not because of throttling, a transaction conflict, or a validation error.\n *\n * This distinction matters: reporting a throttled write as \"email already\n * taken\" is a user-visible lie, and it is the failure mode of any code that\n * treats `TransactionCanceledException` as a uniqueness violation wholesale.\n */\nexport const isConditionalTransactionCanceled = (error: unknown): boolean =>\n isTransactionCanceled(error) &&\n transactionCancellationCodes(error).includes(\"ConditionalCheckFailed\");\n\n/**\n * True when a transaction was cancelled because another transaction was\n * touching the same item. Transient contention, not a decision about the data —\n * so the right response is to try again, not to give up.\n */\nexport const isTransactionConflict = (error: unknown): boolean =>\n isTransactionCanceled(error) &&\n transactionCancellationCodes(error).includes(\"TransactionConflict\");\n","import type { CleanedWhere } from \"@better-auth/core/db/adapter\";\n\nimport { DynamoDBAdapterError } from \"./errors\";\nimport type { QueryPage, StoreItem } from \"./types\";\n\n/** Default cap on pages drained for one logical query. */\nexport const DEFAULT_MAX_PAGES = 25;\n\n/**\n * Drain every page of a paginated store query into a single array.\n *\n * DynamoDB paginates at 1 MB / index-page boundaries regardless of match count,\n * so a single `.query()` can silently return a partial result. Draining here —\n * rather than trusting one page — is what makes `findMany`/`count` correct\n * (and removes the old adapter's implicit 10 000-row cap).\n *\n * Draining is bounded by `maxPages`. Hitting the cap throws: an auth query that\n * silently returns some of its rows is a correctness bug wearing a success\n * response.\n */\nexport async function drainPages(\n fetch: (cursor: unknown) => Promise<QueryPage>,\n maxPages = DEFAULT_MAX_PAGES,\n label = \"query\",\n): Promise<StoreItem[]> {\n const out: StoreItem[] = [];\n let cursor: unknown = undefined;\n let pages = 0;\n do {\n const page = await fetch(cursor);\n out.push(...page.items);\n cursor = page.cursor;\n if (++pages >= maxPages && cursor) {\n throw new DynamoDBAdapterError(\n `better-auth-dynamodb: ${label} exceeded maxPages (${maxPages}) with ` +\n `more pages remaining. Raise maxPages or narrow the query — ` +\n `returning a partial result here would silently drop rows.`,\n );\n }\n } while (cursor);\n return out;\n}\n\nfunction matchOne(item: StoreItem, w: CleanedWhere): boolean {\n // `mode: \"insensitive\"` asks for case-insensitive comparison. It only has\n // meaning between two strings; anything else keeps exact semantics.\n const fold = (v: unknown): unknown =>\n w.mode === \"insensitive\" && typeof v === \"string\" ? v.toLowerCase() : v;\n\n const actual = fold(item[w.field]) as any;\n const expected = Array.isArray(w.value)\n ? (w.value as unknown[]).map(fold)\n : fold(w.value);\n switch (w.operator) {\n case \"eq\":\n return actual === expected;\n case \"ne\":\n return actual !== expected;\n case \"lt\":\n return actual < (expected as any);\n case \"lte\":\n return actual <= (expected as any);\n case \"gt\":\n return actual > (expected as any);\n case \"gte\":\n return actual >= (expected as any);\n case \"in\":\n return Array.isArray(expected) && expected.includes(actual);\n case \"not_in\":\n return Array.isArray(expected) && !expected.includes(actual);\n case \"contains\":\n return String(actual).includes(String(expected));\n case \"starts_with\":\n return String(actual).startsWith(String(expected));\n case \"ends_with\":\n return String(actual).endsWith(String(expected));\n default:\n return actual === expected;\n }\n}\n\n/**\n * Apply the residual `where` clauses in memory, honoring Better Auth's\n * connector semantics: all `AND` clauses must match, and — if any `OR` clauses\n * are present — at least one of them must also match. An empty residual passes.\n */\nexport function matchesResidual(\n item: StoreItem,\n residual: CleanedWhere[],\n): boolean {\n const andOk = residual\n .filter((w) => w.connector !== \"OR\")\n .every((w) => matchOne(item, w));\n if (!andOk) return false;\n\n const or = residual.filter((w) => w.connector === \"OR\");\n return or.length === 0 || or.some((w) => matchOne(item, w));\n}\n\n/** Stable in-memory sort matching Better Auth's `sortBy` contract. */\nexport function applySort(\n items: StoreItem[],\n sortBy?: { field: string; direction: \"asc\" | \"desc\" },\n): StoreItem[] {\n if (!sortBy) return items;\n const { field, direction } = sortBy;\n const dir = direction === \"asc\" ? 1 : -1;\n return [...items].sort((a, b) => {\n if (a[field] < b[field]) return -dir;\n if (a[field] > b[field]) return dir;\n return 0;\n });\n}\n\n/** Apply `offset`/`limit` (a `limit` of 0 or undefined means \"no cap\"). */\nexport function applyWindow(\n items: StoreItem[],\n offset?: number,\n limit?: number,\n): StoreItem[] {\n let out = items;\n if (offset && offset > 0) out = out.slice(offset);\n if (limit && limit > 0) out = out.slice(0, limit);\n return out;\n}\n","import type { CleanedWhere } from \"@better-auth/core/db/adapter\";\n\nimport type { IndexMap } from \"./index-map\";\nimport type { StoreItem } from \"./types\";\n\n/**\n * A resolved query plan: how the adapter will fetch candidate rows for a\n * `where` clause before applying any residual (in-memory) filtering.\n *\n * - `byId` — a direct primary-key get (the cheapest path).\n * - `byIds` — a bounded set of primary-key gets (`id in [...]`).\n * - `index` — a logical index query with a resolved partition/sort key.\n * - `listByType`— no index matched; list all rows of the model and filter.\n *\n * `residual` holds the `where` clauses NOT satisfied by the chosen access path;\n * they are applied in memory by {@link matchesResidual}.\n */\nexport type QueryPlan =\n | { kind: \"byId\"; id: string; residual: CleanedWhere[] }\n | { kind: \"byIds\"; ids: string[]; residual: CleanedWhere[] }\n | { kind: \"index\"; index: string; key: StoreItem; residual: CleanedWhere[] }\n | { kind: \"listByType\"; residual: CleanedWhere[] };\n\n/**\n * A clause usable for index selection: `eq`, `AND`-connected, case-sensitive.\n *\n * `mode: \"insensitive\"` is deliberately excluded. DynamoDB keys are compared\n * byte-for-byte, so serving a case-insensitive equality from an index would\n * silently miss every row whose casing differs from the query. Those clauses\n * stay residual and are matched in memory instead.\n */\nfunction isKeyable(w: CleanedWhere): boolean {\n return (\n w.operator === \"eq\" &&\n w.connector === \"AND\" &&\n w.value != null &&\n w.mode !== \"insensitive\"\n );\n}\n\n/**\n * Choose the most selective access path for `where` against a model's declared\n * access patterns. Falls back to `listByType` when nothing matches.\n *\n * Index selection only ever consumes `eq`/`AND` clauses; everything else (and\n * any `eq` clause not part of the chosen key) becomes residual.\n */\nexport function planQuery(\n model: string,\n where: CleanedWhere[],\n indexMap: IndexMap,\n): QueryPlan {\n const eqByField = new Map<string, CleanedWhere>();\n for (const w of where) {\n if (isKeyable(w) && !eqByField.has(w.field)) eqByField.set(w.field, w);\n }\n\n // Cheapest: a direct id lookup. Everything else filters in memory.\n const idClause = eqByField.get(\"id\");\n if (idClause) {\n return {\n kind: \"byId\",\n id: String(idClause.value),\n residual: where.filter((w) => w !== idClause),\n };\n }\n\n // `id in [...]` is a bounded set of primary-key gets, not a scan. Better\n // Auth uses it to batch-load rows it already has the ids for (loading an\n // organization's members, for instance), and DynamoDB serves it directly.\n const idIn = where.find(\n (w) =>\n w.field === \"id\" &&\n w.operator === \"in\" &&\n w.connector === \"AND\" &&\n Array.isArray(w.value),\n );\n if (idIn) {\n return {\n kind: \"byIds\",\n ids: (idIn.value as unknown[]).map(String),\n residual: where.filter((w) => w !== idIn),\n };\n }\n\n const patterns = indexMap[model] ?? [];\n for (const pattern of patterns) {\n if (!pattern.pk.every((f) => eqByField.has(f))) continue;\n\n const key: StoreItem = {};\n const consumed = new Set<CleanedWhere>();\n for (const f of pattern.pk) {\n const clause = eqByField.get(f)!;\n key[f] = clause.value;\n consumed.add(clause);\n }\n // Attach as many leading sort-key fields as are present (prefix match).\n for (const f of pattern.sk ?? []) {\n const clause = eqByField.get(f);\n if (!clause) break;\n key[f] = clause.value;\n consumed.add(clause);\n }\n\n return {\n kind: \"index\",\n index: pattern.index,\n key,\n residual: where.filter((w) => !consumed.has(w)),\n };\n }\n\n return { kind: \"listByType\", residual: where };\n}\n","import type { BetterAuthDBSchema } from \"@better-auth/core/db\";\n\nimport type { AccessPattern, IndexMap } from \"../../index-map\";\n\n/** Logical index name for a single-field lookup (e.g. `email` -> `by_email`). */\nexport function lookupIndexName(field: string): string {\n return `by_${field}`;\n}\n\n/**\n * Derive a logical access-pattern map from a Better Auth schema.\n *\n * A single-field lookup index is created for every field that is either:\n * - `unique` (e.g. `user.email`, `session.token`) — direct lookups, or\n * - a `references` foreign key (e.g. `account.userId`) — by-parent lookups, or\n * - explicitly flagged `index: true`.\n *\n * This is what lets the built-in store serve any Better Auth model or plugin\n * (two-factor, passkey, api-key, …) out of the box: whatever fields the schema\n * marks as looked-up get an index, with no hand-maintained per-model table.\n */\nexport function deriveIndexMap(schema: BetterAuthDBSchema): IndexMap {\n const map: IndexMap = {};\n\n for (const [model, table] of Object.entries(schema)) {\n const patterns: AccessPattern[] = [];\n const seen = new Set<string>();\n\n const add = (field: string, unique = false) => {\n if (field === \"id\" || seen.has(field)) return;\n seen.add(field);\n patterns.push({ index: lookupIndexName(field), pk: [field], unique });\n };\n\n // `unique` fields first (most selective), then foreign keys, then `index`.\n for (const [field, attr] of Object.entries(table.fields)) {\n if (attr.unique) add(field, true);\n }\n for (const [field, attr] of Object.entries(table.fields)) {\n if (attr.references) add(field);\n }\n for (const [field, attr] of Object.entries(table.fields)) {\n if (attr.index) add(field);\n }\n\n if (patterns.length > 0) map[model] = patterns;\n }\n\n return map;\n}\n","import { randomUUID } from \"node:crypto\";\n\nimport { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport {\n DeleteCommand,\n DynamoDBDocumentClient,\n GetCommand,\n PutCommand,\n QueryCommand,\n TransactWriteCommand,\n type TransactWriteCommandInput,\n UpdateCommand,\n} from \"@aws-sdk/lib-dynamodb\";\n\nimport {\n DynamoDBAdapterError,\n isConditionalCheckFailed,\n isConditionalTransactionCanceled,\n isTransactionConflict,\n OptimisticLockError,\n UniqueConstraintError,\n} from \"../../errors\";\nimport type { AccessPattern, IndexMap } from \"../../index-map\";\nimport type {\n DynamoStore,\n QueryPage,\n StoreItem,\n TtlOptions,\n} from \"../../types\";\nimport type { SlotAssignment } from \"./key-codec\";\nimport {\n assignSlots,\n DEFAULT_TTL_ATTRIBUTE,\n encodeKeys,\n encodeLookupQuery,\n PK,\n primaryKey,\n REVISION,\n stripReserved,\n TYPE_INDEX,\n TYPE_PK,\n uniqueMarkerKey,\n} from \"./key-codec\";\nimport { generateSchemaFile } from \"./schema\";\n\ntype TransactItem = NonNullable<\n TransactWriteCommandInput[\"TransactItems\"]\n>[number];\n\n/** DynamoDB's hard limit on actions in one `TransactWriteItems` call. */\nconst MAX_TRANSACT_ITEMS = 100;\n\n/** Attempts at a guarded read-modify-write before giving up on the race. */\nconst MAX_WRITE_ATTEMPTS = 3;\n\n/** Default cap on pages drained for one logical query. */\nconst DEFAULT_MAX_PAGES = 25;\n\nexport interface SingleTableStoreOptions {\n /** DynamoDB table name. Falls back to `DYNAMODB_TABLE_NAME`, then `\"better-auth\"`. */\n tableName?: string;\n /** AWS region (passed to the DynamoDB client). */\n region?: string;\n /** Endpoint override, e.g. DynamoDB Local or LocalStack (`http://localhost:4566`). */\n endpoint?: string;\n /** Resolved logical access-pattern map (derived or user-supplied). */\n indexMap: IndexMap;\n /**\n * Pre-built document client. Preferred in production, so your application\n * owns credentials, region, middleware, tracing, and marshalling.\n */\n documentClient?: DynamoDBDocumentClient;\n /**\n * Enforce `unique` schema fields with transactional marker rows. Default\n * `true`. Turning it off halves the write cost of creates and restores\n * Better Auth's application-layer check-then-insert, which can admit\n * duplicates under concurrency.\n */\n atomicUniqueness?: boolean;\n /** Max DynamoDB pages drained for one internal count. Default 25. */\n maxPages?: number;\n /** Optional per-request DynamoDB `Limit`. Does not change logical results. */\n pageSize?: number;\n /** Adapter-managed DynamoDB TTL. Omit (or `false`) to disable entirely. */\n ttl?: TtlOptions | false;\n}\n\nconst epochSeconds = (value: unknown): number | undefined => {\n const date =\n value instanceof Date\n ? value\n : typeof value === \"string\" || typeof value === \"number\"\n ? new Date(value)\n : undefined;\n if (!date || Number.isNaN(date.getTime())) return undefined;\n return Math.floor(date.getTime() / 1000);\n};\n\n/**\n * A zero-dependency (beyond the AWS SDK) DynamoDB store for Better Auth.\n *\n * Uses a single table with a `byType` GSI plus generic lookup GSIs. It owns all\n * physical key encoding via the key codec, so the generic adapter core drives it\n * through logical index names only. Works with any Better Auth model or plugin\n * whose looked-up fields are described by the (typically schema-derived) index\n * map.\n *\n * Every mutation is guarded: creates are conditional on the row not already\n * existing, updates and deletes carry an optimistic revision check, and\n * uniqueness markers move in the same transaction as the row they describe.\n */\nexport function createSingleTableStore(\n opts: SingleTableStoreOptions,\n): DynamoStore {\n const tableName =\n opts.tableName ?? process.env.DYNAMODB_TABLE_NAME ?? \"better-auth\";\n const doc =\n opts.documentClient ??\n DynamoDBDocumentClient.from(\n new DynamoDBClient({\n ...(opts.region ? { region: opts.region } : {}),\n ...(opts.endpoint ? { endpoint: opts.endpoint } : {}),\n }),\n { marshallOptions: { removeUndefinedValues: true } },\n );\n const indexMap = opts.indexMap;\n const assignment: SlotAssignment = assignSlots(indexMap);\n const atomicUniqueness = opts.atomicUniqueness ?? true;\n const maxPages = opts.maxPages ?? DEFAULT_MAX_PAGES;\n const ttl = opts.ttl === false ? undefined : opts.ttl;\n const ttlAttribute = ttl?.attributeName ?? DEFAULT_TTL_ATTRIBUTE;\n\n // ---------------------------------------------------------------- TTL ----\n\n const ttlFieldFor = (model: string): string | undefined =>\n ttl ? (ttl.fields?.[model] ?? ttl.defaultField) : undefined;\n\n const ttlAttributesFor = (model: string, item: StoreItem): StoreItem => {\n const field = ttlFieldFor(model);\n if (!field) return {};\n const seconds = epochSeconds(item[field]);\n return seconds === undefined ? {} : { [ttlAttribute]: seconds };\n };\n\n /**\n * DynamoDB reaps expired rows lazily — up to a couple of days late. Treating\n * the TTL attribute as logical expiry on read is what makes an expired\n * session unusable the moment it expires rather than whenever AWS gets round\n * to the delete.\n */\n const isExpired = (item: StoreItem | undefined | null): boolean => {\n if (!item || !ttl) return false;\n const seconds = item[ttlAttribute];\n return (\n typeof seconds === \"number\" && seconds <= Math.floor(Date.now() / 1000)\n );\n };\n\n const modelHasTtl = (model: string): boolean => Boolean(ttlFieldFor(model));\n\n // ----------------------------------------------------------- uniqueness --\n\n const uniquePatternsFor = (model: string): AccessPattern[] =>\n atomicUniqueness ? (indexMap[model] ?? []).filter((p) => p.unique) : [];\n\n const uniqueFieldNames = (model: string): string[] =>\n uniquePatternsFor(model).flatMap((p) => p.pk);\n\n const markerItem = (\n model: string,\n pattern: AccessPattern,\n values: unknown[],\n ownerId: string,\n source: StoreItem,\n ): StoreItem => ({\n ...uniqueMarkerKey(model, pattern.index, values),\n __ba_owner: ownerId,\n ...ttlAttributesFor(model, source),\n });\n\n const applicableValues = (\n pattern: AccessPattern,\n item: StoreItem,\n ): unknown[] | null => {\n const values = pattern.pk.map((f) => item[f]);\n return values.some((v) => v == null) ? null : values;\n };\n\n const uniqueMarkerPuts = (\n model: string,\n item: StoreItem,\n id: string,\n ): TransactItem[] =>\n uniquePatternsFor(model).flatMap((pattern) => {\n const values = applicableValues(pattern, item);\n if (!values) return [];\n return [\n {\n Put: {\n TableName: tableName,\n Item: markerItem(model, pattern, values, id, item),\n ConditionExpression: \"attribute_not_exists(#pk)\",\n ExpressionAttributeNames: { \"#pk\": PK },\n },\n },\n ];\n });\n\n const uniqueMarkerDeletes = (\n model: string,\n item: StoreItem | null,\n ): TransactItem[] => {\n if (!item) return [];\n return uniquePatternsFor(model).flatMap((pattern) => {\n const values = applicableValues(pattern, item);\n if (!values) return [];\n return [\n {\n Delete: {\n TableName: tableName,\n Key: uniqueMarkerKey(model, pattern.index, values),\n },\n },\n ];\n });\n };\n\n /**\n * Marker moves for an update. A pattern whose value did not change is\n * skipped: deleting and re-putting the same key inside one transaction is a\n * self-conflict DynamoDB rejects outright.\n */\n const uniqueMarkerDiff = (\n model: string,\n before: StoreItem,\n after: StoreItem,\n id: string,\n ): TransactItem[] => {\n const items: TransactItem[] = [];\n for (const pattern of uniquePatternsFor(model)) {\n const oldValues = applicableValues(pattern, before);\n const newValues = applicableValues(pattern, after);\n if (\n oldValues?.length === newValues?.length &&\n oldValues?.every((v, i) => v === newValues![i])\n ) {\n continue;\n }\n if (oldValues) {\n items.push({\n Delete: {\n TableName: tableName,\n Key: uniqueMarkerKey(model, pattern.index, oldValues),\n },\n });\n }\n if (newValues) {\n items.push({\n Put: {\n TableName: tableName,\n Item: markerItem(model, pattern, newValues, id, after),\n ConditionExpression: \"attribute_not_exists(#pk)\",\n ExpressionAttributeNames: { \"#pk\": PK },\n },\n });\n }\n }\n return items;\n };\n\n // ---------------------------------------------------------- transactions --\n\n const keyOf = (action: TransactItem): string => {\n const target =\n action.Put?.Item ?? action.Delete?.Key ?? action.Update?.Key ?? {};\n return `${String((target as StoreItem)[PK])}`;\n };\n\n /**\n * DynamoDB rejects a transaction that touches the same item twice, and caps\n * one at 100 actions. Both produce opaque server-side errors, so they are\n * checked here where the cause is still known.\n */\n const validateTransaction = (model: string, items: TransactItem[]): void => {\n if (items.length > MAX_TRANSACT_ITEMS) {\n throw new DynamoDBAdapterError(\n `better-auth-dynamodb: a single write to \"${model}\" would need ` +\n `${items.length} transaction actions, over DynamoDB's ` +\n `${MAX_TRANSACT_ITEMS}-action limit. Reduce the number of unique ` +\n `fields on this model.`,\n );\n }\n const seen = new Set<string>();\n for (const item of items) {\n const key = keyOf(item);\n if (seen.has(key)) {\n throw new DynamoDBAdapterError(\n `better-auth-dynamodb: internal error — two actions in one ` +\n `transaction target the same item on \"${model}\". Please report this.`,\n );\n }\n seen.add(key);\n }\n };\n\n const sendTransaction = async (\n model: string,\n items: TransactItem[],\n ): Promise<void> => {\n validateTransaction(model, items);\n await doc.send(\n new TransactWriteCommand({\n TransactItems: items,\n // Gives the SDK's internal retries of this one send a stable token.\n // Not cross-invocation idempotency.\n ClientRequestToken: randomUUID(),\n }),\n );\n };\n\n const asUniqueConflict = (model: string, err: unknown): never => {\n if (isConditionalTransactionCanceled(err)) {\n throw new UniqueConstraintError(model, uniqueFieldNames(model), {\n cause: err,\n });\n }\n throw err;\n };\n\n // ------------------------------------------------------ revision guards --\n\n /** Read the raw (reserved attributes intact) row — needed for its revision. */\n const readRaw = async (\n model: string,\n id: string,\n ): Promise<StoreItem | null> => {\n const res = await doc.send(\n new GetCommand({\n TableName: tableName,\n Key: primaryKey(model, id),\n ConsistentRead: true,\n }),\n );\n if (!res.Item || isExpired(res.Item)) return null;\n return res.Item as StoreItem;\n };\n\n /**\n * Condition that the row still carries the revision we read. Rows written\n * before revisions existed are matched on the revision being absent, so they\n * migrate in place on their first write rather than becoming unmutatable.\n */\n const revisionGuard = (existing: StoreItem) => {\n const revision = existing[REVISION];\n return revision === undefined\n ? {\n ConditionExpression:\n \"attribute_exists(#pk) AND attribute_not_exists(#rev)\",\n ExpressionAttributeNames: { \"#pk\": PK, \"#rev\": REVISION },\n }\n : {\n ConditionExpression: \"attribute_exists(#pk) AND #rev = :rev\",\n ExpressionAttributeNames: { \"#pk\": PK, \"#rev\": REVISION },\n ExpressionAttributeValues: { \":rev\": revision },\n };\n };\n\n /** The write was refused by a condition we set, rather than failing outright. */\n const rejectedByGuard = (err: unknown): boolean =>\n isConditionalCheckFailed(err) || isConditionalTransactionCanceled(err);\n\n // ---------------------------------------------------------------- reads --\n\n const query = async (\n input: Omit<\n ConstructorParameters<typeof QueryCommand>[0],\n \"TableName\" | \"ExclusiveStartKey\"\n >,\n cursor?: unknown,\n countOnly = false,\n ): Promise<{ items: StoreItem[]; count: number; cursor?: unknown }> => {\n const res = await doc.send(\n new QueryCommand({\n TableName: tableName,\n ExclusiveStartKey: cursor as Record<string, unknown> | undefined,\n ...(countOnly ? { Select: \"COUNT\" } : {}),\n ...(opts.pageSize ? { Limit: opts.pageSize } : {}),\n ...input,\n }),\n );\n return {\n items: countOnly\n ? []\n : (res.Items ?? [])\n .filter((item) => !isExpired(item as StoreItem))\n .map((i) => stripReserved(i)!),\n count: res.Count ?? 0,\n cursor: res.LastEvaluatedKey,\n };\n };\n\n // --------------------------------------------------------------- writes --\n\n const putEntity = (model: string, item: StoreItem): TransactItem => ({\n Put: {\n TableName: tableName,\n Item: {\n ...item,\n ...encodeKeys(model, item, indexMap, assignment),\n ...ttlAttributesFor(model, item),\n [REVISION]: randomUUID(),\n },\n ConditionExpression: \"attribute_not_exists(#pk)\",\n ExpressionAttributeNames: { \"#pk\": PK },\n },\n });\n\n /**\n * Guarded read-modify-write. `build` turns the current raw row into the\n * transaction that replaces it; a lost race re-reads and tries again rather\n * than surfacing a spurious failure for what is usually a benign interleave.\n */\n const guardedWrite = async <T>(\n model: string,\n id: string,\n build: (existing: StoreItem) => { items: TransactItem[]; result: T },\n onMissing: () => T,\n ): Promise<T> => {\n for (let attempt = 1; attempt <= MAX_WRITE_ATTEMPTS; attempt++) {\n const existing = await readRaw(model, id);\n if (!existing) return onMissing();\n const { items, result } = build(existing);\n try {\n await sendTransaction(model, items);\n return result;\n } catch (err) {\n // Two retryable shapes: our own guard refused the write because the row\n // moved, or DynamoDB hit contention on the item. Anything else is real.\n const guarded = rejectedByGuard(err);\n if (!guarded && !isTransactionConflict(err)) throw err;\n // A uniqueness marker collision is terminal; a stale revision is not.\n if (guarded && (await lostToUniqueness(model, id, existing))) {\n return asUniqueConflict(model, err);\n }\n if (attempt === MAX_WRITE_ATTEMPTS) {\n throw new OptimisticLockError(model, id, { cause: err });\n }\n }\n }\n /* c8 ignore next */\n throw new OptimisticLockError(model, id);\n };\n\n /**\n * Distinguish the two reasons a guarded write is cancelled: the row moved\n * under us (retryable), or a unique marker is held by somebody else\n * (terminal). DynamoDB reports both as `ConditionalCheckFailed`, so the row\n * is re-read to tell them apart.\n */\n const lostToUniqueness = async (\n model: string,\n id: string,\n previous: StoreItem,\n ): Promise<boolean> => {\n if (uniquePatternsFor(model).length === 0) return false;\n const current = await readRaw(model, id);\n // The revision is unchanged, so the row did not move — the marker did.\n return Boolean(current) && current![REVISION] === previous[REVISION];\n };\n\n return {\n async put(model, item) {\n const id = String(item.id);\n const entity = putEntity(model, item);\n const markers = uniqueMarkerPuts(model, item, id);\n\n try {\n if (markers.length === 0) {\n await doc.send(new PutCommand({ ...entity.Put! }));\n } else {\n await sendTransaction(model, [entity, ...markers]);\n }\n } catch (err) {\n if (isConditionalCheckFailed(err)) {\n throw new DynamoDBAdapterError(\n `better-auth-dynamodb: a \"${model}\" row with id \"${id}\" already exists.`,\n { cause: err },\n );\n }\n asUniqueConflict(model, err);\n }\n return item;\n },\n\n async getById(model, id) {\n const raw = await readRaw(model, id);\n return stripReserved(raw);\n },\n\n async update(model, id, patch) {\n return guardedWrite<StoreItem | null>(\n model,\n id,\n (existing) => {\n const clean = stripReserved(existing)!;\n const merged = { ...clean, ...patch };\n return {\n items: [\n {\n Put: {\n TableName: tableName,\n Item: {\n ...merged,\n ...encodeKeys(model, merged, indexMap, assignment),\n ...ttlAttributesFor(model, merged),\n [REVISION]: randomUUID(),\n },\n ...revisionGuard(existing),\n },\n },\n ...uniqueMarkerDiff(model, clean, merged, id),\n ],\n result: merged,\n };\n },\n () => null,\n );\n },\n\n async deleteById(model, id) {\n await guardedWrite<void>(\n model,\n id,\n (existing) => ({\n items: [\n {\n Delete: {\n TableName: tableName,\n Key: primaryKey(model, id),\n ...revisionGuard(existing),\n },\n },\n ...uniqueMarkerDeletes(model, stripReserved(existing)),\n ],\n result: undefined,\n }),\n () => undefined,\n );\n },\n\n async consumeOne(model, id) {\n // With no markers to move, a single conditional delete is already atomic\n // and returns what it removed — no read needed.\n if (uniquePatternsFor(model).length === 0) {\n const res = await doc.send(\n new DeleteCommand({\n TableName: tableName,\n Key: primaryKey(model, id),\n ReturnValues: \"ALL_OLD\",\n }),\n );\n const item = res.Attributes as StoreItem | undefined;\n return isExpired(item) ? null : stripReserved(item);\n }\n\n // Otherwise the revision guard is what makes exactly one caller win: the\n // loser's condition fails against an already-deleted row.\n const existing = await readRaw(model, id);\n if (!existing) return null;\n const clean = stripReserved(existing)!;\n try {\n await sendTransaction(model, [\n {\n Delete: {\n TableName: tableName,\n Key: primaryKey(model, id),\n ...revisionGuard(existing),\n },\n },\n ...uniqueMarkerDeletes(model, clean),\n ]);\n } catch (err) {\n if (rejectedByGuard(err)) return null;\n throw err;\n }\n return clean;\n },\n\n async incrementOne(model, id, { increment, set }) {\n const indexedFields = new Set(\n (indexMap[model] ?? []).flatMap((p) => [...p.pk, ...(p.sk ?? [])]),\n );\n const touchesIndex = Object.keys(set ?? {}).some((f) =>\n indexedFields.has(f),\n );\n const ttlField = ttlFieldFor(model);\n const touchesTtl = Boolean(ttlField && set && ttlField in set);\n\n // A native ADD cannot also re-encode GSI keys or the TTL attribute, so a\n // `set` that moves an indexed or TTL field goes the read-modify-write\n // route instead of silently leaving stale index rows behind.\n if (touchesIndex || touchesTtl) {\n return guardedWrite<StoreItem | null>(\n model,\n id,\n (existing) => {\n const clean = stripReserved(existing)!;\n const merged = { ...clean, ...(set ?? {}) };\n for (const [field, delta] of Object.entries(increment)) {\n const current = merged[field];\n merged[field] =\n (typeof current === \"number\" ? current : 0) + delta;\n }\n return {\n items: [\n {\n Put: {\n TableName: tableName,\n Item: {\n ...merged,\n ...encodeKeys(model, merged, indexMap, assignment),\n ...ttlAttributesFor(model, merged),\n [REVISION]: randomUUID(),\n },\n ...revisionGuard(existing),\n },\n },\n ...uniqueMarkerDiff(model, clean, merged, id),\n ],\n result: merged,\n };\n },\n () => null,\n );\n }\n\n const names: Record<string, string> = { \"#pk\": PK, \"#rev\": REVISION };\n const values: Record<string, unknown> = { \":rev\": randomUUID() };\n const addClauses: string[] = [];\n const setClauses: string[] = [\"#rev = :rev\"];\n let i = 0;\n for (const [field, delta] of Object.entries(increment)) {\n names[`#f${i}`] = field;\n values[`:v${i}`] = delta;\n addClauses.push(`#f${i} :v${i}`);\n i++;\n }\n for (const [field, value] of Object.entries(set ?? {})) {\n names[`#f${i}`] = field;\n values[`:v${i}`] = value;\n setClauses.push(`#f${i} = :v${i}`);\n i++;\n }\n const expression = [\n `SET ${setClauses.join(\", \")}`,\n addClauses.length ? `ADD ${addClauses.join(\", \")}` : null,\n ]\n .filter(Boolean)\n .join(\" \");\n\n try {\n const res = await doc.send(\n new UpdateCommand({\n TableName: tableName,\n Key: primaryKey(model, id),\n UpdateExpression: expression,\n // Without this, `ADD` would happily create the row it was told to\n // increment, turning \"increment an existing counter\" into an upsert.\n ConditionExpression: \"attribute_exists(#pk)\",\n ExpressionAttributeNames: names,\n ExpressionAttributeValues: values,\n ReturnValues: \"ALL_NEW\",\n }),\n );\n const item = res.Attributes as StoreItem | undefined;\n return isExpired(item) ? null : stripReserved(item);\n } catch (err) {\n if (isConditionalCheckFailed(err)) return null;\n throw err;\n }\n },\n\n async queryIndex({ model, index, key, cursor }): Promise<QueryPage> {\n const lookup = encodeLookupQuery(model, index, key, indexMap, assignment);\n const names: Record<string, string> = { \"#pk\": lookup.pkAttr };\n const values: Record<string, unknown> = { \":pk\": lookup.pkValue };\n let condition = \"#pk = :pk\";\n if (lookup.skPrefix) {\n names[\"#sk\"] = lookup.skAttr;\n values[\":skp\"] = lookup.skPrefix;\n condition += \" AND begins_with(#sk, :skp)\";\n }\n const { items, cursor: next } = await query(\n {\n IndexName: lookup.indexName,\n KeyConditionExpression: condition,\n ExpressionAttributeNames: names,\n ExpressionAttributeValues: values,\n },\n cursor,\n );\n return { items, cursor: next };\n },\n\n async listByType({ model, cursor }): Promise<QueryPage> {\n const { items, cursor: next } = await query(\n {\n IndexName: TYPE_INDEX,\n KeyConditionExpression: \"#tpk = :tpk\",\n ExpressionAttributeNames: { \"#tpk\": TYPE_PK },\n ExpressionAttributeValues: { \":tpk\": model },\n },\n cursor,\n );\n return { items, cursor: next };\n },\n\n async count({ model, index, key }) {\n // `Select: COUNT` counts rows DynamoDB has not reaped yet, which would\n // over-report a model whose rows expire logically. Fall back to draining.\n if (modelHasTtl(model)) return null;\n\n let base: Parameters<typeof query>[0];\n if (index && key) {\n const lookup = encodeLookupQuery(\n model,\n index,\n key,\n indexMap,\n assignment,\n );\n base = {\n IndexName: lookup.indexName,\n KeyConditionExpression: \"#pk = :pk\",\n ExpressionAttributeNames: { \"#pk\": lookup.pkAttr },\n ExpressionAttributeValues: { \":pk\": lookup.pkValue },\n };\n } else {\n base = {\n IndexName: TYPE_INDEX,\n KeyConditionExpression: \"#tpk = :tpk\",\n ExpressionAttributeNames: { \"#tpk\": TYPE_PK },\n ExpressionAttributeValues: { \":tpk\": model },\n };\n }\n let total = 0;\n let cursor: unknown = undefined;\n let pages = 0;\n do {\n const page = await query(base, cursor, true);\n total += page.count;\n cursor = page.cursor;\n if (++pages >= maxPages && cursor) {\n throw new DynamoDBAdapterError(\n `better-auth-dynamodb: counting \"${model}\" exceeded maxPages ` +\n `(${maxPages}) with more pages remaining. Raise maxPages or ` +\n `narrow the query — returning a partial count would be wrong.`,\n );\n }\n } while (cursor);\n return total;\n },\n\n createSchema: ({ file }) =>\n Promise.resolve(\n generateSchemaFile({\n tableName,\n lookupSlots: assignment.maxSlots,\n file,\n ttlAttribute: ttl ? ttlAttribute : undefined,\n }),\n ),\n };\n}\n","import { createHash } from \"node:crypto\";\n\nimport { DynamoDBAdapterError } from \"../../errors\";\nimport type { IndexMap } from \"../../index-map\";\nimport type { StoreItem } from \"../../types\";\n\n/**\n * Physical key encoding for the built-in single-table store.\n *\n * All reserved attributes are namespaced under `__ba_` so they never collide\n * with Better Auth field names, and are stripped before items are returned. The\n * table has a `PK`/`SK` primary key, one `byType` GSI (to list a whole model),\n * and N generic lookup GSIs — one physical slot per logical index a model uses.\n *\n * ## Why keys are length-prefixed\n *\n * Every variable component of a key is written as `s<byteLength>:<value>`. A\n * naive `a#b#c` join lets a value containing the delimiter forge a different\n * key: with composite partition keys, `(\"a#b\", \"c\")` and `(\"a\", \"b#c\")` encode\n * identically, and a sort-key `begins_with(\"cred#\")` probe matches a row whose\n * value is literally `cred#ential`. Length prefixes make that structurally\n * impossible — `s4:cred#` can never be a prefix of `s10:credential#` — so no\n * field value, whatever a provider or a user puts in it, can be made to collide\n * with or over-match another row's key.\n */\nexport const PK = \"__ba_pk\";\nexport const SK = \"__ba_sk\";\nexport const TYPE_PK = \"__ba_tpk\";\nexport const TYPE_SK = \"__ba_tsk\";\nexport const TYPE_INDEX = \"byType\";\n/** Hidden per-row revision, used as the optimistic-write guard. */\nexport const REVISION = \"__ba_rev\";\n/** Default DynamoDB TTL attribute (epoch seconds). Configurable per store. */\nexport const DEFAULT_TTL_ATTRIBUTE = \"__ba_ttl\";\nconst SK_CONST = \"#\";\nconst RESERVED_PREFIX = \"__ba_\";\n\n/** Row-kind discriminators. Fixed literals, never derived from user data. */\nconst ENTITY_PREFIX = \"E\";\nconst LOOKUP_PREFIX = \"L\";\nconst UNIQUE_PREFIX = \"U\";\n\n/** DynamoDB's own key size limits. */\nconst MAX_PARTITION_KEY_BYTES = 2048;\nconst MAX_SORT_KEY_BYTES = 1024;\n\n/**\n * Byte budget for a single encoded field value before it is hashed instead.\n *\n * The threshold is fixed rather than derived from the assembled key, because a\n * value must encode identically no matter which key it lands in — otherwise a\n * write and its matching lookup would disagree.\n */\nconst MAX_VALUE_COMPONENT_BYTES = 256;\n\nexport const gsiName = (slot: number): string => `lookup${slot}`;\nexport const gsiPk = (slot: number): string => `__ba_g${slot}pk`;\nexport const gsiSk = (slot: number): string => `__ba_g${slot}sk`;\n\nconst byteLength = (value: string): number => Buffer.byteLength(value, \"utf8\");\n\n/** `s<byteLength>:<value>` — the self-delimiting component encoding. */\nconst component = (value: string): string => `s${byteLength(value)}:${value}`;\n\n/**\n * Type-tagged rendering of a field value, so `1`, `\"1\"`, and `true` cannot\n * share a key. Dates normalise to ISO-8601 so an equivalent `Date` and string\n * agree.\n */\nexport function encodeValue(value: unknown): string {\n if (value === null || value === undefined) return \"null:\";\n if (value instanceof Date) return `date:${value.toISOString()}`;\n switch (typeof value) {\n case \"string\":\n return `string:${value}`;\n case \"number\":\n return `number:${value}`;\n case \"boolean\":\n return `boolean:${value}`;\n case \"bigint\":\n return `bigint:${value}`;\n case \"symbol\":\n return `symbol:${value.description ?? \"\"}`;\n case \"function\":\n return \"function:\";\n default:\n // Objects have no meaningful key form; JSON at least keeps them distinct\n // rather than collapsing every one of them to \"[object Object]\".\n return `object:${JSON.stringify(value)}`;\n }\n}\n\n/**\n * Encode one field value as a key component, hashing it when it is too long to\n * sit in a DynamoDB key. `hs:` and `s<digits>:` are disjoint prefixes, so a\n * hashed component can never be confused with a literal one.\n */\nfunction valueComponent(value: unknown): string {\n const typed = encodeValue(value);\n if (byteLength(typed) > MAX_VALUE_COMPONENT_BYTES) {\n return `hs:${createHash(\"sha256\").update(typed).digest(\"hex\")}`;\n }\n return component(typed);\n}\n\nconst joinValues = (values: unknown[]): string =>\n values.map(valueComponent).join(\"#\");\n\nfunction checkedKey(\n value: string,\n label: string,\n maxBytes: number,\n keyType: \"partition\" | \"sort\",\n): string {\n if (byteLength(value) <= maxBytes) return value;\n throw new DynamoDBAdapterError(\n `better-auth-dynamodb: ${label} exceeds DynamoDB's ${maxBytes}-byte ` +\n `${keyType} key limit. Field values are hashed past ` +\n `${MAX_VALUE_COMPONENT_BYTES} bytes, so this means the model name, ` +\n `index name, or record id is itself too long.`,\n );\n}\n\nconst partitionKey = (value: string, label: string): string =>\n checkedKey(value, label, MAX_PARTITION_KEY_BYTES, \"partition\");\n\nconst sortKey = (value: string, label: string): string =>\n checkedKey(value, label, MAX_SORT_KEY_BYTES, \"sort\");\n\n/** Physical slot assignment: model -> (logical index name -> GSI slot number). */\nexport interface SlotAssignment {\n slots: Record<string, Record<string, number>>;\n maxSlots: number;\n}\n\n/**\n * Assign each logical index to a physical GSI slot. Because indexes on\n * different models never coexist on one item, a model's Nth index always maps\n * to slot N — so the table needs only `max(indexes per model)` lookup GSIs.\n */\nexport function assignSlots(indexMap: IndexMap): SlotAssignment {\n const slots: Record<string, Record<string, number>> = {};\n let maxSlots = 0;\n for (const [model, patterns] of Object.entries(indexMap)) {\n const modelSlots: Record<string, number> = {};\n patterns.forEach((pattern, i) => {\n modelSlots[pattern.index] = i + 1;\n });\n slots[model] = modelSlots;\n maxSlots = Math.max(maxSlots, patterns.length);\n }\n return { slots, maxSlots };\n}\n\nexport const primaryKey = (model: string, id: string) => ({\n [PK]: partitionKey(\n `${ENTITY_PREFIX}#${component(model)}#${component(id)}`,\n \"entity partition key\",\n ),\n [SK]: SK_CONST,\n});\n\n/**\n * One marker row per (model, unique index, value) tuple. Written in the same\n * transaction as the entity row with `attribute_not_exists`, which is what\n * makes uniqueness a property of the database rather than of Better Auth's\n * check-then-insert.\n */\nexport const uniqueMarkerKey = (\n model: string,\n index: string,\n values: unknown[],\n) => ({\n [PK]: partitionKey(\n `${UNIQUE_PREFIX}#${component(model)}#${component(index)}#${joinValues(values)}`,\n \"unique marker partition key\",\n ),\n [SK]: SK_CONST,\n});\n\nconst lookupPkValue = (model: string, index: string, values: unknown[]) =>\n partitionKey(\n `${LOOKUP_PREFIX}#${component(model)}#${component(index)}#${joinValues(values)}`,\n \"lookup partition key\",\n );\n\n/**\n * Compute the reserved key attributes to store alongside an item: the primary\n * key, the type-index key, and a lookup key for every index whose partition\n * fields are all present on the item.\n */\nexport function encodeKeys(\n model: string,\n item: StoreItem,\n indexMap: IndexMap,\n assignment: SlotAssignment,\n): StoreItem {\n const id = String(item.id);\n const keys: StoreItem = {\n ...primaryKey(model, id),\n [TYPE_PK]: model,\n // Deliberately *not* length-prefixed: this sort key exists to order a\n // model's rows by creation time, and a byte-length prefix would sort\n // lexicographically by length instead of chronologically. It is never used\n // for `begins_with` lookups, so it carries no collision risk.\n [TYPE_SK]: sortKey(\n `${String(item.createdAt ?? \"\")}#${id}`,\n \"type sort key\",\n ),\n };\n\n for (const pattern of indexMap[model] ?? []) {\n const pkValues = pattern.pk.map((f) => item[f]);\n if (pkValues.some((v) => v == null)) continue; // index doesn't apply to this row\n const slot = assignment.slots[model]?.[pattern.index];\n if (!slot) continue;\n keys[gsiPk(slot)] = lookupPkValue(model, pattern.index, pkValues);\n const skValues = (pattern.sk ?? []).map((f) => item[f]);\n keys[gsiSk(slot)] = sortKey(\n skValues.some((v) => v == null)\n ? component(id)\n : `${joinValues(skValues)}#${component(id)}`,\n \"lookup sort key\",\n );\n }\n\n return keys;\n}\n\n/** A resolved lookup query against one GSI slot. */\nexport interface LookupQuery {\n indexName: string;\n pkAttr: string;\n pkValue: string;\n skAttr: string;\n /** When set, narrow with `begins_with(skAttr, skPrefix)`. */\n skPrefix?: string;\n}\n\n/**\n * Translate a logical index query into a concrete GSI query. Uses the\n * partition key always, and a `begins_with` sort-key prefix when the caller\n * supplied any leading sort-key field values.\n */\nexport function encodeLookupQuery(\n model: string,\n index: string,\n key: StoreItem,\n indexMap: IndexMap,\n assignment: SlotAssignment,\n): LookupQuery {\n const slot = assignment.slots[model]?.[index];\n if (!slot) {\n throw new DynamoDBAdapterError(\n `No physical slot for index \"${index}\" on model \"${model}\". ` +\n `Ensure the index is present in the adapter's index map.`,\n );\n }\n const pattern = (indexMap[model] ?? []).find((p) => p.index === index)!;\n const pkValues = pattern.pk.map((f) => key[f]);\n const skPresent = (pattern.sk ?? [])\n .map((f) => key[f])\n .filter((v) => v != null);\n\n return {\n indexName: gsiName(slot),\n pkAttr: gsiPk(slot),\n pkValue: lookupPkValue(model, index, pkValues),\n skAttr: gsiSk(slot),\n skPrefix: skPresent.length > 0 ? `${joinValues(skPresent)}#` : undefined,\n };\n}\n\n/** Remove all reserved (`__ba_`) attributes, yielding clean Better Auth data. */\nexport function stripReserved(\n item: StoreItem | undefined | null,\n): StoreItem | null {\n if (!item) return null;\n const clean: StoreItem = {};\n for (const [k, v] of Object.entries(item)) {\n if (!k.startsWith(RESERVED_PREFIX)) clean[k] = v;\n }\n return clean;\n}\n","import type {\n CreateTableCommandInput,\n DynamoDBClient,\n} from \"@aws-sdk/client-dynamodb\";\nimport {\n CreateTableCommand,\n ResourceInUseException,\n UpdateTimeToLiveCommand,\n waitUntilTableExists,\n} from \"@aws-sdk/client-dynamodb\";\n\nimport {\n gsiName,\n gsiPk,\n gsiSk,\n PK,\n SK,\n TYPE_INDEX,\n TYPE_PK,\n TYPE_SK,\n} from \"./key-codec\";\n\n/** Build the CreateTable input for the built-in store's single-table layout. */\nexport function buildTableDefinition(\n tableName: string,\n lookupSlots: number,\n): CreateTableCommandInput {\n const attr = (name: string) => ({\n AttributeName: name,\n AttributeType: \"S\" as const,\n });\n\n const attributeDefinitions = [\n attr(PK),\n attr(SK),\n attr(TYPE_PK),\n attr(TYPE_SK),\n ];\n const globalSecondaryIndexes = [\n {\n IndexName: TYPE_INDEX,\n KeySchema: [\n { AttributeName: TYPE_PK, KeyType: \"HASH\" as const },\n { AttributeName: TYPE_SK, KeyType: \"RANGE\" as const },\n ],\n Projection: { ProjectionType: \"ALL\" as const },\n },\n ];\n\n for (let slot = 1; slot <= lookupSlots; slot++) {\n attributeDefinitions.push(attr(gsiPk(slot)), attr(gsiSk(slot)));\n globalSecondaryIndexes.push({\n IndexName: gsiName(slot),\n KeySchema: [\n { AttributeName: gsiPk(slot), KeyType: \"HASH\" as const },\n { AttributeName: gsiSk(slot), KeyType: \"RANGE\" as const },\n ],\n Projection: { ProjectionType: \"ALL\" as const },\n });\n }\n\n return {\n TableName: tableName,\n BillingMode: \"PAY_PER_REQUEST\",\n KeySchema: [\n { AttributeName: PK, KeyType: \"HASH\" },\n { AttributeName: SK, KeyType: \"RANGE\" },\n ],\n AttributeDefinitions: attributeDefinitions,\n GlobalSecondaryIndexes: globalSecondaryIndexes,\n };\n}\n\n/**\n * Create the table if it does not already exist and wait until it is active.\n * Idempotent — safe to call at startup or in test `beforeAll` hooks.\n */\nexport async function ensureSchema(opts: {\n client: DynamoDBClient;\n tableName: string;\n lookupSlots: number;\n /**\n * Enable DynamoDB TTL on this attribute. Pass the same value the adapter is\n * configured with (`ttl.attributeName`, default `__ba_ttl`) — the adapter\n * writes the attribute, but only the table setting makes AWS act on it.\n */\n ttlAttribute?: string;\n}): Promise<void> {\n const input = buildTableDefinition(opts.tableName, opts.lookupSlots);\n try {\n await opts.client.send(new CreateTableCommand(input));\n } catch (error) {\n if (!(error instanceof ResourceInUseException)) throw error; // already exists\n }\n await waitUntilTableExists(\n { client: opts.client, maxWaitTime: 60 },\n { TableName: opts.tableName },\n );\n\n if (!opts.ttlAttribute) return;\n try {\n await opts.client.send(\n new UpdateTimeToLiveCommand({\n TableName: opts.tableName,\n TimeToLiveSpecification: {\n Enabled: true,\n AttributeName: opts.ttlAttribute,\n },\n }),\n );\n } catch (error) {\n // Re-enabling TTL on the same attribute is a no-op AWS reports as an\n // error; anything else is a real provisioning failure.\n if (!isAlreadyEnabled(error)) throw error;\n }\n}\n\nconst isAlreadyEnabled = (error: unknown): boolean =>\n typeof error === \"object\" &&\n error !== null &&\n \"name\" in error &&\n (error as { name?: unknown }).name === \"ValidationException\" &&\n typeof (error as unknown as { message?: unknown }).message === \"string\" &&\n (error as unknown as { message: string }).message.includes(\"already\");\n\n/**\n * Better Auth CLI `generate` hook: emit a portable CloudFormation template for\n * the built-in store's table. Table name and lookup-GSI count are configurable\n * (unlike a hardcoded layout), so the generated stack matches the adapter's\n * actual access patterns.\n */\nexport function generateSchemaFile(opts: {\n tableName: string;\n lookupSlots: number;\n file?: string;\n /** When set, the template enables DynamoDB TTL on this attribute. */\n ttlAttribute?: string;\n}): { code: string; path: string; overwrite: boolean } {\n const table = {\n ...buildTableDefinition(opts.tableName, opts.lookupSlots),\n ...(opts.ttlAttribute\n ? {\n TimeToLiveSpecification: {\n Enabled: true,\n AttributeName: opts.ttlAttribute,\n },\n }\n : {}),\n };\n const template = {\n AWSTemplateFormatVersion: \"2010-09-09\",\n Resources: {\n BetterAuthTable: { Type: \"AWS::DynamoDB::Table\", Properties: table },\n },\n };\n const code = `/**\n * Auto-generated DynamoDB CloudFormation template for @datar-platform/better-auth-dynamodb.\n * Table: ${opts.tableName} — ${opts.lookupSlots} lookup GSI(s) + 1 byType GSI.\n * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html\n */\nexport const dynamoDBSchema = ${JSON.stringify(template, null, 2)} as const;\n\nexport default dynamoDBSchema;\n`;\n return {\n code,\n path: opts.file ?? \"dynamodb-cloudformation.ts\",\n overwrite: true,\n };\n}\n"],"mappings":"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","names":["DEFAULT_MAX_PAGES"]}
|
|
1
|
+
{"version":3,"sources":["../src/adapter.ts","../src/errors.ts","../src/pagination.ts","../src/planner.ts","../src/stores/default/derive-index-map.ts","../src/stores/default/single-table-store.ts","../src/stores/default/key-codec.ts","../src/stores/default/schema.ts","../src/stores/default/migrate.ts"],"sourcesContent":["import type { CleanedWhere } from \"@better-auth/core/db/adapter\";\nimport type { BetterAuthOptions } from \"better-auth\";\nimport type { AdapterFactory } from \"better-auth/adapters\";\nimport { createAdapterFactory } from \"better-auth/adapters\";\n\nimport { UnsupportedQueryError } from \"./errors\";\nimport type { QueryPlan } from \"./planner\";\nimport type { DynamoAdapterConfig, DynamoStore, StoreItem } from \"./types\";\nimport {\n applySort,\n applyWindow,\n DEFAULT_MAX_PAGES,\n drainPages,\n matchesResidual,\n} from \"./pagination\";\nimport { planQuery } from \"./planner\";\nimport { deriveIndexMap } from \"./stores/default/derive-index-map\";\nimport { createSingleTableStore } from \"./stores/default/single-table-store\";\n\n/**\n * A generic DynamoDB adapter for Better Auth.\n *\n * The factory handles field mapping, JSON/date/boolean coercion, ID generation\n * and `where`-clause normalization; this adapter only implements the raw DB\n * operations. Those operations are expressed against a pluggable\n * {@link DynamoStore} and a declarative access-pattern map, so the same core\n * drives both the built-in single-table store and any custom store.\n *\n * @example\n * ```ts\n * betterAuth({ database: dynamoAdapter({ tableName: \"auth\", region: \"us-east-1\" }) });\n * ```\n */\nexport const dynamoAdapter = (\n config: DynamoAdapterConfig = {},\n): AdapterFactory<BetterAuthOptions> => {\n return createAdapterFactory({\n config: {\n adapterId: \"dynamodb\",\n adapterName: \"DynamoDB\",\n // DynamoDB keys are strings; let Better Auth generate string ids.\n supportsNumericIds: false,\n // We serialize JSON ourselves via the SDK document client.\n supportsJSON: true,\n // No native Date type — stored as ISO strings (see transforms below).\n supportsDates: false,\n supportsBooleans: true,\n usePlural: false,\n debugLogs: config.debugLogs,\n\n // Dates <-> ISO strings, since supportsDates is false.\n customTransformInput: ({ data, fieldAttributes }) =>\n fieldAttributes.type === \"date\" && data instanceof Date\n ? data.toISOString()\n : data,\n customTransformOutput: ({ data, fieldAttributes }) =>\n fieldAttributes.type === \"date\" && typeof data === \"string\"\n ? new Date(data)\n : data,\n },\n\n adapter: ({ schema, debugLog, getDefaultModelName, getFieldName }) => {\n const indexMap = config.indexMap ?? deriveIndexMap(schema);\n const maxPages = config.maxPages ?? DEFAULT_MAX_PAGES;\n const store: DynamoStore =\n config.store ??\n createSingleTableStore({\n tableName: config.tableName,\n region: config.region,\n endpoint: config.endpoint,\n documentClient: config.documentClient,\n atomicUniqueness: config.atomicUniqueness,\n maxPages,\n pageSize: config.pageSize,\n ttl: config.ttl,\n indexMap,\n });\n\n /** Resolve every row matching `where`, using the best access path. */\n const queryAll = async (\n model: string,\n where?: CleanedWhere[],\n ): Promise<StoreItem[]> => {\n const clauses = where ?? [];\n const plan = planQuery(model, clauses, indexMap);\n debugLog(\"queryAll\", { model, plan: plan.kind });\n\n let candidates: StoreItem[];\n if (plan.kind === \"byId\") {\n const one = await store.getById(model, plan.id);\n candidates = one ? [one] : [];\n } else if (plan.kind === \"byIds\") {\n const found = await mapBatched(plan.ids, (id) =>\n store.getById(model, id),\n );\n candidates = found.filter((item): item is StoreItem => item !== null);\n } else if (plan.kind === \"index\") {\n candidates = await drainPages(\n (cursor) =>\n store.queryIndex({\n model,\n index: plan.index,\n key: plan.key,\n cursor,\n }),\n maxPages,\n `index query on \"${model}\"`,\n );\n } else {\n if (!config.unsafeAllowScan) {\n throw new UnsupportedQueryError(\n `better-auth-dynamodb: no index can serve this query on ` +\n `\"${model}\" (${describeWhere(clauses)}), so it would have to ` +\n `read every row of the model and filter in memory. Add the ` +\n `field to the index map — or to the Better Auth schema as ` +\n `\\`unique\\`, \\`references\\`, or \\`index: true\\` — or set ` +\n `\\`unsafeAllowScan: true\\` to accept the cost.`,\n );\n }\n candidates = await drainPages(\n (cursor) => store.listByType({ model, cursor }),\n maxPages,\n `model scan of \"${model}\"`,\n );\n }\n return candidates.filter((item) =>\n matchesResidual(item, plan.residual),\n );\n };\n\n /** Find the primary id for a `where`, cheaply if it names `id` directly. */\n const resolveId = async (\n model: string,\n where: CleanedWhere[],\n ): Promise<string | null> => {\n const plan: QueryPlan = planQuery(model, where, indexMap);\n // The shortcut is only sound when `id` was the *whole* predicate.\n // `where: [id = x, status = \"active\"]` must still check the status, or\n // a guarded update would fire against a row that does not qualify.\n if (plan.kind === \"byId\" && plan.residual.length === 0) return plan.id;\n const [first] = await queryAll(model, where);\n return first ? String(first.id) : null;\n };\n\n /**\n * Narrow rows to the requested fields. Better Auth asks for projection at\n * the API surface; DynamoDB could do it server-side, but only by giving\n * up the reserved key attributes the store needs, so it happens here.\n */\n const project = (\n model: string,\n items: StoreItem[],\n select?: string[],\n ): StoreItem[] => {\n if (!select?.length) return items;\n // `select` arrives in Better Auth's field names; rows are stored under\n // whatever `fieldName` the schema maps them to, so the names have to be\n // translated before picking or every field comes back undefined.\n const stored = select.map((field) => getFieldName({ model, field }));\n return items.map((item) => {\n const picked: StoreItem = {};\n for (const field of stored) {\n if (field in item) picked[field] = item[field];\n }\n return picked;\n });\n };\n\n return {\n async create<T extends Record<string, any>>({\n model,\n data,\n }: {\n model: string;\n data: T;\n }) {\n const m = getDefaultModelName(model);\n const created = await store.put(m, data);\n return created as T;\n },\n\n async update<T>({\n model,\n where,\n update,\n }: {\n model: string;\n where: CleanedWhere[];\n update: T;\n }) {\n const m = getDefaultModelName(model);\n const id = await resolveId(m, where);\n if (!id) return null;\n const updated = await store.update(m, id, update as StoreItem);\n return updated as T | null;\n },\n\n async updateMany({\n model,\n where,\n update,\n }: {\n model: string;\n where: CleanedWhere[];\n update: Record<string, any>;\n }) {\n const m = getDefaultModelName(model);\n const items = await queryAll(m, where);\n await runBatched(items, (item) =>\n store.update(m, String(item.id), update),\n );\n return items.length;\n },\n\n async delete({\n model,\n where,\n }: {\n model: string;\n where: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const id = await resolveId(m, where);\n if (id) await store.deleteById(m, id);\n },\n\n async deleteMany({\n model,\n where,\n }: {\n model: string;\n where: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const items = await queryAll(m, where);\n await runBatched(items, (item) =>\n store.deleteById(m, String(item.id)),\n );\n return items.length;\n },\n\n async findOne<T>({\n model,\n where,\n select,\n }: {\n model: string;\n where: CleanedWhere[];\n select?: string[];\n }) {\n const m = getDefaultModelName(model);\n const [first] = project(m, await queryAll(m, where), select);\n return (first ?? null) as T | null;\n },\n\n async findMany<T>({\n model,\n where,\n limit,\n sortBy,\n offset,\n select,\n }: {\n model: string;\n where?: CleanedWhere[];\n limit: number;\n sortBy?: { field: string; direction: \"asc\" | \"desc\" };\n offset?: number;\n select?: string[];\n }) {\n const m = getDefaultModelName(model);\n // Projection runs last: sorting by a field the caller did not select\n // must still work.\n const items = project(\n m,\n applyWindow(\n applySort(await queryAll(m, where), sortBy),\n offset,\n limit,\n ),\n select,\n );\n return items as T[];\n },\n\n async count({\n model,\n where,\n }: {\n model: string;\n where?: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const clauses = where ?? [];\n const plan = planQuery(m, clauses, indexMap);\n // Fast path: no residual predicates and the store can count natively.\n // Only for the two plans a native count actually describes — counting\n // a `byId`/`byIds` plan this way would count the whole model instead.\n if (plan.residual.length === 0 && store.count) {\n if (plan.kind === \"index\") {\n const fast = await store.count({\n model: m,\n index: plan.index,\n key: plan.key,\n });\n if (fast != null) return fast;\n } else if (plan.kind === \"listByType\") {\n const fast = await store.count({ model: m });\n if (fast != null) return fast;\n }\n }\n return (await queryAll(m, clauses)).length;\n },\n\n async consumeOne<T>({\n model,\n where,\n }: {\n model: string;\n where: CleanedWhere[];\n }) {\n const m = getDefaultModelName(model);\n const id = await resolveId(m, where);\n if (!id) return null;\n if (store.consumeOne)\n return (await store.consumeOne(m, id)) as T | null;\n // Fallback for stores without a native atomic delete-and-return: not\n // safe under true concurrency, but correct for the common single\n // in-flight verification case.\n const item = await store.getById(m, id);\n if (!item) return null;\n await store.deleteById(m, id);\n return item as T;\n },\n\n async incrementOne<T>({\n model,\n where,\n increment,\n set,\n }: {\n model: string;\n where: CleanedWhere[];\n increment: Record<string, number>;\n set?: Record<string, any>;\n }) {\n const m = getDefaultModelName(model);\n const id = await resolveId(m, where);\n if (!id) return null;\n if (store.incrementOne) {\n return (await store.incrementOne(m, id, {\n increment,\n set,\n })) as T | null;\n }\n // Fallback for stores without a native atomic add/set: not safe\n // under true concurrency, but correct for the common single\n // in-flight update case.\n const current = await store.getById(m, id);\n if (!current) return null;\n const patch: StoreItem = { ...set };\n for (const [field, delta] of Object.entries(increment)) {\n patch[field] = (Number(current[field]) || 0) + delta;\n }\n return (await store.update(m, id, patch)) as T | null;\n },\n\n ...(store.createSchema\n ? {\n createSchema: (props: { file?: string; tables: unknown }) =>\n store.createSchema!(props),\n }\n : {}),\n };\n },\n });\n};\n\n/** Render a `where` for an error message, without leaking the values. */\nfunction describeWhere(where: CleanedWhere[]): string {\n if (where.length === 0) return \"no where clause\";\n return where.map((w) => `${w.field} ${w.operator}`).join(\", \");\n}\n\n/** Map items through an async op in small concurrent chunks, keeping order. */\nasync function mapBatched<T, R>(\n items: T[],\n op: (item: T) => Promise<R>,\n chunkSize = 10,\n): Promise<R[]> {\n const out: R[] = [];\n for (let i = 0; i < items.length; i += chunkSize) {\n out.push(...(await Promise.all(items.slice(i, i + chunkSize).map(op))));\n }\n return out;\n}\n\n/** Run an async op over items in small concurrent chunks to avoid throttling. */\nasync function runBatched<T>(\n items: T[],\n op: (item: T) => Promise<unknown>,\n chunkSize = 10,\n): Promise<void> {\n for (let i = 0; i < items.length; i += chunkSize) {\n await Promise.all(items.slice(i, i + chunkSize).map(op));\n }\n}\n","/**\n * Error types raised by this adapter.\n *\n * Everything thrown from the adapter core or the built-in store derives from\n * {@link DynamoDBAdapterError}, so a consumer can distinguish \"the adapter\n * refused/failed\" from a raw AWS SDK error escaping the seam.\n */\nexport class DynamoDBAdapterError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"DynamoDBAdapterError\";\n }\n}\n\n/**\n * A write lost a race against a uniqueness constraint — another row already\n * holds the value for a `unique` field (e.g. `user.email`, `organization.slug`).\n */\nexport class UniqueConstraintError extends DynamoDBAdapterError {\n constructor(\n readonly model: string,\n readonly fields: string[],\n options?: ErrorOptions,\n ) {\n super(\n `better-auth-dynamodb: unique constraint violation on \"${model}\" ` +\n `(duplicate value for ${fields.join(\", \")})`,\n options,\n );\n this.name = \"UniqueConstraintError\";\n }\n}\n\n/**\n * The requested access pattern cannot be served without a table/model scan, and\n * scans have not been explicitly enabled.\n */\nexport class UnsupportedQueryError extends DynamoDBAdapterError {\n constructor(message: string) {\n super(message);\n this.name = \"UnsupportedQueryError\";\n }\n}\n\n/**\n * A guarded write was rejected because the row changed between the adapter's\n * read and its write. Better Auth's own retry/null semantics decide what\n * happens next; this exists so the cause is legible rather than an opaque\n * `TransactionCanceledException`.\n */\nexport class OptimisticLockError extends DynamoDBAdapterError {\n constructor(model: string, id: string, options?: ErrorOptions) {\n super(\n `better-auth-dynamodb: concurrent modification of \"${model}\" row \"${id}\" ` +\n `— the record changed between read and write`,\n options,\n );\n this.name = \"OptimisticLockError\";\n }\n}\n\nconst named = (error: unknown, name: string): boolean =>\n typeof error === \"object\" &&\n error !== null &&\n \"name\" in error &&\n (error as { name?: unknown }).name === name;\n\n/** True for a bare `ConditionalCheckFailedException` from a non-transactional write. */\nexport const isConditionalCheckFailed = (error: unknown): boolean =>\n named(error, \"ConditionalCheckFailedException\");\n\n/** True for any cancelled `TransactWriteItems`, whatever the reason. */\nexport const isTransactionCanceled = (error: unknown): boolean =>\n named(error, \"TransactionCanceledException\");\n\n/**\n * The per-action cancellation codes AWS attaches to a cancelled transaction\n * (e.g. `ConditionalCheckFailed`, `TransactionConflict`, `ThrottlingError`,\n * `ValidationError`, or `None` for actions that were fine).\n */\nexport function transactionCancellationCodes(error: unknown): string[] {\n if (\n typeof error !== \"object\" ||\n error === null ||\n !(\"CancellationReasons\" in error)\n ) {\n return [];\n }\n const reasons = (error as { CancellationReasons?: unknown })\n .CancellationReasons;\n if (!Array.isArray(reasons)) return [];\n return reasons\n .map((reason) =>\n typeof reason === \"object\" && reason !== null && \"Code\" in reason\n ? (reason as { Code?: unknown }).Code\n : undefined,\n )\n .filter((code): code is string => typeof code === \"string\");\n}\n\n/**\n * True only when a transaction was cancelled *because a condition failed* —\n * not because of throttling, a transaction conflict, or a validation error.\n *\n * This distinction matters: reporting a throttled write as \"email already\n * taken\" is a user-visible lie, and it is the failure mode of any code that\n * treats `TransactionCanceledException` as a uniqueness violation wholesale.\n */\nexport const isConditionalTransactionCanceled = (error: unknown): boolean =>\n isTransactionCanceled(error) &&\n transactionCancellationCodes(error).includes(\"ConditionalCheckFailed\");\n\n/**\n * True when a transaction was cancelled because another transaction was\n * touching the same item. Transient contention, not a decision about the data —\n * so the right response is to try again, not to give up.\n */\nexport const isTransactionConflict = (error: unknown): boolean =>\n isTransactionCanceled(error) &&\n transactionCancellationCodes(error).includes(\"TransactionConflict\");\n","import type { CleanedWhere } from \"@better-auth/core/db/adapter\";\n\nimport { DynamoDBAdapterError } from \"./errors\";\nimport type { QueryPage, StoreItem } from \"./types\";\n\n/** Default cap on pages drained for one logical query. */\nexport const DEFAULT_MAX_PAGES = 25;\n\n/**\n * Drain every page of a paginated store query into a single array.\n *\n * DynamoDB paginates at 1 MB / index-page boundaries regardless of match count,\n * so a single `.query()` can silently return a partial result. Draining here —\n * rather than trusting one page — is what makes `findMany`/`count` correct\n * (and removes the old adapter's implicit 10 000-row cap).\n *\n * Draining is bounded by `maxPages`. Hitting the cap throws: an auth query that\n * silently returns some of its rows is a correctness bug wearing a success\n * response.\n */\nexport async function drainPages(\n fetch: (cursor: unknown) => Promise<QueryPage>,\n maxPages = DEFAULT_MAX_PAGES,\n label = \"query\",\n): Promise<StoreItem[]> {\n const out: StoreItem[] = [];\n let cursor: unknown = undefined;\n let pages = 0;\n do {\n const page = await fetch(cursor);\n out.push(...page.items);\n cursor = page.cursor;\n if (++pages >= maxPages && cursor) {\n throw new DynamoDBAdapterError(\n `better-auth-dynamodb: ${label} exceeded maxPages (${maxPages}) with ` +\n `more pages remaining. Raise maxPages or narrow the query — ` +\n `returning a partial result here would silently drop rows.`,\n );\n }\n } while (cursor);\n return out;\n}\n\nfunction matchOne(item: StoreItem, w: CleanedWhere): boolean {\n // `mode: \"insensitive\"` asks for case-insensitive comparison. It only has\n // meaning between two strings; anything else keeps exact semantics.\n const fold = (v: unknown): unknown =>\n w.mode === \"insensitive\" && typeof v === \"string\" ? v.toLowerCase() : v;\n\n const actual = fold(item[w.field]) as any;\n const expected = Array.isArray(w.value)\n ? (w.value as unknown[]).map(fold)\n : fold(w.value);\n switch (w.operator) {\n case \"eq\":\n return actual === expected;\n case \"ne\":\n return actual !== expected;\n case \"lt\":\n return actual < (expected as any);\n case \"lte\":\n return actual <= (expected as any);\n case \"gt\":\n return actual > (expected as any);\n case \"gte\":\n return actual >= (expected as any);\n case \"in\":\n return Array.isArray(expected) && expected.includes(actual);\n case \"not_in\":\n return Array.isArray(expected) && !expected.includes(actual);\n case \"contains\":\n return String(actual).includes(String(expected));\n case \"starts_with\":\n return String(actual).startsWith(String(expected));\n case \"ends_with\":\n return String(actual).endsWith(String(expected));\n default:\n return actual === expected;\n }\n}\n\n/**\n * Apply the residual `where` clauses in memory, honoring Better Auth's\n * connector semantics: all `AND` clauses must match, and — if any `OR` clauses\n * are present — at least one of them must also match. An empty residual passes.\n */\nexport function matchesResidual(\n item: StoreItem,\n residual: CleanedWhere[],\n): boolean {\n const andOk = residual\n .filter((w) => w.connector !== \"OR\")\n .every((w) => matchOne(item, w));\n if (!andOk) return false;\n\n const or = residual.filter((w) => w.connector === \"OR\");\n return or.length === 0 || or.some((w) => matchOne(item, w));\n}\n\n/** Stable in-memory sort matching Better Auth's `sortBy` contract. */\nexport function applySort(\n items: StoreItem[],\n sortBy?: { field: string; direction: \"asc\" | \"desc\" },\n): StoreItem[] {\n if (!sortBy) return items;\n const { field, direction } = sortBy;\n const dir = direction === \"asc\" ? 1 : -1;\n return [...items].sort((a, b) => {\n if (a[field] < b[field]) return -dir;\n if (a[field] > b[field]) return dir;\n return 0;\n });\n}\n\n/** Apply `offset`/`limit` (a `limit` of 0 or undefined means \"no cap\"). */\nexport function applyWindow(\n items: StoreItem[],\n offset?: number,\n limit?: number,\n): StoreItem[] {\n let out = items;\n if (offset && offset > 0) out = out.slice(offset);\n if (limit && limit > 0) out = out.slice(0, limit);\n return out;\n}\n","import type { CleanedWhere } from \"@better-auth/core/db/adapter\";\n\nimport type { IndexMap } from \"./index-map\";\nimport type { StoreItem } from \"./types\";\n\n/**\n * A resolved query plan: how the adapter will fetch candidate rows for a\n * `where` clause before applying any residual (in-memory) filtering.\n *\n * - `byId` — a direct primary-key get (the cheapest path).\n * - `byIds` — a bounded set of primary-key gets (`id in [...]`).\n * - `index` — a logical index query with a resolved partition/sort key.\n * - `listByType`— no index matched; list all rows of the model and filter.\n *\n * `residual` holds the `where` clauses NOT satisfied by the chosen access path;\n * they are applied in memory by {@link matchesResidual}.\n */\nexport type QueryPlan =\n | { kind: \"byId\"; id: string; residual: CleanedWhere[] }\n | { kind: \"byIds\"; ids: string[]; residual: CleanedWhere[] }\n | { kind: \"index\"; index: string; key: StoreItem; residual: CleanedWhere[] }\n | { kind: \"listByType\"; residual: CleanedWhere[] };\n\n/**\n * A clause usable for index selection: `eq`, `AND`-connected, case-sensitive.\n *\n * `mode: \"insensitive\"` is deliberately excluded. DynamoDB keys are compared\n * byte-for-byte, so serving a case-insensitive equality from an index would\n * silently miss every row whose casing differs from the query. Those clauses\n * stay residual and are matched in memory instead.\n */\nfunction isKeyable(w: CleanedWhere): boolean {\n return (\n w.operator === \"eq\" &&\n w.connector === \"AND\" &&\n w.value != null &&\n w.mode !== \"insensitive\"\n );\n}\n\n/**\n * Choose the most selective access path for `where` against a model's declared\n * access patterns. Falls back to `listByType` when nothing matches.\n *\n * Index selection only ever consumes `eq`/`AND` clauses; everything else (and\n * any `eq` clause not part of the chosen key) becomes residual.\n */\nexport function planQuery(\n model: string,\n where: CleanedWhere[],\n indexMap: IndexMap,\n): QueryPlan {\n const eqByField = new Map<string, CleanedWhere>();\n for (const w of where) {\n if (isKeyable(w) && !eqByField.has(w.field)) eqByField.set(w.field, w);\n }\n\n // Cheapest: a direct id lookup. Everything else filters in memory.\n const idClause = eqByField.get(\"id\");\n if (idClause) {\n return {\n kind: \"byId\",\n id: String(idClause.value),\n residual: where.filter((w) => w !== idClause),\n };\n }\n\n // `id in [...]` is a bounded set of primary-key gets, not a scan. Better\n // Auth uses it to batch-load rows it already has the ids for (loading an\n // organization's members, for instance), and DynamoDB serves it directly.\n const idIn = where.find(\n (w) =>\n w.field === \"id\" &&\n w.operator === \"in\" &&\n w.connector === \"AND\" &&\n Array.isArray(w.value),\n );\n if (idIn) {\n return {\n kind: \"byIds\",\n ids: (idIn.value as unknown[]).map(String),\n residual: where.filter((w) => w !== idIn),\n };\n }\n\n const patterns = indexMap[model] ?? [];\n for (const pattern of patterns) {\n if (!pattern.pk.every((f) => eqByField.has(f))) continue;\n\n const key: StoreItem = {};\n const consumed = new Set<CleanedWhere>();\n for (const f of pattern.pk) {\n const clause = eqByField.get(f)!;\n key[f] = clause.value;\n consumed.add(clause);\n }\n // Attach as many leading sort-key fields as are present (prefix match).\n for (const f of pattern.sk ?? []) {\n const clause = eqByField.get(f);\n if (!clause) break;\n key[f] = clause.value;\n consumed.add(clause);\n }\n\n return {\n kind: \"index\",\n index: pattern.index,\n key,\n residual: where.filter((w) => !consumed.has(w)),\n };\n }\n\n return { kind: \"listByType\", residual: where };\n}\n","import type { BetterAuthDBSchema } from \"@better-auth/core/db\";\n\nimport type { AccessPattern, IndexMap } from \"../../index-map\";\n\n/** Logical index name for a single-field lookup (e.g. `email` -> `by_email`). */\nexport function lookupIndexName(field: string): string {\n return `by_${field}`;\n}\n\n/**\n * Derive a logical access-pattern map from a Better Auth schema.\n *\n * A single-field lookup index is created for every field that is either:\n * - `unique` (e.g. `user.email`, `session.token`) — direct lookups, or\n * - a `references` foreign key (e.g. `account.userId`) — by-parent lookups, or\n * - explicitly flagged `index: true`.\n *\n * This is what lets the built-in store serve any Better Auth model or plugin\n * (two-factor, passkey, api-key, …) out of the box: whatever fields the schema\n * marks as looked-up get an index, with no hand-maintained per-model table.\n */\nexport function deriveIndexMap(schema: BetterAuthDBSchema): IndexMap {\n const map: IndexMap = {};\n\n for (const [model, table] of Object.entries(schema)) {\n const patterns: AccessPattern[] = [];\n const seen = new Set<string>();\n\n const add = (field: string, unique = false) => {\n if (field === \"id\" || seen.has(field)) return;\n seen.add(field);\n patterns.push({ index: lookupIndexName(field), pk: [field], unique });\n };\n\n // `unique` fields first (most selective), then foreign keys, then `index`.\n for (const [field, attr] of Object.entries(table.fields)) {\n if (attr.unique) add(field, true);\n }\n for (const [field, attr] of Object.entries(table.fields)) {\n if (attr.references) add(field);\n }\n for (const [field, attr] of Object.entries(table.fields)) {\n if (attr.index) add(field);\n }\n\n if (patterns.length > 0) map[model] = patterns;\n }\n\n return map;\n}\n","import { randomUUID } from \"node:crypto\";\n\nimport { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport {\n DeleteCommand,\n DynamoDBDocumentClient,\n GetCommand,\n PutCommand,\n QueryCommand,\n TransactWriteCommand,\n type TransactWriteCommandInput,\n UpdateCommand,\n} from \"@aws-sdk/lib-dynamodb\";\n\nimport {\n DynamoDBAdapterError,\n isConditionalCheckFailed,\n isConditionalTransactionCanceled,\n isTransactionConflict,\n OptimisticLockError,\n UniqueConstraintError,\n} from \"../../errors\";\nimport type { AccessPattern, IndexMap } from \"../../index-map\";\nimport type {\n DynamoStore,\n QueryPage,\n StoreItem,\n TtlOptions,\n} from \"../../types\";\nimport type { SlotAssignment } from \"./key-codec\";\nimport {\n assignSlots,\n DEFAULT_TTL_ATTRIBUTE,\n encodeKeys,\n encodeLookupQuery,\n PK,\n primaryKey,\n REVISION,\n stripReserved,\n TYPE_INDEX,\n TYPE_PK,\n uniqueMarkerKey,\n} from \"./key-codec\";\nimport { generateSchemaFile } from \"./schema\";\n\ntype TransactItem = NonNullable<\n TransactWriteCommandInput[\"TransactItems\"]\n>[number];\n\n/** DynamoDB's hard limit on actions in one `TransactWriteItems` call. */\nconst MAX_TRANSACT_ITEMS = 100;\n\n/** Attempts at a guarded read-modify-write before giving up on the race. */\nconst MAX_WRITE_ATTEMPTS = 3;\n\n/** Default cap on pages drained for one logical query. */\nconst DEFAULT_MAX_PAGES = 25;\n\nexport interface SingleTableStoreOptions {\n /** DynamoDB table name. Falls back to `DYNAMODB_TABLE_NAME`, then `\"better-auth\"`. */\n tableName?: string;\n /** AWS region (passed to the DynamoDB client). */\n region?: string;\n /** Endpoint override, e.g. DynamoDB Local or LocalStack (`http://localhost:4566`). */\n endpoint?: string;\n /** Resolved logical access-pattern map (derived or user-supplied). */\n indexMap: IndexMap;\n /**\n * Pre-built document client. Preferred in production, so your application\n * owns credentials, region, middleware, tracing, and marshalling.\n */\n documentClient?: DynamoDBDocumentClient;\n /**\n * Enforce `unique` schema fields with transactional marker rows. Default\n * `true`. Turning it off halves the write cost of creates and restores\n * Better Auth's application-layer check-then-insert, which can admit\n * duplicates under concurrency.\n */\n atomicUniqueness?: boolean;\n /** Max DynamoDB pages drained for one internal count. Default 25. */\n maxPages?: number;\n /** Optional per-request DynamoDB `Limit`. Does not change logical results. */\n pageSize?: number;\n /** Adapter-managed DynamoDB TTL. Omit (or `false`) to disable entirely. */\n ttl?: TtlOptions | false;\n}\n\nconst epochSeconds = (value: unknown): number | undefined => {\n const date =\n value instanceof Date\n ? value\n : typeof value === \"string\" || typeof value === \"number\"\n ? new Date(value)\n : undefined;\n if (!date || Number.isNaN(date.getTime())) return undefined;\n return Math.floor(date.getTime() / 1000);\n};\n\n/**\n * A zero-dependency (beyond the AWS SDK) DynamoDB store for Better Auth.\n *\n * Uses a single table with a `byType` GSI plus generic lookup GSIs. It owns all\n * physical key encoding via the key codec, so the generic adapter core drives it\n * through logical index names only. Works with any Better Auth model or plugin\n * whose looked-up fields are described by the (typically schema-derived) index\n * map.\n *\n * Every mutation is guarded: creates are conditional on the row not already\n * existing, updates and deletes carry an optimistic revision check, and\n * uniqueness markers move in the same transaction as the row they describe.\n */\nexport function createSingleTableStore(\n opts: SingleTableStoreOptions,\n): DynamoStore {\n const tableName =\n opts.tableName ?? process.env.DYNAMODB_TABLE_NAME ?? \"better-auth\";\n const doc =\n opts.documentClient ??\n DynamoDBDocumentClient.from(\n new DynamoDBClient({\n ...(opts.region ? { region: opts.region } : {}),\n ...(opts.endpoint ? { endpoint: opts.endpoint } : {}),\n }),\n { marshallOptions: { removeUndefinedValues: true } },\n );\n const indexMap = opts.indexMap;\n const assignment: SlotAssignment = assignSlots(indexMap);\n const atomicUniqueness = opts.atomicUniqueness ?? true;\n const maxPages = opts.maxPages ?? DEFAULT_MAX_PAGES;\n const ttl = opts.ttl === false ? undefined : opts.ttl;\n const ttlAttribute = ttl?.attributeName ?? DEFAULT_TTL_ATTRIBUTE;\n\n // ---------------------------------------------------------------- TTL ----\n\n const ttlFieldFor = (model: string): string | undefined =>\n ttl ? (ttl.fields?.[model] ?? ttl.defaultField) : undefined;\n\n const ttlAttributesFor = (model: string, item: StoreItem): StoreItem => {\n const field = ttlFieldFor(model);\n if (!field) return {};\n const seconds = epochSeconds(item[field]);\n return seconds === undefined ? {} : { [ttlAttribute]: seconds };\n };\n\n /**\n * DynamoDB reaps expired rows lazily — up to a couple of days late. Treating\n * the TTL attribute as logical expiry on read is what makes an expired\n * session unusable the moment it expires rather than whenever AWS gets round\n * to the delete.\n */\n const isExpired = (item: StoreItem | undefined | null): boolean => {\n if (!item || !ttl) return false;\n const seconds = item[ttlAttribute];\n return (\n typeof seconds === \"number\" && seconds <= Math.floor(Date.now() / 1000)\n );\n };\n\n const modelHasTtl = (model: string): boolean => Boolean(ttlFieldFor(model));\n\n // ----------------------------------------------------------- uniqueness --\n\n const uniquePatternsFor = (model: string): AccessPattern[] =>\n atomicUniqueness ? (indexMap[model] ?? []).filter((p) => p.unique) : [];\n\n const uniqueFieldNames = (model: string): string[] =>\n uniquePatternsFor(model).flatMap((p) => p.pk);\n\n const markerItem = (\n model: string,\n pattern: AccessPattern,\n values: unknown[],\n ownerId: string,\n source: StoreItem,\n ): StoreItem => ({\n ...uniqueMarkerKey(model, pattern.index, values),\n __ba_owner: ownerId,\n ...ttlAttributesFor(model, source),\n });\n\n const applicableValues = (\n pattern: AccessPattern,\n item: StoreItem,\n ): unknown[] | null => {\n const values = pattern.pk.map((f) => item[f]);\n return values.some((v) => v == null) ? null : values;\n };\n\n const uniqueMarkerPuts = (\n model: string,\n item: StoreItem,\n id: string,\n ): TransactItem[] =>\n uniquePatternsFor(model).flatMap((pattern) => {\n const values = applicableValues(pattern, item);\n if (!values) return [];\n return [\n {\n Put: {\n TableName: tableName,\n Item: markerItem(model, pattern, values, id, item),\n ConditionExpression: \"attribute_not_exists(#pk)\",\n ExpressionAttributeNames: { \"#pk\": PK },\n },\n },\n ];\n });\n\n const uniqueMarkerDeletes = (\n model: string,\n item: StoreItem | null,\n ): TransactItem[] => {\n if (!item) return [];\n return uniquePatternsFor(model).flatMap((pattern) => {\n const values = applicableValues(pattern, item);\n if (!values) return [];\n return [\n {\n Delete: {\n TableName: tableName,\n Key: uniqueMarkerKey(model, pattern.index, values),\n },\n },\n ];\n });\n };\n\n /**\n * Marker moves for an update. A pattern whose value did not change is\n * skipped: deleting and re-putting the same key inside one transaction is a\n * self-conflict DynamoDB rejects outright.\n */\n const uniqueMarkerDiff = (\n model: string,\n before: StoreItem,\n after: StoreItem,\n id: string,\n ): TransactItem[] => {\n const items: TransactItem[] = [];\n for (const pattern of uniquePatternsFor(model)) {\n const oldValues = applicableValues(pattern, before);\n const newValues = applicableValues(pattern, after);\n if (\n oldValues?.length === newValues?.length &&\n oldValues?.every((v, i) => v === newValues![i])\n ) {\n continue;\n }\n if (oldValues) {\n items.push({\n Delete: {\n TableName: tableName,\n Key: uniqueMarkerKey(model, pattern.index, oldValues),\n },\n });\n }\n if (newValues) {\n items.push({\n Put: {\n TableName: tableName,\n Item: markerItem(model, pattern, newValues, id, after),\n ConditionExpression: \"attribute_not_exists(#pk)\",\n ExpressionAttributeNames: { \"#pk\": PK },\n },\n });\n }\n }\n return items;\n };\n\n // ---------------------------------------------------------- transactions --\n\n const keyOf = (action: TransactItem): string => {\n const target =\n action.Put?.Item ?? action.Delete?.Key ?? action.Update?.Key ?? {};\n return `${String((target as StoreItem)[PK])}`;\n };\n\n /**\n * DynamoDB rejects a transaction that touches the same item twice, and caps\n * one at 100 actions. Both produce opaque server-side errors, so they are\n * checked here where the cause is still known.\n */\n const validateTransaction = (model: string, items: TransactItem[]): void => {\n if (items.length > MAX_TRANSACT_ITEMS) {\n throw new DynamoDBAdapterError(\n `better-auth-dynamodb: a single write to \"${model}\" would need ` +\n `${items.length} transaction actions, over DynamoDB's ` +\n `${MAX_TRANSACT_ITEMS}-action limit. Reduce the number of unique ` +\n `fields on this model.`,\n );\n }\n const seen = new Set<string>();\n for (const item of items) {\n const key = keyOf(item);\n if (seen.has(key)) {\n throw new DynamoDBAdapterError(\n `better-auth-dynamodb: internal error — two actions in one ` +\n `transaction target the same item on \"${model}\". Please report this.`,\n );\n }\n seen.add(key);\n }\n };\n\n const sendTransaction = async (\n model: string,\n items: TransactItem[],\n ): Promise<void> => {\n validateTransaction(model, items);\n await doc.send(\n new TransactWriteCommand({\n TransactItems: items,\n // Gives the SDK's internal retries of this one send a stable token.\n // Not cross-invocation idempotency.\n ClientRequestToken: randomUUID(),\n }),\n );\n };\n\n const asUniqueConflict = (model: string, err: unknown): never => {\n if (isConditionalTransactionCanceled(err)) {\n throw new UniqueConstraintError(model, uniqueFieldNames(model), {\n cause: err,\n });\n }\n throw err;\n };\n\n // ------------------------------------------------------ revision guards --\n\n /** Read the raw (reserved attributes intact) row — needed for its revision. */\n const readRaw = async (\n model: string,\n id: string,\n ): Promise<StoreItem | null> => {\n const res = await doc.send(\n new GetCommand({\n TableName: tableName,\n Key: primaryKey(model, id),\n ConsistentRead: true,\n }),\n );\n if (!res.Item || isExpired(res.Item)) return null;\n return res.Item as StoreItem;\n };\n\n /**\n * Condition that the row still carries the revision we read. Rows written\n * before revisions existed are matched on the revision being absent, so they\n * migrate in place on their first write rather than becoming unmutatable.\n */\n const revisionGuard = (existing: StoreItem) => {\n const revision = existing[REVISION];\n return revision === undefined\n ? {\n ConditionExpression:\n \"attribute_exists(#pk) AND attribute_not_exists(#rev)\",\n ExpressionAttributeNames: { \"#pk\": PK, \"#rev\": REVISION },\n }\n : {\n ConditionExpression: \"attribute_exists(#pk) AND #rev = :rev\",\n ExpressionAttributeNames: { \"#pk\": PK, \"#rev\": REVISION },\n ExpressionAttributeValues: { \":rev\": revision },\n };\n };\n\n /** The write was refused by a condition we set, rather than failing outright. */\n const rejectedByGuard = (err: unknown): boolean =>\n isConditionalCheckFailed(err) || isConditionalTransactionCanceled(err);\n\n // ---------------------------------------------------------------- reads --\n\n const query = async (\n input: Omit<\n ConstructorParameters<typeof QueryCommand>[0],\n \"TableName\" | \"ExclusiveStartKey\"\n >,\n cursor?: unknown,\n countOnly = false,\n ): Promise<{ items: StoreItem[]; count: number; cursor?: unknown }> => {\n const res = await doc.send(\n new QueryCommand({\n TableName: tableName,\n ExclusiveStartKey: cursor as Record<string, unknown> | undefined,\n ...(countOnly ? { Select: \"COUNT\" } : {}),\n ...(opts.pageSize ? { Limit: opts.pageSize } : {}),\n ...input,\n }),\n );\n return {\n items: countOnly\n ? []\n : (res.Items ?? [])\n .filter((item) => !isExpired(item as StoreItem))\n .map((i) => stripReserved(i)!),\n count: res.Count ?? 0,\n cursor: res.LastEvaluatedKey,\n };\n };\n\n // --------------------------------------------------------------- writes --\n\n const putEntity = (model: string, item: StoreItem): TransactItem => ({\n Put: {\n TableName: tableName,\n Item: {\n ...item,\n ...encodeKeys(model, item, indexMap, assignment),\n ...ttlAttributesFor(model, item),\n [REVISION]: randomUUID(),\n },\n ConditionExpression: \"attribute_not_exists(#pk)\",\n ExpressionAttributeNames: { \"#pk\": PK },\n },\n });\n\n /**\n * Guarded read-modify-write. `build` turns the current raw row into the\n * transaction that replaces it; a lost race re-reads and tries again rather\n * than surfacing a spurious failure for what is usually a benign interleave.\n */\n const guardedWrite = async <T>(\n model: string,\n id: string,\n build: (existing: StoreItem) => { items: TransactItem[]; result: T },\n onMissing: () => T,\n ): Promise<T> => {\n for (let attempt = 1; attempt <= MAX_WRITE_ATTEMPTS; attempt++) {\n const existing = await readRaw(model, id);\n if (!existing) return onMissing();\n const { items, result } = build(existing);\n try {\n await sendTransaction(model, items);\n return result;\n } catch (err) {\n // Two retryable shapes: our own guard refused the write because the row\n // moved, or DynamoDB hit contention on the item. Anything else is real.\n const guarded = rejectedByGuard(err);\n if (!guarded && !isTransactionConflict(err)) throw err;\n // A uniqueness marker collision is terminal; a stale revision is not.\n if (guarded && (await lostToUniqueness(model, id, existing))) {\n return asUniqueConflict(model, err);\n }\n if (attempt === MAX_WRITE_ATTEMPTS) {\n throw new OptimisticLockError(model, id, { cause: err });\n }\n }\n }\n /* c8 ignore next */\n throw new OptimisticLockError(model, id);\n };\n\n /**\n * Distinguish the two reasons a guarded write is cancelled: the row moved\n * under us (retryable), or a unique marker is held by somebody else\n * (terminal). DynamoDB reports both as `ConditionalCheckFailed`, so the row\n * is re-read to tell them apart.\n */\n const lostToUniqueness = async (\n model: string,\n id: string,\n previous: StoreItem,\n ): Promise<boolean> => {\n if (uniquePatternsFor(model).length === 0) return false;\n const current = await readRaw(model, id);\n // The revision is unchanged, so the row did not move — the marker did.\n return Boolean(current) && current![REVISION] === previous[REVISION];\n };\n\n return {\n async put(model, item) {\n const id = String(item.id);\n const entity = putEntity(model, item);\n const markers = uniqueMarkerPuts(model, item, id);\n\n try {\n if (markers.length === 0) {\n await doc.send(new PutCommand({ ...entity.Put! }));\n } else {\n await sendTransaction(model, [entity, ...markers]);\n }\n } catch (err) {\n if (isConditionalCheckFailed(err)) {\n throw new DynamoDBAdapterError(\n `better-auth-dynamodb: a \"${model}\" row with id \"${id}\" already exists.`,\n { cause: err },\n );\n }\n asUniqueConflict(model, err);\n }\n return item;\n },\n\n async getById(model, id) {\n const raw = await readRaw(model, id);\n return stripReserved(raw);\n },\n\n async update(model, id, patch) {\n return guardedWrite<StoreItem | null>(\n model,\n id,\n (existing) => {\n const clean = stripReserved(existing)!;\n const merged = { ...clean, ...patch };\n return {\n items: [\n {\n Put: {\n TableName: tableName,\n Item: {\n ...merged,\n ...encodeKeys(model, merged, indexMap, assignment),\n ...ttlAttributesFor(model, merged),\n [REVISION]: randomUUID(),\n },\n ...revisionGuard(existing),\n },\n },\n ...uniqueMarkerDiff(model, clean, merged, id),\n ],\n result: merged,\n };\n },\n () => null,\n );\n },\n\n async deleteById(model, id) {\n await guardedWrite<void>(\n model,\n id,\n (existing) => ({\n items: [\n {\n Delete: {\n TableName: tableName,\n Key: primaryKey(model, id),\n ...revisionGuard(existing),\n },\n },\n ...uniqueMarkerDeletes(model, stripReserved(existing)),\n ],\n result: undefined,\n }),\n () => undefined,\n );\n },\n\n async consumeOne(model, id) {\n // With no markers to move, a single conditional delete is already atomic\n // and returns what it removed — no read needed.\n if (uniquePatternsFor(model).length === 0) {\n const res = await doc.send(\n new DeleteCommand({\n TableName: tableName,\n Key: primaryKey(model, id),\n ReturnValues: \"ALL_OLD\",\n }),\n );\n const item = res.Attributes as StoreItem | undefined;\n return isExpired(item) ? null : stripReserved(item);\n }\n\n // Otherwise the revision guard is what makes exactly one caller win: the\n // loser's condition fails against an already-deleted row.\n const existing = await readRaw(model, id);\n if (!existing) return null;\n const clean = stripReserved(existing)!;\n try {\n await sendTransaction(model, [\n {\n Delete: {\n TableName: tableName,\n Key: primaryKey(model, id),\n ...revisionGuard(existing),\n },\n },\n ...uniqueMarkerDeletes(model, clean),\n ]);\n } catch (err) {\n if (rejectedByGuard(err)) return null;\n throw err;\n }\n return clean;\n },\n\n async incrementOne(model, id, { increment, set }) {\n const indexedFields = new Set(\n (indexMap[model] ?? []).flatMap((p) => [...p.pk, ...(p.sk ?? [])]),\n );\n const touchesIndex = Object.keys(set ?? {}).some((f) =>\n indexedFields.has(f),\n );\n const ttlField = ttlFieldFor(model);\n const touchesTtl = Boolean(ttlField && set && ttlField in set);\n\n // A native ADD cannot also re-encode GSI keys or the TTL attribute, so a\n // `set` that moves an indexed or TTL field goes the read-modify-write\n // route instead of silently leaving stale index rows behind.\n if (touchesIndex || touchesTtl) {\n return guardedWrite<StoreItem | null>(\n model,\n id,\n (existing) => {\n const clean = stripReserved(existing)!;\n const merged = { ...clean, ...(set ?? {}) };\n for (const [field, delta] of Object.entries(increment)) {\n const current = merged[field];\n merged[field] =\n (typeof current === \"number\" ? current : 0) + delta;\n }\n return {\n items: [\n {\n Put: {\n TableName: tableName,\n Item: {\n ...merged,\n ...encodeKeys(model, merged, indexMap, assignment),\n ...ttlAttributesFor(model, merged),\n [REVISION]: randomUUID(),\n },\n ...revisionGuard(existing),\n },\n },\n ...uniqueMarkerDiff(model, clean, merged, id),\n ],\n result: merged,\n };\n },\n () => null,\n );\n }\n\n const names: Record<string, string> = { \"#pk\": PK, \"#rev\": REVISION };\n const values: Record<string, unknown> = { \":rev\": randomUUID() };\n const addClauses: string[] = [];\n const setClauses: string[] = [\"#rev = :rev\"];\n let i = 0;\n for (const [field, delta] of Object.entries(increment)) {\n names[`#f${i}`] = field;\n values[`:v${i}`] = delta;\n addClauses.push(`#f${i} :v${i}`);\n i++;\n }\n for (const [field, value] of Object.entries(set ?? {})) {\n names[`#f${i}`] = field;\n values[`:v${i}`] = value;\n setClauses.push(`#f${i} = :v${i}`);\n i++;\n }\n const expression = [\n `SET ${setClauses.join(\", \")}`,\n addClauses.length ? `ADD ${addClauses.join(\", \")}` : null,\n ]\n .filter(Boolean)\n .join(\" \");\n\n try {\n const res = await doc.send(\n new UpdateCommand({\n TableName: tableName,\n Key: primaryKey(model, id),\n UpdateExpression: expression,\n // Without this, `ADD` would happily create the row it was told to\n // increment, turning \"increment an existing counter\" into an upsert.\n ConditionExpression: \"attribute_exists(#pk)\",\n ExpressionAttributeNames: names,\n ExpressionAttributeValues: values,\n ReturnValues: \"ALL_NEW\",\n }),\n );\n const item = res.Attributes as StoreItem | undefined;\n return isExpired(item) ? null : stripReserved(item);\n } catch (err) {\n if (isConditionalCheckFailed(err)) return null;\n throw err;\n }\n },\n\n async queryIndex({ model, index, key, cursor }): Promise<QueryPage> {\n const lookup = encodeLookupQuery(model, index, key, indexMap, assignment);\n const names: Record<string, string> = { \"#pk\": lookup.pkAttr };\n const values: Record<string, unknown> = { \":pk\": lookup.pkValue };\n let condition = \"#pk = :pk\";\n if (lookup.skPrefix) {\n names[\"#sk\"] = lookup.skAttr;\n values[\":skp\"] = lookup.skPrefix;\n condition += \" AND begins_with(#sk, :skp)\";\n }\n const { items, cursor: next } = await query(\n {\n IndexName: lookup.indexName,\n KeyConditionExpression: condition,\n ExpressionAttributeNames: names,\n ExpressionAttributeValues: values,\n },\n cursor,\n );\n return { items, cursor: next };\n },\n\n async listByType({ model, cursor }): Promise<QueryPage> {\n const { items, cursor: next } = await query(\n {\n IndexName: TYPE_INDEX,\n KeyConditionExpression: \"#tpk = :tpk\",\n ExpressionAttributeNames: { \"#tpk\": TYPE_PK },\n ExpressionAttributeValues: { \":tpk\": model },\n },\n cursor,\n );\n return { items, cursor: next };\n },\n\n async count({ model, index, key }) {\n // `Select: COUNT` counts rows DynamoDB has not reaped yet, which would\n // over-report a model whose rows expire logically. Fall back to draining.\n if (modelHasTtl(model)) return null;\n\n let base: Parameters<typeof query>[0];\n if (index && key) {\n const lookup = encodeLookupQuery(\n model,\n index,\n key,\n indexMap,\n assignment,\n );\n base = {\n IndexName: lookup.indexName,\n KeyConditionExpression: \"#pk = :pk\",\n ExpressionAttributeNames: { \"#pk\": lookup.pkAttr },\n ExpressionAttributeValues: { \":pk\": lookup.pkValue },\n };\n } else {\n base = {\n IndexName: TYPE_INDEX,\n KeyConditionExpression: \"#tpk = :tpk\",\n ExpressionAttributeNames: { \"#tpk\": TYPE_PK },\n ExpressionAttributeValues: { \":tpk\": model },\n };\n }\n let total = 0;\n let cursor: unknown = undefined;\n let pages = 0;\n do {\n const page = await query(base, cursor, true);\n total += page.count;\n cursor = page.cursor;\n if (++pages >= maxPages && cursor) {\n throw new DynamoDBAdapterError(\n `better-auth-dynamodb: counting \"${model}\" exceeded maxPages ` +\n `(${maxPages}) with more pages remaining. Raise maxPages or ` +\n `narrow the query — returning a partial count would be wrong.`,\n );\n }\n } while (cursor);\n return total;\n },\n\n createSchema: ({ file }) =>\n Promise.resolve(\n generateSchemaFile({\n tableName,\n lookupSlots: assignment.maxSlots,\n file,\n ttlAttribute: ttl ? ttlAttribute : undefined,\n }),\n ),\n };\n}\n","import { createHash } from \"node:crypto\";\n\nimport { DynamoDBAdapterError } from \"../../errors\";\nimport type { IndexMap } from \"../../index-map\";\nimport type { StoreItem } from \"../../types\";\n\n/**\n * Physical key encoding for the built-in single-table store.\n *\n * All reserved attributes are namespaced under `__ba_` so they never collide\n * with Better Auth field names, and are stripped before items are returned. The\n * table has a `PK`/`SK` primary key, one `byType` GSI (to list a whole model),\n * and N generic lookup GSIs — one physical slot per logical index a model uses.\n *\n * ## Why keys are length-prefixed\n *\n * Every variable component of a key is written as `s<byteLength>:<value>`. A\n * naive `a#b#c` join lets a value containing the delimiter forge a different\n * key: with composite partition keys, `(\"a#b\", \"c\")` and `(\"a\", \"b#c\")` encode\n * identically, and a sort-key `begins_with(\"cred#\")` probe matches a row whose\n * value is literally `cred#ential`. Length prefixes make that structurally\n * impossible — `s4:cred#` can never be a prefix of `s10:credential#` — so no\n * field value, whatever a provider or a user puts in it, can be made to collide\n * with or over-match another row's key.\n */\nexport const PK = \"__ba_pk\";\nexport const SK = \"__ba_sk\";\nexport const TYPE_PK = \"__ba_tpk\";\nexport const TYPE_SK = \"__ba_tsk\";\nexport const TYPE_INDEX = \"byType\";\n/** Hidden per-row revision, used as the optimistic-write guard. */\nexport const REVISION = \"__ba_rev\";\n/** Default DynamoDB TTL attribute (epoch seconds). Configurable per store. */\nexport const DEFAULT_TTL_ATTRIBUTE = \"__ba_ttl\";\nconst SK_CONST = \"#\";\nconst RESERVED_PREFIX = \"__ba_\";\n\n/** Row-kind discriminators. Fixed literals, never derived from user data. */\nexport const ENTITY_PREFIX = \"E\";\nconst LOOKUP_PREFIX = \"L\";\nexport const UNIQUE_PREFIX = \"U\";\n\n/** DynamoDB's own key size limits. */\nconst MAX_PARTITION_KEY_BYTES = 2048;\nconst MAX_SORT_KEY_BYTES = 1024;\n\n/**\n * Byte budget for a single encoded field value before it is hashed instead.\n *\n * The threshold is fixed rather than derived from the assembled key, because a\n * value must encode identically no matter which key it lands in — otherwise a\n * write and its matching lookup would disagree.\n */\nconst MAX_VALUE_COMPONENT_BYTES = 256;\n\nexport const gsiName = (slot: number): string => `lookup${slot}`;\nexport const gsiPk = (slot: number): string => `__ba_g${slot}pk`;\nexport const gsiSk = (slot: number): string => `__ba_g${slot}sk`;\n\nconst byteLength = (value: string): number => Buffer.byteLength(value, \"utf8\");\n\n/** `s<byteLength>:<value>` — the self-delimiting component encoding. */\nconst component = (value: string): string => `s${byteLength(value)}:${value}`;\n\n/**\n * Type-tagged rendering of a field value, so `1`, `\"1\"`, and `true` cannot\n * share a key. Dates normalise to ISO-8601 so an equivalent `Date` and string\n * agree.\n */\nexport function encodeValue(value: unknown): string {\n if (value === null || value === undefined) return \"null:\";\n if (value instanceof Date) return `date:${value.toISOString()}`;\n switch (typeof value) {\n case \"string\":\n return `string:${value}`;\n case \"number\":\n return `number:${value}`;\n case \"boolean\":\n return `boolean:${value}`;\n case \"bigint\":\n return `bigint:${value}`;\n case \"symbol\":\n return `symbol:${value.description ?? \"\"}`;\n case \"function\":\n return \"function:\";\n default:\n // Objects have no meaningful key form; JSON at least keeps them distinct\n // rather than collapsing every one of them to \"[object Object]\".\n return `object:${JSON.stringify(value)}`;\n }\n}\n\n/**\n * Encode one field value as a key component, hashing it when it is too long to\n * sit in a DynamoDB key. `hs:` and `s<digits>:` are disjoint prefixes, so a\n * hashed component can never be confused with a literal one.\n */\nfunction valueComponent(value: unknown): string {\n const typed = encodeValue(value);\n if (byteLength(typed) > MAX_VALUE_COMPONENT_BYTES) {\n return `hs:${createHash(\"sha256\").update(typed).digest(\"hex\")}`;\n }\n return component(typed);\n}\n\nconst joinValues = (values: unknown[]): string =>\n values.map(valueComponent).join(\"#\");\n\nfunction checkedKey(\n value: string,\n label: string,\n maxBytes: number,\n keyType: \"partition\" | \"sort\",\n): string {\n if (byteLength(value) <= maxBytes) return value;\n throw new DynamoDBAdapterError(\n `better-auth-dynamodb: ${label} exceeds DynamoDB's ${maxBytes}-byte ` +\n `${keyType} key limit. Field values are hashed past ` +\n `${MAX_VALUE_COMPONENT_BYTES} bytes, so this means the model name, ` +\n `index name, or record id is itself too long.`,\n );\n}\n\nconst partitionKey = (value: string, label: string): string =>\n checkedKey(value, label, MAX_PARTITION_KEY_BYTES, \"partition\");\n\nconst sortKey = (value: string, label: string): string =>\n checkedKey(value, label, MAX_SORT_KEY_BYTES, \"sort\");\n\n/** Physical slot assignment: model -> (logical index name -> GSI slot number). */\nexport interface SlotAssignment {\n slots: Record<string, Record<string, number>>;\n maxSlots: number;\n}\n\n/**\n * Assign each logical index to a physical GSI slot. Because indexes on\n * different models never coexist on one item, a model's Nth index always maps\n * to slot N — so the table needs only `max(indexes per model)` lookup GSIs.\n */\nexport function assignSlots(indexMap: IndexMap): SlotAssignment {\n const slots: Record<string, Record<string, number>> = {};\n let maxSlots = 0;\n for (const [model, patterns] of Object.entries(indexMap)) {\n const modelSlots: Record<string, number> = {};\n patterns.forEach((pattern, i) => {\n modelSlots[pattern.index] = i + 1;\n });\n slots[model] = modelSlots;\n maxSlots = Math.max(maxSlots, patterns.length);\n }\n return { slots, maxSlots };\n}\n\nexport const primaryKey = (model: string, id: string) => ({\n [PK]: partitionKey(\n `${ENTITY_PREFIX}#${component(model)}#${component(id)}`,\n \"entity partition key\",\n ),\n [SK]: SK_CONST,\n});\n\n/**\n * One marker row per (model, unique index, value) tuple. Written in the same\n * transaction as the entity row with `attribute_not_exists`, which is what\n * makes uniqueness a property of the database rather than of Better Auth's\n * check-then-insert.\n */\nexport const uniqueMarkerKey = (\n model: string,\n index: string,\n values: unknown[],\n) => ({\n [PK]: partitionKey(\n `${UNIQUE_PREFIX}#${component(model)}#${component(index)}#${joinValues(values)}`,\n \"unique marker partition key\",\n ),\n [SK]: SK_CONST,\n});\n\nconst lookupPkValue = (model: string, index: string, values: unknown[]) =>\n partitionKey(\n `${LOOKUP_PREFIX}#${component(model)}#${component(index)}#${joinValues(values)}`,\n \"lookup partition key\",\n );\n\n/**\n * Compute the reserved key attributes to store alongside an item: the primary\n * key, the type-index key, and a lookup key for every index whose partition\n * fields are all present on the item.\n */\nexport function encodeKeys(\n model: string,\n item: StoreItem,\n indexMap: IndexMap,\n assignment: SlotAssignment,\n): StoreItem {\n const id = String(item.id);\n const keys: StoreItem = {\n ...primaryKey(model, id),\n [TYPE_PK]: model,\n // Deliberately *not* length-prefixed: this sort key exists to order a\n // model's rows by creation time, and a byte-length prefix would sort\n // lexicographically by length instead of chronologically. It is never used\n // for `begins_with` lookups, so it carries no collision risk.\n [TYPE_SK]: sortKey(\n `${String(item.createdAt ?? \"\")}#${id}`,\n \"type sort key\",\n ),\n };\n\n for (const pattern of indexMap[model] ?? []) {\n const pkValues = pattern.pk.map((f) => item[f]);\n if (pkValues.some((v) => v == null)) continue; // index doesn't apply to this row\n const slot = assignment.slots[model]?.[pattern.index];\n if (!slot) continue;\n keys[gsiPk(slot)] = lookupPkValue(model, pattern.index, pkValues);\n const skValues = (pattern.sk ?? []).map((f) => item[f]);\n keys[gsiSk(slot)] = sortKey(\n skValues.some((v) => v == null)\n ? component(id)\n : `${joinValues(skValues)}#${component(id)}`,\n \"lookup sort key\",\n );\n }\n\n return keys;\n}\n\n/** A resolved lookup query against one GSI slot. */\nexport interface LookupQuery {\n indexName: string;\n pkAttr: string;\n pkValue: string;\n skAttr: string;\n /** When set, narrow with `begins_with(skAttr, skPrefix)`. */\n skPrefix?: string;\n}\n\n/**\n * Translate a logical index query into a concrete GSI query. Uses the\n * partition key always, and a `begins_with` sort-key prefix when the caller\n * supplied any leading sort-key field values.\n */\nexport function encodeLookupQuery(\n model: string,\n index: string,\n key: StoreItem,\n indexMap: IndexMap,\n assignment: SlotAssignment,\n): LookupQuery {\n const slot = assignment.slots[model]?.[index];\n if (!slot) {\n throw new DynamoDBAdapterError(\n `No physical slot for index \"${index}\" on model \"${model}\". ` +\n `Ensure the index is present in the adapter's index map.`,\n );\n }\n const pattern = (indexMap[model] ?? []).find((p) => p.index === index)!;\n const pkValues = pattern.pk.map((f) => key[f]);\n const skPresent = (pattern.sk ?? [])\n .map((f) => key[f])\n .filter((v) => v != null);\n\n return {\n indexName: gsiName(slot),\n pkAttr: gsiPk(slot),\n pkValue: lookupPkValue(model, index, pkValues),\n skAttr: gsiSk(slot),\n skPrefix: skPresent.length > 0 ? `${joinValues(skPresent)}#` : undefined,\n };\n}\n\n/** Remove all reserved (`__ba_`) attributes, yielding clean Better Auth data. */\nexport function stripReserved(\n item: StoreItem | undefined | null,\n): StoreItem | null {\n if (!item) return null;\n const clean: StoreItem = {};\n for (const [k, v] of Object.entries(item)) {\n if (!k.startsWith(RESERVED_PREFIX)) clean[k] = v;\n }\n return clean;\n}\n","import type {\n CreateTableCommandInput,\n DynamoDBClient,\n} from \"@aws-sdk/client-dynamodb\";\nimport {\n CreateTableCommand,\n ResourceInUseException,\n UpdateTimeToLiveCommand,\n waitUntilTableExists,\n} from \"@aws-sdk/client-dynamodb\";\n\nimport {\n gsiName,\n gsiPk,\n gsiSk,\n PK,\n SK,\n TYPE_INDEX,\n TYPE_PK,\n TYPE_SK,\n} from \"./key-codec\";\n\n/** Build the CreateTable input for the built-in store's single-table layout. */\nexport function buildTableDefinition(\n tableName: string,\n lookupSlots: number,\n): CreateTableCommandInput {\n const attr = (name: string) => ({\n AttributeName: name,\n AttributeType: \"S\" as const,\n });\n\n const attributeDefinitions = [\n attr(PK),\n attr(SK),\n attr(TYPE_PK),\n attr(TYPE_SK),\n ];\n const globalSecondaryIndexes = [\n {\n IndexName: TYPE_INDEX,\n KeySchema: [\n { AttributeName: TYPE_PK, KeyType: \"HASH\" as const },\n { AttributeName: TYPE_SK, KeyType: \"RANGE\" as const },\n ],\n Projection: { ProjectionType: \"ALL\" as const },\n },\n ];\n\n for (let slot = 1; slot <= lookupSlots; slot++) {\n attributeDefinitions.push(attr(gsiPk(slot)), attr(gsiSk(slot)));\n globalSecondaryIndexes.push({\n IndexName: gsiName(slot),\n KeySchema: [\n { AttributeName: gsiPk(slot), KeyType: \"HASH\" as const },\n { AttributeName: gsiSk(slot), KeyType: \"RANGE\" as const },\n ],\n Projection: { ProjectionType: \"ALL\" as const },\n });\n }\n\n return {\n TableName: tableName,\n BillingMode: \"PAY_PER_REQUEST\",\n KeySchema: [\n { AttributeName: PK, KeyType: \"HASH\" },\n { AttributeName: SK, KeyType: \"RANGE\" },\n ],\n AttributeDefinitions: attributeDefinitions,\n GlobalSecondaryIndexes: globalSecondaryIndexes,\n };\n}\n\n/**\n * Create the table if it does not already exist and wait until it is active.\n * Idempotent — safe to call at startup or in test `beforeAll` hooks.\n */\nexport async function ensureSchema(opts: {\n client: DynamoDBClient;\n tableName: string;\n lookupSlots: number;\n /**\n * Enable DynamoDB TTL on this attribute. Pass the same value the adapter is\n * configured with (`ttl.attributeName`, default `__ba_ttl`) — the adapter\n * writes the attribute, but only the table setting makes AWS act on it.\n */\n ttlAttribute?: string;\n}): Promise<void> {\n const input = buildTableDefinition(opts.tableName, opts.lookupSlots);\n try {\n await opts.client.send(new CreateTableCommand(input));\n } catch (error) {\n if (!(error instanceof ResourceInUseException)) throw error; // already exists\n }\n await waitUntilTableExists(\n { client: opts.client, maxWaitTime: 60 },\n { TableName: opts.tableName },\n );\n\n if (!opts.ttlAttribute) return;\n try {\n await opts.client.send(\n new UpdateTimeToLiveCommand({\n TableName: opts.tableName,\n TimeToLiveSpecification: {\n Enabled: true,\n AttributeName: opts.ttlAttribute,\n },\n }),\n );\n } catch (error) {\n // Re-enabling TTL on the same attribute is a no-op AWS reports as an\n // error; anything else is a real provisioning failure.\n if (!isAlreadyEnabled(error)) throw error;\n }\n}\n\nconst isAlreadyEnabled = (error: unknown): boolean =>\n typeof error === \"object\" &&\n error !== null &&\n \"name\" in error &&\n (error as { name?: unknown }).name === \"ValidationException\" &&\n typeof (error as unknown as { message?: unknown }).message === \"string\" &&\n (error as unknown as { message: string }).message.includes(\"already\");\n\n/**\n * Better Auth CLI `generate` hook: emit a portable CloudFormation template for\n * the built-in store's table. Table name and lookup-GSI count are configurable\n * (unlike a hardcoded layout), so the generated stack matches the adapter's\n * actual access patterns.\n */\nexport function generateSchemaFile(opts: {\n tableName: string;\n lookupSlots: number;\n file?: string;\n /** When set, the template enables DynamoDB TTL on this attribute. */\n ttlAttribute?: string;\n}): { code: string; path: string; overwrite: boolean } {\n const table = {\n ...buildTableDefinition(opts.tableName, opts.lookupSlots),\n ...(opts.ttlAttribute\n ? {\n TimeToLiveSpecification: {\n Enabled: true,\n AttributeName: opts.ttlAttribute,\n },\n }\n : {}),\n };\n const template = {\n AWSTemplateFormatVersion: \"2010-09-09\",\n Resources: {\n BetterAuthTable: { Type: \"AWS::DynamoDB::Table\", Properties: table },\n },\n };\n const code = `/**\n * Auto-generated DynamoDB CloudFormation template for @datar-platform/better-auth-dynamodb.\n * Table: ${opts.tableName} — ${opts.lookupSlots} lookup GSI(s) + 1 byType GSI.\n * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html\n */\nexport const dynamoDBSchema = ${JSON.stringify(template, null, 2)} as const;\n\nexport default dynamoDBSchema;\n`;\n return {\n code,\n path: opts.file ?? \"dynamodb-cloudformation.ts\",\n overwrite: true,\n };\n}\n","import type { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport {\n DeleteCommand,\n DynamoDBDocumentClient,\n PutCommand,\n ScanCommand,\n TransactWriteCommand,\n type TransactWriteCommandInput,\n} from \"@aws-sdk/lib-dynamodb\";\n\nimport { DynamoDBAdapterError } from \"../../errors\";\nimport type { AccessPattern, IndexMap } from \"../../index-map\";\nimport type { StoreItem, TtlOptions } from \"../../types\";\nimport {\n assignSlots,\n DEFAULT_TTL_ATTRIBUTE,\n encodeKeys,\n encodeValue,\n ENTITY_PREFIX,\n PK,\n primaryKey,\n REVISION,\n SK,\n stripReserved,\n TYPE_PK,\n uniqueMarkerKey,\n UNIQUE_PREFIX,\n} from \"./key-codec\";\n\n/**\n * One-time migration from the 0.1.x key format to 0.2.0's.\n *\n * 0.2.0 changed how keys are built — components are length-prefixed and\n * type-tagged so a value containing the delimiter cannot forge another key —\n * which means rows written by 0.1.x are not *lost*, but are no longer found by\n * any lookup. On a dev table the answer is to recreate it. On a table holding\n * real users, \"recreate it\" means an outage, so this rewrites the rows in place\n * instead.\n *\n * It also backfills the uniqueness markers 0.1.x never wrote, so uniqueness is\n * enforced for the rows that already exist rather than only for new ones.\n *\n * Deliberately explicit rather than automatic: silently rewriting an auth table\n * the first time a new version boots is not a decision a library should make on\n * its own. Run it once, from a deploy step or a script.\n *\n * Safe to run repeatedly. On a table with nothing to migrate — already\n * migrated, or created fresh on 0.2.0 — it is a no-op.\n */\n\ntype TransactItem = NonNullable<\n TransactWriteCommandInput[\"TransactItems\"]\n>[number];\n\nexport interface MigrateKeysOptions {\n /** Table to migrate. */\n tableName: string;\n /**\n * The same access-pattern map the adapter runs with — normally\n * `deriveIndexMap(getAuthTables(betterAuthOptions))`. Keys and markers are\n * rebuilt from it, so a mismatch here produces a table the adapter cannot\n * read.\n */\n indexMap: IndexMap;\n /** Pre-built document client. Preferred, for the usual credential reasons. */\n documentClient?: DynamoDBDocumentClient;\n /** Raw client, wrapped internally. Ignored when `documentClient` is given. */\n client?: DynamoDBClient;\n /**\n * Report what would change without writing anything. Run this first — the\n * report names any duplicate that would block the migration.\n */\n dryRun?: boolean;\n /** Backfill uniqueness markers. Default `true`. */\n atomicUniqueness?: boolean;\n /** Same TTL configuration the adapter uses, so migrated rows carry it too. */\n ttl?: TtlOptions | false;\n /** Called after each page, for progress on a large table. */\n onProgress?: (progress: { scanned: number; migrated: number }) => void;\n}\n\n/** A value that more than one row claims, so a unique marker cannot cover it. */\nexport interface UniquenessConflict {\n model: string;\n index: string;\n /** Ids of every row holding the duplicated value. */\n ids: string[];\n}\n\nexport interface MigrationReport {\n /** Rows examined. */\n scanned: number;\n /** Rows rewritten into the current key format (or that would be, on a dry run). */\n migrated: number;\n /** Rows already in the current format, left untouched. */\n alreadyCurrent: number;\n /** Uniqueness marker rows created. */\n markersCreated: number;\n /**\n * Duplicates found. Non-empty means the migration did not run: 0.1.x could\n * admit two rows with the same `unique` value, and there is no correct way\n * for a migration to choose which one keeps it.\n */\n conflicts: UniquenessConflict[];\n /** True when nothing needed doing. */\n noop: boolean;\n}\n\nconst epochSeconds = (value: unknown): number | undefined => {\n const date =\n value instanceof Date\n ? value\n : typeof value === \"string\" || typeof value === \"number\"\n ? new Date(value)\n : undefined;\n if (!date || Number.isNaN(date.getTime())) return undefined;\n return Math.floor(date.getTime() / 1000);\n};\n\n/**\n * A row written by 0.1.x. Its partition key is a bare `model#id`, with none of\n * the row-kind prefixes 0.2.0 writes — so the test is simply whether the key\n * carries a prefix this version would have written.\n */\nconst isLegacyRow = (item: StoreItem): boolean => {\n const pk = item[PK];\n if (typeof pk !== \"string\") return false;\n return (\n !pk.startsWith(`${ENTITY_PREFIX}#`) && !pk.startsWith(`${UNIQUE_PREFIX}#`)\n );\n};\n\nconst isMarkerRow = (item: StoreItem): boolean => {\n const pk = item[PK];\n return typeof pk === \"string\" && pk.startsWith(`${UNIQUE_PREFIX}#`);\n};\n\nexport async function migrateKeys(\n opts: MigrateKeysOptions,\n): Promise<MigrationReport> {\n const doc =\n opts.documentClient ??\n (opts.client\n ? DynamoDBDocumentClient.from(opts.client, {\n marshallOptions: { removeUndefinedValues: true },\n })\n : undefined);\n if (!doc) {\n throw new DynamoDBAdapterError(\n \"better-auth-dynamodb: migrateKeys needs a `documentClient` or a `client`.\",\n );\n }\n\n const { tableName, indexMap } = opts;\n const assignment = assignSlots(indexMap);\n const atomicUniqueness = opts.atomicUniqueness ?? true;\n const ttl = opts.ttl === false ? undefined : opts.ttl;\n const ttlAttribute = ttl?.attributeName ?? DEFAULT_TTL_ATTRIBUTE;\n\n const ttlAttributesFor = (model: string, item: StoreItem): StoreItem => {\n const field = ttl ? (ttl.fields?.[model] ?? ttl.defaultField) : undefined;\n if (!field) return {};\n const seconds = epochSeconds(item[field]);\n return seconds === undefined ? {} : { [ttlAttribute]: seconds };\n };\n\n const uniquePatternsFor = (model: string): AccessPattern[] =>\n atomicUniqueness ? (indexMap[model] ?? []).filter((p) => p.unique) : [];\n\n // ---- pass one: read the whole table -------------------------------------\n //\n // A full scan is the right tool exactly once: the migration genuinely has to\n // visit every row, and no index describes \"rows in the old format\".\n const legacy: StoreItem[] = [];\n let scanned = 0;\n let alreadyCurrent = 0;\n let cursor: Record<string, unknown> | undefined;\n\n do {\n const page = await doc.send(\n new ScanCommand({ TableName: tableName, ExclusiveStartKey: cursor }),\n );\n for (const raw of page.Items ?? []) {\n const item = raw as StoreItem;\n scanned++;\n if (isMarkerRow(item)) {\n alreadyCurrent++;\n } else if (isLegacyRow(item)) {\n legacy.push(item);\n } else {\n alreadyCurrent++;\n }\n }\n cursor = page.LastEvaluatedKey;\n opts.onProgress?.({ scanned, migrated: 0 });\n } while (cursor);\n\n if (legacy.length === 0) {\n return {\n scanned,\n migrated: 0,\n alreadyCurrent,\n markersCreated: 0,\n conflicts: [],\n noop: true,\n };\n }\n\n // ---- pass two: find duplicates before writing anything ------------------\n //\n // 0.1.x enforced uniqueness in Better Auth's application layer, which two\n // concurrent writers could both pass. If that ever happened, the table holds\n // a duplicate no marker can cover — and picking a winner is not a migration's\n // decision to make. Report and stop.\n const claims = new Map<\n string,\n { model: string; index: string; ids: string[] }\n >();\n for (const item of legacy) {\n const model = String(item[TYPE_PK] ?? \"\");\n const id = String(item.id);\n for (const pattern of uniquePatternsFor(model)) {\n const values = pattern.pk.map((f) => item[f]);\n if (values.some((v) => v == null)) continue;\n const key = `${model}\u0000${pattern.index}\u0000${values.map(encodeValue).join(\"\u0000\")}`;\n const existing = claims.get(key);\n if (existing) existing.ids.push(id);\n else claims.set(key, { model, index: pattern.index, ids: [id] });\n }\n }\n const conflicts: UniquenessConflict[] = [...claims.values()]\n .filter((c) => c.ids.length > 1)\n .map(({ model, index, ids }) => ({ model, index, ids }));\n\n if (conflicts.length > 0) {\n return {\n scanned,\n migrated: 0,\n alreadyCurrent,\n markersCreated: 0,\n conflicts,\n noop: false,\n };\n }\n\n // ---- pass three: rewrite ------------------------------------------------\n let migrated = 0;\n let markersCreated = 0;\n\n for (const item of legacy) {\n const model = String(item[TYPE_PK] ?? \"\");\n const clean = stripReserved(item)!;\n const id = String(clean.id);\n\n const newItem: StoreItem = {\n ...clean,\n ...encodeKeys(model, clean, indexMap, assignment),\n ...ttlAttributesFor(model, clean),\n // A fresh revision, so the optimistic guard has something to hold onto.\n [REVISION]: `migrated-${id}`,\n };\n\n const markers: TransactItem[] = uniquePatternsFor(model).flatMap(\n (pattern) => {\n const values = pattern.pk.map((f) => clean[f]);\n if (values.some((v) => v == null)) return [];\n return [\n {\n Put: {\n TableName: tableName,\n Item: {\n ...uniqueMarkerKey(model, pattern.index, values),\n __ba_owner: id,\n ...ttlAttributesFor(model, clean),\n },\n ConditionExpression: \"attribute_not_exists(#pk)\",\n ExpressionAttributeNames: { \"#pk\": PK },\n },\n },\n ];\n },\n );\n\n if (opts.dryRun) {\n migrated++;\n markersCreated += markers.length;\n continue;\n }\n\n // Write the new row before removing the old one, so an interrupted run\n // leaves both rather than neither.\n if (markers.length === 0) {\n await doc.send(new PutCommand({ TableName: tableName, Item: newItem }));\n } else {\n await doc.send(\n new TransactWriteCommand({\n TransactItems: [\n { Put: { TableName: tableName, Item: newItem } },\n ...markers,\n ],\n }),\n );\n }\n await doc.send(\n new DeleteCommand({\n TableName: tableName,\n Key: { [PK]: item[PK], [SK]: item[SK] },\n }),\n );\n\n migrated++;\n markersCreated += markers.length;\n opts.onProgress?.({ scanned, migrated });\n }\n\n return {\n scanned,\n migrated,\n alreadyCurrent,\n markersCreated,\n conflicts: [],\n noop: false,\n };\n}\n\n/** Re-exported for callers building their own primary keys around a migration. */\nexport { primaryKey };\n"],"mappings":"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","names":["DEFAULT_MAX_PAGES","DeleteCommand","DynamoDBDocumentClient","PutCommand","TransactWriteCommand","epochSeconds","DynamoDBDocumentClient","PutCommand","TransactWriteCommand","DeleteCommand"]}
|
package/package.json
CHANGED